BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Personal workout tracker. Next.js + Supabase PWA replacing a Notion database.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Personal workout tracker. Replaces a four-table Notion database with a typed Postgres schema, a PWA that installs to the iPhone home screen, and per-set logging that drives real progress charts.
Single user, single deployment, single source of truth (Supabase).
| Layer | Choice |
|---|---|
| Framework | Next.js 16 + React 19 (App Router) |
| Hosting | Vercel |
| Database | Supabase Postgres |
| Auth | Supabase Auth (magic link, not yet wired) |
| Client state | TanStack Query |
| Styling | Tailwind 4 |
| Components | shadcn/ui (added per-screen) |
| PWA | @ducanh2912/next-pwa |
| Toasts | Sonner |
| Tests | Vitest |
| Package manager | bun |
Mirrors the conventions in Personal/projects/nutrition-app/v1/ (Plately).
Milestone 1 (current). Scaffold, database schema, Notion seed script, PWA shell. No screens. No auth UI. No analytics page.
Run bun run build to confirm the project compiles. Run bun run test
to exercise the parsers, the recovery domain, the program contract and
the migrations (284 tests across 21 files).
Milestone 2 (next). Screens: calendar home, active workout, template detail, exercise library, history, analytics.
# Install dependencies
bun install
# Generate placeholder PWA icons (already committed, only needed if you change the design)
bun run icons:generate
# Run the dev server
bun run dev
# Run tests
bun run test
bun run test:watch
# Production build
bun run build
The dev server uses webpack (not Turbopack) because @ducanh2912/next-pwa
injects a webpack config. PWA features only activate in the production
build; bun run dev runs without a service worker.
Copy .env.local.example to .env.local and fill in the keys. The seed
script reads from Notion and writes to Supabase using the service-role key.
You will need:
auth.users id, captured after signing in onceSource of truth lives in supabase/migrations/.
# Push migrations to the linked Supabase project
supabase link --project-ref <your-ref>
supabase db push
# Reset local schema (DANGER: drops data)
supabase db reset
The migration test applies every file in supabase/migrations/ to an
in-process Postgres (PGlite) with Supabase's roles stubbed, so schema
mistakes fail here before supabase db push.
The schema has eighteen tables, thirteen enums, RLS on every table, and
five analytics views. See CLAUDE.md for the full list of tables and
enums.
Library tables (muscle_groups, exercises, exercise_alternates,
templates, template_exercises) allow reads by any authenticated user.
Per-user tables (workouts, workout_exercises, sets,
user_settings) filter by auth.uid().
bun run seed:notion
Reads the four Notion databases and writes to Supabase via the service
role. Idempotent: each table has a _notion_id column the script
upserts on. The column stays; it is now the ownership key the three
seeds (template:, achilles:, program:) use to clear and rewrite
only their own rows.
Templates are rebuilt from each canonical name's most recent Workouts
row because Notion page templates keep their prescription in a button
automation the API cannot read. The canonical names live in
scripts/lib/template-name.ts.
The seed writes a verification report to scripts/seed-report.json with:
equipment_type=other (need a hand-fix)machine_location setWeight field failed to parse, with their Notion URLsWorkouts relation, and Sessions rows with no parent workoutThe Weight CSV parser lives at scripts/parse-weight-csv.ts with full
test coverage in scripts/__tests__/parse-weight-csv.test.ts.
bun run seed:achilles
Runs after the Notion seed. Writes the recovery program and its phases,
the sources, the reviewed exercises with every authoring input and the
demonstration clips, the three recovery templates with their phases,
and the user's recovery row, clearances, events, rules and available
equipment. The per-user rows come from
scripts/data/achilles/personal.local.json, which is gitignored; its
shape lives in scripts/data/achilles/personal.example.json.
Exercises match the library by slug. A matched row gets its authoring
inputs updated and keeps its name, group and Notion demo link unless
the seed carries a verified clip for it, and its existing alternates
are never overwritten: the seed fills empty slots, corrects the rows it
wrote itself, and reports the ones it left alone. A template row that knowingly
breaks an authoring rule carries override_rule and override_reason,
so a conflict the user chose to keep is recorded on the row rather than
left silent.
Idempotent. The seed writes scripts/seed-achilles-report.json with
row counts, which exercises matched versus inserted, alternate slots
kept as they were, unverified videos, rejected doses, and the overrides
recorded.
bun run convert:ppl # vault workbook -> scripts/data/programs/<name>.json
bun run extract:pdfs # vault PDFs -> scripts/data/programs/raw/<name>.json (needs python3 + pdfplumber)
bun run convert:ppl1 # PPL 1.0 dump -> program JSON
bun run convert:powerbuilding # Powerbuilding 4x dump -> program JSON
bun run convert:arm # Arm Hypertrophy dump -> program JSON
bun run seed:programs # every program JSON -> Supabase
A program is blocks of weeks, each week ordered days, each day
ordered exercises with a prescription. The JSON contract lives in
scripts/data/programs/schema.ts; example.json next to it shows the
shape with made-up content. Program content is copyrighted, so every
other JSON in that folder is gitignored and the seed loads whatever is
present.
The converter writes nothing and exits 1 if any row of the workbook
could not be placed or turned into a dose and prints those rows. The
seed validates every file and parses every dose before its first write.
It creates one programs row per file, one program_phases row per
week (block name in block), and one template per day per week,
ordered inside its phase by template_phases.position. Exercises match
the library by slug or a near-duplicate slug (plural, db/dumbbell,
bb/barbell); unknown ones are inserted with the equipment type the
name implies and no authoring inputs, and the report's handFix field
lists the seed's exercises still at other or without a muscle group.
Substitutions become alternates on the exercise row; a slot that
already holds a Notion sub-option is left alone and reported. Each
loaded program's rows the JSON no longer has are removed. The Notion
and Achilles templates and any program not in the loaded files are
never touched.
Idempotent. The seed writes scripts/seed-programs-report.json with
row counts, exercises inserted, matched and removed, handFix,
alternates in place, kept and removed, and orphans.
Three Nippard PDFs (PPL 1.0, Powerbuilding 4x, Arm Hypertrophy) go
through a second pipeline into the same contract. extract:pdfs dumps
each PDF's pages (text lines and tables) to gitignored JSON with
pdfplumber (python3 -m pip install pdfplumber); one tested parser per
program in scripts/lib/ builds the contract from its dump and one
CLI per program writes the gitignored JSON, exiting 1 on any row it
could not dose. Powerbuilding week 10 is max testing option A (option B
is reported as omitted) and its optional arm day sits on the odd weeks.
The Get Ready Manual holds no program and stays a reference. The same
seed loads all four JSONs.
.
├── .env.local.example # template for local env vars
├── docs/ # planning, handoff prompt, README snapshot
├── public/ # static assets + PWA manifest + generated icons
├── scripts/
│ ├── parse-weight-csv.ts # pure parser, used by seed
│ ├── seed-from-notion.ts # one-time Notion -> Supabase migration
│ ├── seed-achilles.ts # Achilles program, exercises, templates, personal rows
│ ├── seed-programs.ts # every program JSON -> programs, phases, templates
│ ├── convert-ppl-sheet.ts # vault workbook -> program JSON
│ ├── extract-pdf-tables.py # one PDF -> page dump JSON (pdfplumber)
│ ├── extract-training-pdfs.ts # runs the extractor over the vault's three PDFs
│ ├── convert-ppl1.ts # PPL 1.0 dump -> program JSON
│ ├── convert-powerbuilding.ts # Powerbuilding 4x dump -> program JSON
│ ├── convert-arm.ts # Arm Hypertrophy dump -> program JSON
│ ├── data/achilles/ # committed seed data + gitignored personal.local.json
│ ├── data/programs/ # JSON contract (schema.ts), library map (library.ts), example.json, gitignored program JSON
│ ├── generate-icons.ts # placeholder PWA icon generator
│ ├── lib/
│ │ ├── env.ts # env var loader and validator
│ │ ├── parse-prescription.ts # parse "3-5", "3 mins" etc
│ │ ├── exercise-name.ts # parse ↑↓ arrows, infer equipment_type
│ │ ├── template-name.ts # canonical template names, category and variant
│ │ ├── ppl-sheet.ts # workbook rows -> program JSON, with skipped rows
│ │ ├── exercise-variant.ts # split a set type out of an exercise name, split "A + B" cells
│ │ ├── pdf-program.ts # shared PDF cell helpers: names, notes, rest, RPE or %1RM, doses
│ │ ├── ppl1-pdf.ts # PPL 1.0 dump -> program JSON
│ │ ├── powerbuilding-pdf.ts # Powerbuilding 4x dump -> program JSON
│ │ └── arm-pdf.ts # Arm Hypertrophy dump -> program JSON
│ └── __tests__/ # Vitest unit tests for the parsers
├── src/
│ └── app/ # Next.js App Router
│ ├── layout.tsx # root layout + PWA metadata + viewport
│ └── page.tsx # M1 placeholder
├── supabase/
│ ├── config.toml # supabase CLI local config
│ └── migrations/
│ ├── 0001_initial_schema.sql
│ ├── 0002_analytics_views.sql
│ ├── 0003_exercise_authoring.sql
│ ├── 0004_programs.sql
│ ├── 0005_recovery.sql
│ ├── 0006_programs_weekly.sql
│ └── 0007_template_exercise_variant.sql
├── next.config.ts # PWA wrapper
├── package.json
└── vitest.config.ts
feat:, fix:, chore:, docs:, refactor:.Private. Personal project.