原始内容
Workout Tracker
My personal Progressive Web App for tracking strength training and CrossFit-style workouts. I built this because I wanted a workout app that actually works offline, supports the timer protocols I use (AMRAP, EMOM, Tabata), and doesn't require a subscription.
Features
Block-Based Workouts
Compose workouts from different block types that you can mix and match:
- Strength Blocks — Track sets with weight (kg/lbs), reps, and RIR (Reps In Reserve)
- Timed Blocks — CrossFit-style interval protocols
Timer Modes
Full support for popular CrossFit timing protocols:
| Mode | Description |
|---|---|
| AMRAP | As Many Rounds As Possible — set a duration, track rounds completed |
| EMOM | Every Minute On the Minute — exercises reset each minute |
| Tabata | Classic 20s work / 10s rest intervals |
| For Time | Race to complete all reps, optional time cap |
Exercise Library
- 100+ pre-loaded exercises with icons and muscle group tags
- Create your own custom exercises
- Filter by muscle group (chest, back, legs, shoulders, arms, core)
- Full-text search
Workout Templates
Save your favorite workouts as templates to reuse later. No more rebuilding the same workout every week.
Offline-First PWA
- Works completely offline — no internet required during workouts
- Installable on your home screen (iOS, Android, desktop)
- All data stored locally in IndexedDB
- Export/import your data anytime
Customization
- Dark mode
- Multiple languages (i18n ready)
- Weight units (kg or lbs)
- Timer volume control
Tech Stack
- Vue 3.5+ with Composition API and
<script setup> - TypeScript with strict type checking
- Tailwind CSS 4 for styling
- shadcn-vue (reka-ui) component library
- Pinia for state management
- Dexie for IndexedDB persistence
- Vite 6 with PWA plugin
- Vitest with Playwright browser testing
Getting Started
Prerequisites
- Node.js ^20.19.0 or >=22.12.0
- pnpm 10.24.0+
Installation
pnpm install
Development
pnpm dev
Build
pnpm build
Scripts
| Command | Description |
|---|---|
pnpm dev |
Start dev server with HMR |
pnpm build |
Type-check and build for production |
pnpm test |
Run all tests (Playwright browser) |
pnpm test:headed |
Run tests with visible browser |
pnpm test:ui |
Run tests with Vitest UI |
pnpm test:coverage |
Run tests with coverage report |
pnpm lint |
Run oxlint + eslint with auto-fix |
pnpm type-check |
TypeScript type checking only |
pnpm knip |
Find unused exports/dependencies |
Architecture
This project follows Bulletproof feature-based architecture:
src/
├── features/ # Domain-specific modules
│ ├── exercises/ # Exercise library CRUD
│ ├── settings/ # App settings
│ ├── templates/ # Workout templates
│ ├── timers/ # Timer UI components
│ └── workout/ # Core workout execution
├── composables/ # Shared Vue composables
│ └── timers/ # Timer state machines
├── components/ # Shared UI components
│ └── ui/ # shadcn-vue primitives
├── db/ # Dexie database layer
│ └── repositories/ # Data access patterns
├── stores/ # Pinia stores
├── views/ # Route-level components
└── types/ # TypeScript types
Dependency Rules
- Views → Features → Shared (composables, components, stores, db)
- Features cannot import other features
- Shared code cannot import features
Contributing
Contributions are welcome! Whether it's bug reports, feature suggestions, or pull requests.
Reporting Bugs
Open an issue with:
- What you expected to happen
- What actually happened
- Steps to reproduce
- Browser/device info
Suggesting Features
Open an issue describing:
- The problem you're trying to solve
- Your proposed solution
- Any alternatives you considered
Pull Requests
- Fork the repo and create your branch from
main - Run
pnpm installto set up dependencies - Make your changes
- Ensure the tests covering your change pass:
pnpm exec vitest run --project=default src/__tests__/<area>(the full suite runs in CI on your PR — see Running Tests) - Ensure linting passes:
pnpm lint - Ensure types check:
pnpm type-check - Open a PR with a clear description of your changes
Code Style
- TypeScript strict mode — no
any, no type assertions - Vue 3.5+ APIs (reactive props destructure,
defineModel,useTemplateRef) - Composition API with
<script setup> - See
CLAUDE.mdfor detailed coding standards
Testing
This project uses Vitest with Playwright for browser-based testing. Tests run in a real browser environment, not jsdom.
Running Tests
# Run the tests covering what you changed — the everyday command.
# Filters match test paths under src/__tests__/, not source paths.
pnpm exec vitest run --project=default src/__tests__/features/workout
pnpm exec vitest run --project=default src/__tests__/browser/timer-audio.spec.ts
# Fast Node tier: pure logic, no DOM or IndexedDB (~1.5s)
pnpm test:unit
# Run with visible browser (great for debugging)
pnpm test:headed
# Run with Vitest UI (interactive test explorer)
pnpm test:ui
# Run with coverage
pnpm test:coverage
# Run the whole browser tier (~5 min) — CI does this for you on every PR
pnpm test
Prefer the scoped run locally. The full browser tier takes about five minutes on one machine, and CI shards it four ways and runs the a11y, visual, e2e, and coverage tiers alongside it on every pull request, so a local full run mostly buys a slower copy of a signal the PR gets anyway.
Test Structure
Specs are not colocated with source. They all live under src/__tests__/,
mirroring the source tree — so the test for src/features/workout/foo.ts lives
at src/__tests__/features/workout/foo.spec.ts, and a scoped run filters on the
test path, never the source path.
src/
└── __tests__/
├── features/ # Feature specs, mirroring src/features/
│ └── workout/
├── composables/ # Composable specs, mirroring src/composables/
│ └── timers/
├── components/ # Shared component specs
├── db/ # Repository and converter specs
├── stores/ # Shared store specs
├── integration/ # Cross-feature flows
├── browser/ # Browser-only behavior (wake lock, timer audio)
├── unit/ # `unit` project — pure Node, no DOM or IndexedDB
├── a11y/ # `a11y` project
├── visual/ # `visual` project
├── architecture/ # `arch` project
├── helpers/ # Test utilities (createTestApp, withSetup)
└── factories/ # Test data builders
The default project covers src/__tests__/**/*.spec.ts but excludes unit/,
a11y/, and visual/ — those have their own projects (pnpm test:unit,
test:a11y, test:visual), so scope a run with the project that owns the specs
you want.
Writing Tests
- Composables: Test directly or use
withSetup()helper for lifecycle hooks - Integration: Use
createTestApp()for full app testing with routing - Factories: Use builders like
workoutBuilder()anddbWorkoutBuilder()for test data
IDE Setup
- VS Code + Vue (Official)
- Vue.js devtools browser extension
License
MIT