BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.