Workout Domain
Block-Based Workout Model
Workouts are sequences of blocks using discriminated unions via kind:
ts1type WorkoutBlock = StrengthBlock | TimedBlock | CardioBlock 2 3type TimedBlock = AmrapBlock | EmomBlock | TabataBlock | ForTimeBlock
Strength Block
Traditional sets/reps exercises:
ts1type StrengthBlock = { 2 kind: 'strength' 3 id: number 4 exerciseName: string 5 sets: Array<Set> 6} 7 8type Set = { 9 id: number 10 weight?: string // User input as string 11 reps?: string 12 rir?: string // Reps in reserve 13 completed: boolean 14}
Timed Blocks
AMRAP, EMOM, Tabata, ForTime:
ts1type AmrapBlock = { 2 kind: 'amrap' 3 id: number 4 config: AmrapConfig 5 exercises: Array<BlockExercise> 6 result?: AmrapResult 7} 8 9type EmomBlock = { 10 kind: 'emom' 11 id: number 12 config: EmomConfig 13 exercises: Array<BlockExercise> 14 result?: EmomResult 15} 16 17type TabataBlock = { 18 kind: 'tabata' 19 id: number 20 config: TabataConfig 21 exercises: Array<BlockExercise> 22 result?: TabataResult 23} 24 25type ForTimeBlock = { 26 kind: 'fortime' 27 id: number 28 config: ForTimeConfig 29 exercises: Array<BlockExercise> 30 result?: ForTimeResult 31}
Cardio Block
ts1type CardioBlock = { 2 kind: 'cardio' 3 id: number 4 exerciseName: string 5 duration?: number 6 distance?: number 7 calories?: number 8}
Type Files
| File | Purpose |
|---|---|
src/types/blocks.ts | Runtime block types |
src/db/schema.ts | Persistence types with Db prefix |
Convention: Database types use Db prefix (e.g., DbStrengthBlock) and null instead of undefined.
Key Composables
Workout Feature
| Composable | Purpose |
|---|---|
useWorkout.ts | Singleton state, block/set CRUD operations |
useWorkoutPersistence.ts | Auto-save, complete, discard active workout |
useWorkoutExercise.ts | Exercise selection within workout |
useWorkoutBuilder.ts | Build workout from scratch or template |
Benchmark Feature
| Composable | Purpose |
|---|---|
useBenchmark.ts | Core benchmark state and operations |
useBenchmarkPersistence.ts | Save benchmark attempts |
useBenchmarkTimer.ts | Timer for timed benchmarks |
Template Feature
| Composable | Purpose |
|---|---|
useTemplateForm.ts | Create/edit template form state |
useTemplatePicker.ts | Select template to start workout |
Working with Blocks
Adding a Block
ts1import { useWorkout } from '@/features/workout/composables/useWorkout' 2 3const { addBlock } = useWorkout() 4 5// Add strength block 6addBlock({ 7 kind: 'strength', 8 exerciseName: 'Squat', 9 sets: [] 10}) 11 12// Add AMRAP block 13addBlock({ 14 kind: 'amrap', 15 config: { duration: 10 }, 16 exercises: [] 17})
Type-Safe Block Handling
Use exhaustive switch for block kinds:
ts1function getBlockTitle(block: WorkoutBlock): string { 2 switch (block.kind) { 3 case 'strength': 4 return block.exerciseName 5 case 'amrap': 6 return `AMRAP ${block.config.duration}min` 7 case 'emom': 8 return `EMOM ${block.config.rounds}x${block.config.interval}s` 9 case 'tabata': 10 return 'Tabata' 11 case 'fortime': 12 return 'For Time' 13 case 'cardio': 14 return block.exerciseName 15 default: 16 // TypeScript exhaustiveness check 17 const _exhaustive: never = block 18 return _exhaustive 19 } 20}
Quick Find
bash1rg -n "kind: '(strength|amrap|emom|tabata|fortime|cardio)'" src/ # Block usage 2rg -n "type.*Block = " src/types/blocks.ts # Block type definitions 3rg -n "export function use" src/features/workout/composables # Workout composables