BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A private college football pick'em for friends and family. Each week the commissioner
publishes a slate of games, members pick winners one game at a time, picks lock at the
first kickoff, and the app grades them against real scores and keeps the season leaderboard.
• Domain terms: CONTEXT.md
• Roadmap and tickets: issue #1
• Research and decisions: docs/research/, docs/adr/
Next.js 16 (App Router, TypeScript, Tailwind, shadcn/ui) on Vercel Hobby. Postgres on Neon
through Drizzle. Vitest for tests. Game data from CollegeFootballData.檢視完整 README ↗A private college football pick'em for friends and family. Each week the commissioner publishes a slate of games, members pick winners one game at a time, picks lock at the first kickoff, and the app grades them against real scores and keeps the season leaderboard.
GLOSSARY.mddocs/research/, docs/adr/Next.js 16 (App Router, TypeScript, Tailwind, shadcn/ui) on Vercel Hobby. Postgres on Neon through Drizzle. Vitest for tests. Game data from CollegeFootballData.
Next.js 16 differs from older versions (proxy.ts instead of middleware.ts, async
cookies()/params, new caching model). Read node_modules/next/dist/docs/ before writing
route or cache code, per AGENTS.md.
Requires Node 20.9 or newer and npm. You need access to the cfb-pickem Vercel project.
npm install
npx vercel login # once; use your GitHub account
npx vercel link # once; pick the cfb-pickem project
npx vercel env pull .env.local # Development env vars, including DATABASE_URL
npm run migrate # apply committed migrations to the Development database
npm run seed # 2026 season + commissioners; prints their Magic Links
npm run dev # http://localhost:3000
.env.local is gitignored. Re-run vercel env pull whenever a variable changes in Vercel.
Never commit secrets; they live only in Vercel.
Managed in Vercel (Production, Preview, Development). Pull them locally with vercel env pull.
| Variable | Source | Purpose |
|---|---|---|
DATABASE_URL | Neon integration (Production, Preview); set by hand for Development | Pooled connection for request-time queries (drizzle-orm/neon-http) |
DATABASE_URL_UNPOOLED | Neon integration (Production, Preview); set by hand for Development | Direct connection for migrations |
CFBD_API_KEY | CollegeFootballData account | Game schedules and scores |
CRON_SECRET | Generated | Bearer token required by scheduled route handlers |
NEXT_PUBLIC_APP_URL | Set by hand | Absolute URL used in magic links and texts. Production: https://slate.midfield-mafia.com. Development: http://localhost:3000. Unset in Preview; code should fall back to https://${VERCEL_URL} |
PINGRAM_API_KEY | Pingram dashboard | Sends text messages (free tier, 100 SMS a month) |
PINGRAM_WEBHOOK_SECRET | Pingram dashboard, Webhooks | Verifies the Events Webhook at /api/pingram/events, which records a member's STOP and START. Production only; unset, every delivery is refused |
Schema lives in Drizzle TypeScript files. Migrations are generated locally, committed, and
applied at deploy time. Never use drizzle-kit push against a shared database.
npx drizzle-kit generate # after editing the schema: writes SQL to drizzle/
git add drizzle/ # commit the SQL with the schema change
The build script runs drizzle-kit migrate against DATABASE_URL_UNPOOLED before
next build, so every deployment corresponds to an already-migrated database. Because the
Neon integration creates a database branch per Preview deployment, each preview migrates its
own branch and production data is never touched by a PR. Locally, run npm run migrate
once after pulling schema changes.
Neon branches: main is production. Vercel creates preview/<git-branch> per Preview
deployment and injects that branch's connection string into the deployment only. The
development branch backs the Vercel Development environment, so .env.local, npm run migrate, and npm run seed never touch production. Its two connection strings are set by
hand in Vercel (Development only) because the integration serves only Production and Preview.
npm run migrate is the same command with the config's .env.local fallback, for local use.
Commissioners build each week at /console/slate. The page reads that week's FBS games,
the latest AP poll, and sportsbook spreads from CollegeFootballData (three calls, cached
in process for ten minutes; "Refresh from feed" bypasses the cache and re-reads kickoffs
for games already on the slate). Filter to ranked or SEC games, add about ten, flag one
as the Tiebreaker Game. The Deadline floats at the earliest kickoff until publish and can
only be moved earlier. Publishing freezes the Deadline and shows the slate to members at
/week; after that a game can only be voided, with a note, never removed. Kickoff times
render in each viewer's own time zone.
Adding a game also snapshots its pick-screen detail into games.detail, in the GameDetail
shape from src/lib/scoring/types.ts: venue and city, TV outlet, the line, pregame win
probability, the forecast, and each team's rank, record, and per-game points and yards. The
CollegeFootballData sources are /records, /stats/season, the season's /games (points;
the stats feed has none), /games/media, /metrics/wp/pregame, /venues, and
/games/weather (temperature, wind, sky; included in the $1 tier). The chance of rain comes
from Open-Meteo, free and keyless, one call per week for every
venue at its kickoff hour; kickoffs past its sixteen-day horizon get no figure. "Refresh from
feed" re-reads all of it, published or not, so the forecast can move during the week.
Members never trigger a feed call: every screen reads the snapshot.
The CollegeFootballData client lives in src/lib/cfbd/ and the Open-Meteo client in
src/lib/weather/. Tests never call the real APIs: they replay the recorded Week 2 2026
responses in each module's fixtures/ folder (the season-wide responses are trimmed to the
teams and venues in that week's games).
Members pick at /picks, one game per screen: tapping a team saves the Pick at once (there is
no submit step) and moves to the next game; the strip on top jumps anywhere, and the flow opens
at the first unpicked game. /picks/review lists every pick, opens the Lock of the Week drawer,
takes the Tiebreaker Guess, and counts down to the Deadline from the server clock. /week shows
the slate with a running count and the door into both.
The server seam is src/lib/picks/: savePick, setLock, setTiebreakerGuess, pickSheet
(the member's own sheet), and weekPicks (everyone's picks, refused for everyone until the
Deadline). Every change is checked against the server clock, so at or after the Deadline the
route handlers under /api/week/ answer 423 Locked and the screens flip to their locked state.
Picks are stored per member per game; unpicked games have no row.
There are no passwords. Each member has a permanent Magic Link (/m/<token>); opening it sets
a year-long httpOnly session cookie and lands on the welcome page the first time, the current
week after that. Commissioners add members and copy, regenerate, or deactivate links at
/console/members. Regenerating or deactivating signs that member out everywhere.
Seed the 2026 season and the two commissioners, and print their Magic Links:
npm run seed # Development database (.env.local)
npx vercel env pull --environment=production .env.production.local # once, for production
npx tsx --env-file=.env.production.local scripts/seed.ts # production database
The seed is idempotent: re-running prints the existing links. SEED_JONAH_PHONE and
SEED_ALEX_PHONE set phone numbers on first creation.
Production is slate.midfield-mafia.com. The domain is
registered at GoDaddy; an A record for slate points at Vercel (76.76.21.21) and the apex is
left free for other Midfield Mafia projects. The cfb-pickem-eight.vercel.app URL keeps working
as an alias.
Pushing to main deploys to production. Every other branch and PR gets a Preview deployment
with its own Neon branch. Vercel Hobby deploys commits from any repo collaborator because the
repo is public; if Vercel comments on a PR asking for authorization, a team owner clicks it once.
Neon Free suspends its compute after 5 idle minutes, so the first request after a quiet spell takes a few hundred milliseconds longer.
scripts/provision-infra.sh walks the commissioner through
creating the Vercel project, attaching Neon, setting the secrets, and creating the Pingram account. It is
idempotent and safe to re-run.