Workout Feature Guide
Definitive guide to the Workout feature — exercise library, workout day scheduling, set/rep tracking, muscle groups, and analytics across all platforms.
Introduction
The Workout Feature is a comprehensive exercise tracking and workout management system that enables users to:
- Create and manage personal exercise libraries
- Schedule exercises across weekly workout days (Monday-Sunday)
- Track workouts at three levels: workout completion, exercise completion, and individual set logging
- Monitor progress with historical tracking and weekly summaries
- Organize exercises by muscle groups
- Define target sets, reps, and rest periods for each exercise
This guide establishes the standards, patterns, and business rules that govern all workout feature implementations across desktop (web/desktop apps) and mobile platforms.
Business Logic & Rules
2.1 Core Concepts
Exercise
An exercise is a fitness activity definition containing:
- Basic Info: Name, description
- Performance Targets: Base sets (target # of sets), base reps (target reps per set), rest seconds
- Categorization: Muscle group (FULL_BODY, CORE, LEGS, CHEST, BACK, SHOULDERS, ARMS, GLUTES, CARDIO)
- Optional Resources: Instructional videos, images, or documents
Workout Day
A workout day represents a scheduled training session:
- Day of Week: Integer 1-7 (Monday-Sunday)
- Day Name: Display name (e.g., “Monday”, “Leg Day”)
- Time: Preferred workout time (e.g., “06:00”, “18:30”). When stored as
"00:00", the notification dispatcher falls back toUserSettings.defaultWorkoutTime. - Exercises: Collection of exercises assigned to this day
- Completion Status: Whether the entire workout was completed on a given date (
isCompleted— used by the notification dispatcher to suppress “workout starting” reminders once done)
A workout day with a non-stub
idtriggers a “workout starting soon” push when its start falls withinUserSettings.defaultWorkoutReminderOffsetMinutes(default 30,0to disable). See Default reminder offsets.
Exercise Binding
The association between exercises and workout days:
- One exercise can be assigned to multiple days (e.g., Push-ups on Mon/Wed/Fri)
- One day can have multiple exercises (e.g., Monday: Bench Press, Squats, Pull-ups)
- Managed via junction table (ExercisesWithWorkoutDays)
Three-Level Tracking System
Level 1: Workout Tracking
- Tracks completion of an entire workout day
- Creates WorkoutTrack record
- Used for: “I completed my Monday workout”
Level 2: Exercise Tracking
- Tracks completion of a specific exercise within a workout
- Creates ExerciseTrack record
- Used for: “I completed Push-ups during Monday’s workout”
Level 3: Set Tracking (Primary)
- Tracks individual sets within an exercise
- Creates ExerciseSetTrack records
- Used for: “I did 3 sets of Push-ups: 10 reps, 12 reps, 8 reps”
- Most granular and important tracking level
Exercise Day Status
A composite view showing exercise progress:
exerciseTrackId: Unique identifier for the exercise sessionexerciseId: Which exercise was performedsetsCountDone: Number of sets completedtotalRepsDone: Sum of all reps across setsexerciseDone: Whether exercise marked completesetsDone: Array of individual set data (id, reps, doneDateTime)
2.2 Business Rules
Exercise Management
E-001: Exercise Creation
- Name is required (non-empty string)
- Description is optional
- Base sets, base reps, rest seconds are optional but must be positive integers if provided
- Muscle group must match enum values or be null
- User can only create exercises for themselves
- Each exercise is owned by a single user
E-002: Exercise Updates
- Users can only update their own exercises
- All fields can be modified independently
- Changing exercise parameters does not affect historical tracking data
- Update does not affect day assignments (managed separately)
E-003: Exercise Deletion
- Soft delete: Exercise marked as DELETED, not physically removed
- Existing day bindings remain in database but filtered from queries
- Historical tracking data (ExerciseTracks, ExerciseSetTracks) preserved
- Deleted exercises not shown in exercise library or day views
E-004: Exercise Ownership
- Exercises are user-scoped; users cannot see other users’ exercises
- All queries must filter by user_id
- Exercise IDs are globally unique (UUID) but always queried with user context
Workout Day Management
W-001: Workout Day Creation
- Seven workout days (1-7 for Monday-Sunday) per user
- Auto-created when first exercise is bound to a day
- Day name defaults to weekday name but can be customized
- Time defaults to null but can be set by user
- Each user has their own set of 7 workout days
W-002: Workout Day Updates
- Users can update day name, time, and exercise list
- Updating exercise list replaces all bindings for that day
- Update operation is transactional (all or nothing)
- Cannot change day number (1-7) once created
W-003: Workout Day Completion Status
- Completion status is date-specific (can complete Monday on 2025-12-29 and again on 2026-01-05)
- Determined by WorkoutTrack records for that day + date
- Completion does not prevent further exercise/set logging
- User can have multiple completions for same day in different weeks
Exercise-Day Binding
B-001: Binding Exercises to Days
- Exercise can be bound to 0-7 days
- Creates ExercisesWithWorkoutDays junction record
- If WorkoutDay doesn’t exist, auto-create it with day number
- Duplicate bindings prevented (same exercise + same day)
B-002: Unbinding Exercises from Days
- Removes ExercisesWithWorkoutDays junction record (hard delete)
- Does not delete WorkoutDay (day can exist without exercises)
- Does not affect historical tracking data
- User can rebind same exercise later
B-003: Binding Constraints
- Cannot bind exercise owned by different user
- Day must be 1-7 (Monday-Sunday)
- One exercise-day pair can only be bound once
Set Tracking (Core Workflow)
ST-001: Adding a Set
- User specifies reps for the set
- System finds or creates ExerciseTrack for (exercise + workout day + today’s date)
- Creates ExerciseSetTrack within that ExerciseTrack
- Records: reps, doneDateTime
- Returns set ID to client for future updates/deletes
ST-002: Set Editing
- CRITICAL RULE: Only newly created sets are editable
- When user adds a set, backend returns the set data
- UI enables editing ONLY for that returned set
- Previously logged sets are read-only (displayed but not editable)
- This prevents accidental modification of historical data
ST-003: Set Deletion
- User can delete any set they’ve logged
- Soft delete: ExerciseSetTrack status set to DELETED
- If all sets deleted from ExerciseTrack, the track remains (exercise attempted but no sets completed)
- UI removes set from display
ST-004: Exceeding Base Sets
- If user adds more sets than baseSets, show warning dialog
- Warning: “You’re adding set X, but target is Y sets. Continue?”
- User can choose to proceed or cancel
- No hard limit on set count
ST-005: Set Data Integrity
- Reps must be positive integer
- DoneDateTime auto-set to current timestamp
- Each set is immutable once created (except via delete/re-add)
- Sets are linked to ExerciseTrack, not directly to Exercise
Exercise Completion Tracking
EC-001: Completing an Exercise
- User marks entire exercise as complete
- Creates or updates ExerciseTrack with doneDateTime
- Does not require any sets to be logged (user can mark complete without set data)
- Typically used when user completes exercise but doesn’t log individual sets
EC-002: Exercise Completion vs Set Tracking
- Set tracking is independent of exercise completion
- User can log sets without marking exercise complete
- User can mark exercise complete without logging sets
- Best practice: Use set tracking as primary method, exercise completion as fallback
EC-003: Undoing Exercise Completion
- Removes ExerciseTrack record (soft delete)
- Does not affect ExerciseSetTracks (sets remain if logged)
- Exercise returns to “not completed” state
Workout Completion Tracking
WC-001: Completing a Workout
- User marks entire workout day as complete
- Creates WorkoutTrack record with workoutDayId + doneDateTime
- Independent of exercise/set tracking (can complete workout without logging exercises)
- Used for high-level tracking: “I did my Monday workout”
WC-002: Workout Completion Independence
- Workout completion does not auto-mark exercises as complete
- Exercise/set tracking does not auto-mark workout as complete
- User controls each level independently
WC-003: Undoing Workout Completion
- Removes WorkoutTrack record (soft delete)
- Does not affect exercise or set tracks
- Workout returns to “not completed” state
WC-004: Weekly Summary
- System provides count of workouts completed per day (0-6 representing Sun-Sat or Mon-Sun)
- Counts WorkoutTrack records within current week
- Used for weekly progress visualization
Filtering & Search
F-001: Exercise Text Search
- Search by exercise name (case-insensitive, partial match)
- Uses SQL LIKE with wildcards:
%search_term% - Search applies across all user’s exercises regardless of day assignment
F-002: Day Filtering
- Filter exercises by assigned day (1-7)
- Returns only exercises bound to specified day
- Requires JOIN with ExercisesWithWorkoutDays and WorkoutDays
F-003: Muscle Group Filtering
- Filter by muscle group enum
- If multiple groups selected, uses OR logic
- If no filter, show all exercises
F-004: Date-Time Context Filtering
- When fetching exercises for a day, optionally provide dateTime
- Returns exercises with completion status for that specific date
- Enables showing “completed today” vs “completed yesterday”
Pagination
PG-001: Pagination Parameters
limit: Number of results per page (default: 50, max: 200)offset: Page number (0-indexed)- Actual SQL offset =
offset * limit - Not all endpoints support pagination (small result sets return all)
PG-002: Pagination Behavior
- Filtering applied before pagination
- Results sorted by name (default) before pagination
- Empty array returned if offset beyond available data
2.3 Validation Rules
V-001: Exercise Name
- Required, non-empty
- Max length: 255 characters
- No special character restrictions
V-002: Exercise Numeric Fields
- Base sets, base reps, rest seconds: all optional
- Must be positive integers if provided (> 0)
- No upper limit enforced (use reasonable UI constraints)
V-003: Muscle Group
- Must match enum: FULL_BODY, CORE, LEGS, CHEST, BACK, SHOULDERS, ARMS, GLUTES, CARDIO
- Case-sensitive
- If invalid value provided, throw validation error (don’t auto-convert to null)
- Optional field (null allowed)
V-004: Workout Day Number
- Must be integer 1-7
- 1 = Monday, 7 = Sunday
- Required field
- Out-of-range values rejected
V-005: Workout Day Time
- Format: HH:mm (24-hour)
- Examples: “06:00”, “18:30”, “23:45”
- Optional field
- Invalid format rejected
V-006: Set Reps
- Must be positive integer (> 0)
- Max recommended: 999 (UI constraint)
- Required when adding set
V-007: DateTime Fields
- Format: ISO 8601 with time:
YYYY-MM-DDTHH:mm:ss - Examples: “2025-12-29T09:30:00”
- Required for tracking operations
- Auto-generated by client if not provided
2.4 Error Handling
ERR-001: Exercise Not Found
- HTTP 404
- Message: “Exercise not found”
- Occurs when: user requests exercise that doesn’t exist or belongs to another user
ERR-002: Workout Day Not Found
- HTTP 404
- Message: “Workout day not found”
- Occurs when: requesting day that doesn’t exist for user
ERR-003: Unauthorized Access
- HTTP 403
- Message: “Unauthorized access to exercise/workout”
- Occurs when: user tries to modify/delete another user’s data
ERR-004: Validation Failure
- HTTP 400
- Message: Specific validation error (e.g., “Exercise name is required”, “Invalid muscle group”)
- Return first validation error encountered
ERR-005: Duplicate Binding
- HTTP 409 (Conflict)
- Message: “Exercise already bound to this day”
- Occurs when: trying to bind exercise to day it’s already assigned to
ERR-006: Server Error
- HTTP 500
- Message: “Internal server error”
- Log full stack trace server-side
- Don’t expose internal details to client
Desktop Design Standards
3.1 Platform Contexts
Desktop includes:
- Desktop Application (Kotlin Compose Multiplatform - Desktop target)
- Web Application (Kotlin Compose Multiplatform - JS/WASM target)
Desktop design prioritizes:
- Larger screen real estate for workout planning
- Multi-day view for weekly scheduling
- Detailed set logging with inline editing
- Keyboard shortcuts for quick logging
- Visual progress indicators
3.2 Layout Standards
Workout Dashboard View
Layout Structure:
+--------------------------------------------------+
| Header: "Workout Dashboard" |
| Weekly Summary: 4/7 workouts completed |
+--------------------------------------------------+
| Day Selector (Chip Row) |
| [Mon] [Tue] [Wed] [Thu] [Fri] [Sat] [Sun] [All]|
+--------------------------------------------------+
| Selected Day: Monday |
| Time: 06:00 AM [Complete Workout] |
+--------------------------------------------------+
| Exercises (3) |
| +--------------------------------------------+ |
| | Exercise Card: Bench Press | |
| | 3 sets × 10 reps • 90s rest | |
| | Target muscle: CHEST | |
| | [View Details] [Log Sets] | |
| +--------------------------------------------+ |
| | Exercise Card: Squats | |
| | ... | |
| +--------------------------------------------+ |
+--------------------------------------------------+
| [+ Add Exercise to Monday] |
+--------------------------------------------------+Key Elements:
- Day selector chips at top (active day highlighted)
- “All” mode shows entire exercise library
- Workout completion button (disabled if already completed)
- Exercise cards expand to show set logging
- Add exercise button to bind new exercises to selected day
Exercise Card (Expanded)
Layout Structure:
+--------------------------------------------------+
| Bench Press [Edit] [X] |
| 3 sets × 10 reps • 90 seconds rest |
| Target: CHEST |
| Assigned to: Mon, Wed, Fri [Manage Schedule] |
+--------------------------------------------------+
| Sets Logged Today: |
| +--------------------------------------------+ |
| | Set 1: 12 reps [Edit] [Delete] | |
| | Set 2: 10 reps [Edit] [Delete] | |
| | Set 3: 8 reps [Edit] [Delete] | |
| +--------------------------------------------+ |
| [+ Add Set] Reps: [____] [Log] |
| Warning: Target is 3 sets, you're adding set 4 |
+--------------------------------------------------+
| [Mark Exercise Complete] [View History] |
+--------------------------------------------------+States:
- Collapsed: Shows summary only (name, targets, muscle group)
- Expanded: Shows set logging area
- Completed: Green checkmark overlay, disabled logging
- In Progress: Some sets logged, can add more
Interactive Elements:
- Click card header to expand/collapse
- Edit button opens exercise editor
- Delete (X) unbinds from current day (not in “All Exercises” mode)
- Manage Schedule opens day binding dialog
- Add Set: text input for reps + Log button
- Each logged set has Edit/Delete actions
All Exercises View
Layout: Grid or List
Grid Mode (Recommended):
+--------------------------------------------------+
| All Exercises [+ New] |
| [Search: ___________] [Filter] [Sort] |
+--------------------------------------------------+
| [Push-ups] [Bench Press] [Squats] |
| Chest/Arms Chest Legs |
| M W F M W M Th |
| |
| [Pull-ups] [Deadlifts] [Curls] |
| Back Legs/Back Arms |
| M W F W F T Th S |
+--------------------------------------------------+List Mode:
- Each row: Name | Muscle Group | Days | Actions
- Compact for large exercise libraries
- Sortable columns
Features:
- Search bar for name filtering
- Filter by muscle group
- Sort by name, muscle group, # of days assigned
- Each exercise card shows day badges (clickable to unbind)
- New Exercise button prominent
Exercise Form (Create/Edit)
Form Structure:
+--------------------------------------------------+
| New Exercise [Close] |
+--------------------------------------------------+
| Name: [________________________] |
| |
| Description: |
| [_____________________________________________] |
| [_____________________________________________] |
| |
| Performance Targets: |
| Base Sets: [___] (e.g., 3) |
| Base Reps: [___] (e.g., 10) |
| Rest (sec): [___] (e.g., 90) |
| |
| Muscle Group: [Dropdown ▼] |
| └─ Options: Full Body, Core, Legs, Chest... |
| |
| Schedule: |
| [☐ Mon] [☐ Tue] [☐ Wed] [☐ Thu] |
| [☐ Fri] [☐ Sat] [☐ Sun] |
| |
| [Cancel] [Save] |
+--------------------------------------------------+Validation:
- Name required (highlighted if empty)
- Numeric fields must be positive integers or empty
- Muscle group validated against enum
- Schedule optional (can create unscheduled exercise)
Edit Mode Differences:
- Title: “Edit Exercise”
- Pre-populate all fields
- Save button text: “Update”
- Delete button in footer (danger zone)
Workout History View
Layout: Timeline or Calendar
Timeline View:
+--------------------------------------------------+
| Workout History |
| [Date Range: Dec 1 - Dec 29] [This Week] |
+--------------------------------------------------+
| Monday, Dec 29, 2025 |
| ✓ Workout Completed (06:15 AM) |
| └─ Bench Press: 3 sets (12, 10, 8 reps) |
| └─ Squats: 4 sets (15, 12, 10, 8 reps) |
| |
| Monday, Dec 22, 2025 |
| ✓ Workout Completed (06:00 AM) |
| └─ Bench Press: 3 sets (10, 10, 9 reps) |
| └─ Squats: 3 sets (12, 12, 10 reps) |
| |
| Friday, Dec 19, 2025 |
| ✗ Workout Not Completed |
| └─ Deadlifts: 2 sets (8, 6 reps) [incomplete] |
+--------------------------------------------------+Calendar View (Alternative):
- Month calendar with workout completion indicators
- Click date to see details
- Color coding: Green = completed, Yellow = partial, Gray = missed
3.3 Interactive Components
Day Selector Chips
Visual Design:
[Mon] [Tue] [Wed] [Thu] [Fri] [Sat] [Sun] [All Exercises]States:
- Default: Gray background, dark text
- Selected: Primary color background, white text
- Completed Today: Green border/badge overlay
- Has Exercises: Bold text or dot indicator
Behavior:
- Click to switch day
- Keyboard: Arrow keys to navigate, Enter to select
- “All Exercises” special mode (shows library, not day-specific)
Set Logging Input
Inline Form:
Add Set: Reps [____] [Log Set]After Logging Set:
Set 3: 12 reps ✓ Logged [Edit Reps] [Delete]
↑ Editable for ~5 seconds after creationBehavior:
- Enter reps, click “Log Set” or press Enter
- Backend creates set, returns set data
- UI makes that set editable (reps can be changed)
- After blur or timeout, set becomes read-only
- Other sets always read-only
Warning for Exceeding Target:
⚠️ You're adding set 4, but target is 3 sets.
[Add Anyway] [Cancel]Manage Schedule Dialog
Triggered by: “Manage Schedule” button on exercise card in All Exercises mode
Layout:
+----------------------------------+
| Schedule: Bench Press [Close]|
+----------------------------------+
| Select days for this exercise: |
| |
| [✓ Mon] [✓ Wed] [✓ Fri] |
| [ Tue] [ Thu] [ Sat] |
| [ Sun] |
| |
| Current: Mon, Wed, Fri |
| |
| [Cancel] [Save] |
+----------------------------------+Behavior:
- Pre-select days where exercise is currently bound
- Toggle days on/off
- On Save:
- Calculate toBind = selected - current
- Calculate toUnbind = current - selected
- Execute bind/unbind operations
- Refresh exercise card to show updated day badges
3.4 View Modes
Desktop should support multiple view modes:
Mode 1: Daily Workout View
- Purpose: Focus on today’s or specific day’s workout
- Layout: Single day’s exercises with full set logging
- Day Selector: Prominent at top
- Use Case: During workout, logging sets in real-time
Mode 2: Weekly Planning View
- Purpose: Plan entire week at a glance
- Layout: 7 columns (one per day), each showing exercise names
- Interaction: Drag-and-drop to assign exercises (enhancement)
- Use Case: Planning upcoming week’s workouts
Mode 3: Exercise Library View
- Purpose: Manage all exercises
- Layout: Grid or list of all exercises
- Day Context: None (shows all exercises regardless of assignment)
- Use Case: Creating, editing, organizing exercise collection
Mode 4: History View
- Purpose: Review past workouts
- Layout: Timeline or calendar
- Date Range: Selectable (week, month, all time)
- Use Case: Tracking progress, identifying patterns
View Mode Switcher:
- Tab bar or navigation sidebar
- Persist selected mode in user preferences
- Each mode has context-appropriate actions
3.5 Keyboard Navigation
Desktop must support efficient keyboard navigation:
Global Shortcuts:
Ctrl/Cmd + N: New ExerciseCtrl/Cmd + F: Focus search inputCtrl/Cmd + 1-7: Switch to day 1-7 (Mon-Sun)Ctrl/Cmd + 0: Switch to All Exercises viewESC: Close modals/dialogs
Exercise Card Navigation:
Tab: Navigate between exercise cardsEnter: Expand/collapse focused cardSpace: Toggle exercise completeDelete: Unbind from current day (with confirmation)
Set Logging:
- Focus on reps input field
Enter: Log setTab: Navigate to Edit/Delete buttons
Form Navigation:
Tab: Move to next fieldShift + Tab: Move to previous fieldEnter: Submit form (if not in textarea)ESC: Cancel/close form
3.6 Responsive Breakpoints (for Web Desktop)
- Large Desktop (>= 1440px): 3-column exercise grid, full sidebar
- Desktop (1024px - 1439px): 2-column exercise grid, full sidebar
- Tablet (768px - 1023px): 1-column exercise grid, collapsible sidebar
- Mobile (<768px): Use mobile design standardsMobile Design Standards
4.1 Platform Context
Mobile includes:
- Android Application (Kotlin Android)
- iOS Application (Kotlin Multiplatform - iOS target, if implemented)
Mobile design prioritizes:
- Touch-first interaction
- Quick workout logging during exercise
- Swipe gestures for common actions
- Minimal taps to log sets
- Workout timer integration
- Rest timer between sets
4.2 Layout Standards
Workout View (Mobile)
Layout: Vertical List
+----------------------------------+
| Monday Workout 06:00 AM |
| [< Back] [...] |
+----------------------------------+
| Progress: 2/5 exercises done |
| [■■■■□] 80% |
+----------------------------------+
| Exercise Card |
| +----------------------------+ |
| | Bench Press [Edit] | |
| | 3 × 10 reps • 90s rest | |
| | ✓ Set 1: 12 reps | |
| | ✓ Set 2: 10 reps | |
| | Set 3: [__] reps [Log] | |
| | [✓ Mark Complete] | |
| +----------------------------+ |
| |
| Exercise Card |
| +----------------------------+ |
| | Squats [Edit] | |
| | 4 × 12 reps • 60s rest | |
| | [+ Start Logging Sets] | |
| +----------------------------+ |
+----------------------------------+
| [✓ Complete Workout] |
+----------------------------------+Key Characteristics:
- Day name and time in header
- Progress indicator at top
- Exercise cards stacked vertically
- Inline set logging (no modal)
- Floating action button for complete workout
Exercise Card (Mobile - Collapsed)
+----------------------------------+
| Bench Press [Edit] |
| CHEST • 3×10 • 90s rest |
| Days: M W F |
| [Start Logging] |
+----------------------------------+Exercise Card (Mobile - Expanded/Logging)
+----------------------------------+
| Bench Press [Edit] |
| CHEST • 3×10 • 90s rest |
+----------------------------------+
| Sets: |
| ✓ Set 1: 12 reps [Timer: 90s]|
| ✓ Set 2: 10 reps [Timer: 90s]|
| > Set 3: [12] reps [LOG SET] |
| |
| [+ Add Another Set] |
| [✓ Complete Exercise] |
+----------------------------------+Interactive Elements:
- Tap card to expand
- Reps input with number keyboard
- “LOG SET” button triggers timer and adds set
- Timer countdown overlay during rest period
- Swipe left on logged set to delete
All Exercises View (Mobile)
Layout: Vertical List with Search
+----------------------------------+
| Exercises [+ New] |
| [Search: ___________] |
| [Filter ▼] [Sort ▼] |
+----------------------------------+
| Bench Press |
| Chest • M W F |
| 3×10 • 90s rest |
| ────────────────────────────── |
| Squats |
| Legs • M Th |
| 4×12 • 60s rest |
| ────────────────────────────── |
| Pull-ups |
| Back/Arms • M W F |
| 3×8 • 120s rest |
+----------------------------------+Swipe Actions:
- Swipe right: Quick edit
- Swipe left: Delete/unbind options
- Long press: Multi-select mode
Exercise Form (Mobile)
Layout: Scrollable Full-Screen
+----------------------------------+
| New Exercise [X] Close |
+----------------------------------+
| Name |
| [________________________] |
| |
| Description |
| [________________________] |
| [________________________] |
| |
| Base Sets [3] |
| Base Reps [10] |
| Rest (sec) [90] |
| |
| Muscle Group |
| [Chest ▼] |
| |
| Assign to Days |
| [✓M] [✓W] [✓F] [ T] [ T] [ S] [ S]|
| |
| [Save Exercise] |
+----------------------------------+Behavior:
- Native mobile keyboard types (number, text)
- Day toggles as large touch targets
- Save button sticky at bottom
- Validation inline with red underlines
Workout History (Mobile)
Layout: Timeline List
+----------------------------------+
| History [Filter] |
| [This Week ▼] |
+----------------------------------+
| Monday, Dec 29 |
| ✓ Workout Completed |
| +----------------------------+ |
| | Bench Press: 3 sets | |
| | Squats: 4 sets | |
| | Pull-ups: 3 sets | |
| +----------------------------+ |
| |
| Monday, Dec 22 |
| ✗ Incomplete |
| +----------------------------+ |
| | Bench Press: 2 sets | |
| +----------------------------+ |
+----------------------------------+Tap to Expand:
+----------------------------------+
| Monday, Dec 29 06:15 AM |
| ✓ Workout Completed |
+----------------------------------+
| Bench Press |
| • Set 1: 12 reps |
| • Set 2: 10 reps |
| • Set 3: 8 reps |
| |
| Squats |
| • Set 1: 15 reps |
| • Set 2: 12 reps |
| • Set 3: 10 reps |
| • Set 4: 8 reps |
+----------------------------------+4.3 Interactive Components (Mobile)
Rest Timer Overlay
Triggered by: Logging a set
Layout:
+----------------------------------+
| |
| |
| Rest Timer |
| |
| 01:30 |
| [■■■■■□□□□□] |
| |
| [Skip Rest] [Add 30s] |
| |
+----------------------------------+Behavior:
- Semi-transparent overlay over workout view
- Countdown from exercise’s rest seconds
- Vibrate/sound when timer completes
- Tap anywhere to dismiss early
- Swipe down to minimize to notification
Filter Bottom Sheet
Triggered by: Filter button in All Exercises view
Layout:
+----------------------------------+
| Filter Exercises [Close]|
+----------------------------------+
| Muscle Groups |
| [Chest] [Back] [Legs] [Arms] |
| [Shoulders] [Core] [Full Body] |
| (Toggle buttons) |
+----------------------------------+
| Assigned Days |
| [M] [T] [W] [T] [F] [S] [S] |
| (Toggle buttons) |
+----------------------------------+
| [Clear Filters] [Apply] |
+----------------------------------+Behavior:
- Modal bottom sheet (slides up)
- Multi-select muscle groups (OR logic)
- Multi-select days
- Apply button triggers filter
- Clear resets all
Quick Actions Bottom Sheet
Triggered by: Long-press on exercise card
Layout:
+----------------------------------+
| Bench Press |
+----------------------------------+
| [Edit Exercise] |
| [Manage Schedule] |
| [View History] |
| [Delete Exercise] |
| [Cancel] |
+----------------------------------+4.4 Gestures
Swipe Actions on Exercise Cards:
- Swipe Right: Quick edit
- Swipe Left: Delete/unbind options
- Long Press: Show quick actions bottom sheet
Swipe Actions on Logged Sets:
- Swipe Left: Delete set
Pull to Refresh:
- Pull down on workout view to refresh data
Swipe to Dismiss:
- Bottom sheets dismissible by swiping down
4.5 Mobile-Specific Patterns
Tab Navigation:
+----------------------------------+
| [Workout] [Exercises] [History] |
+----------------------------------+
| |
| (Content for selected tab) |
| |
+----------------------------------+Floating Action Button:
- Context: Workout View → “Complete Workout”
- Context: Exercises View → “New Exercise”
- Context: History View → “Today’s Workout”
Contextual Actions:
- Three-dot menu for secondary actions
- Bottom sheets for action lists
- Native share sheet for workout summaries
Workout Mode:
- Immersive full-screen during active workout
- Screen stays awake
- Volume buttons for quick set logging (optional)
- Voice commands for hands-free logging (enhancement)
Architecture Patterns
5.1 Layer Architecture
All platforms follow Clean Architecture principles:
+--------------------------------------------------+
| PRESENTATION LAYER |
| - Screens/Views (Composables) |
| - ViewModels (State management) |
| - State (UI state models) |
+--------------------------------------------------+
↕
+--------------------------------------------------+
| DOMAIN LAYER |
| - Use Cases (Business logic) |
| - Repository Interfaces |
| - Domain Models |
+--------------------------------------------------+
↕
+--------------------------------------------------+
| DATA LAYER |
| - Repository Implementations |
| - Data Sources (Remote/Local) |
| - API Clients |
| - DTOs (Data Transfer Objects) |
| - Mappers (DTO ↔ Domain) |
+--------------------------------------------------+5.2 State Management Pattern
Current Pattern (Desktop):
// State
data class WorkoutState(
val exercises: List<Exercise> = emptyList(),
val workoutDays: List<WorkoutDay> = emptyList(),
val completedExercises: Set<ExerciseDayStatus> = emptySet(),
val exerciseDayMap: Map<String, Set<Int>> = emptyMap(),
val weeklyWorkoutsCompleted: List<Int> = emptyList(),
val daySelected: Int = 0,
val allExerciseMode: Boolean = false,
val isCompleted: Boolean = false,
val isLoading: Boolean = false,
val isError: Boolean = false,
val errorMessage: String = ""
)
// ViewModel
class WorkoutViewModel : ViewModel() {
private val _state = MutableStateFlow(WorkoutState())
val state: StateFlow<WorkoutState> = _state.asStateFlow()
fun getExercisesByDay(day: Int) {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
val exercises = workoutService.getExercisesByDay(day, dateTime)
_state.update {
it.copy(
exercises = exercises,
daySelected = day,
isLoading = false
)
}
} catch (e: Exception) {
_state.update {
it.copy(
isLoading = false,
isError = true,
errorMessage = e.message ?: "Unknown error"
)
}
}
}
}
}Recommended Pattern (MVI - for consistency with other features):
// State
data class WorkoutState(
val exercises: List<Exercise> = emptyList(),
val workoutDays: List<WorkoutDay> = emptyList(),
val completedExercises: Set<ExerciseDayStatus> = emptySet(),
val exerciseDayMap: Map<String, Set<Int>> = emptyMap(),
val daySelected: Int = 0,
val isLoading: Boolean = false
)
// Intent
sealed class WorkoutIntent {
data class GetExercisesByDay(val day: Int) : WorkoutIntent()
data class AddExercise(val exercise: Exercise) : WorkoutIntent()
data class UpdateExercise(val exercise: Exercise) : WorkoutIntent()
data class DeleteExercise(val id: String) : WorkoutIntent()
data class AddSet(val exerciseId: String, val workoutDayId: String, val reps: Int) : WorkoutIntent()
data class CompleteExercise(val exerciseId: String, val day: Int, val workoutDayId: String) : WorkoutIntent()
data class CompleteWorkout(val dayId: Int) : WorkoutIntent()
data class BindExerciseToDay(val exerciseId: String, val day: Int) : WorkoutIntent()
// ... more intents
}
// Effect
sealed class WorkoutEffect {
data class ShowSnackBar(val message: String, val type: SnackBarType) : WorkoutEffect()
data class NavigateToExerciseDetail(val exerciseId: String) : WorkoutEffect()
data class StartRestTimer(val seconds: Int) : WorkoutEffect()
// ... more effects
}
// ViewModel
class WorkoutViewModel : BaseViewModel<WorkoutState, WorkoutIntent, WorkoutEffect>() {
override fun handleIntent(intent: WorkoutIntent) {
when (intent) {
is WorkoutIntent.GetExercisesByDay -> getExercisesByDay(intent.day)
is WorkoutIntent.AddSet -> addSet(intent.exerciseId, intent.workoutDayId, intent.reps)
// ... handle other intents
}
}
private fun addSet(exerciseId: String, workoutDayId: String, reps: Int) {
viewModelScope.launch {
workoutUseCases.addExerciseSet(exerciseId, workoutDayId, reps)
.onSuccess { exerciseSet ->
// Refresh exercises for current day
getExercisesByDay(currentState.daySelected)
emitEffect(WorkoutEffect.ShowSnackBar(
message = "Set logged: $reps reps",
type = SnackBarType.SUCCESS
))
emitEffect(WorkoutEffect.StartRestTimer(
seconds = getRestSecondsForExercise(exerciseId)
))
}
.onFailure { error ->
emitEffect(WorkoutEffect.ShowSnackBar(
message = "Failed to log set",
type = SnackBarType.ERROR
))
}
}
}
}5.3 Data Flow Pattern
Read Flow (Get Exercises for Day):
User Action (Select Monday)
↓
Intent: GetExercisesByDay(day = 1)
↓
ViewModel: handleIntent
↓
UseCase: getExercisesByWorkoutDay.invoke(day = 1, dateTime = now)
↓
Repository: getExercisesByDay(1, now)
↓
Remote DataSource: api.getExercisesByDay(1, now)
↓
Server API: GET /workout/byDay/1?dateTime=2025-12-29T09:00:00
↓
Server: Query WorkoutDays → ExercisesWithWorkoutDays → Exercises
Query ExerciseTracks/ExerciseSetTracks for completion status
Map entities to DTOs with completion data
↓
Response: List<ExerciseDTO> (with completion status)
↓
DataSource: receives response
↓
Repository: maps DTOs to domain models
↓
UseCase: returns Result<List<Exercise>>
↓
ViewModel: updates state with exercises
↓
View: recomposes with exercises + completion statusWrite Flow (Add Set):
User Action (Logs "12 reps" for Bench Press)
↓
Intent: AddSet(exerciseId, workoutDayId, reps = 12)
↓
ViewModel: handleIntent
↓
UseCase: addExerciseSet.invoke(exerciseId, workoutDayId, 12)
↓
Repository: addExerciseSet(exerciseId, workoutDayId, 12)
↓
Remote DataSource: api.completeExerciseSet(CreateExerciseSetTrackDTO)
↓
Server API: POST /workout/exercise-tracking/sets/complete
Body: { exerciseId, workoutDayId, reps: 12, doneDateTime: "2025-12-29T09:30:00" }
↓
Server:
1. Find or create ExerciseTrack (exercise + day + date)
2. Create ExerciseSetTrack (reps, doneDateTime)
3. Link to ExerciseTrack
4. Return success
↓
Response: 201 Created
↓
Repository: returns Result<Unit>
↓
ViewModel: emits success effect, refreshes exercises
↓
Effect: ShowSnackBar("Set logged: 12 reps")
StartRestTimer(90) // Use exercise's rest seconds
↓
View: shows success message, displays rest timer, updates exercise cardBinding Flow (Assign Exercise to Day):
User Action (Binds Push-ups to Monday)
↓
Intent: BindExerciseToDay(exerciseId = "pushups-id", day = 1)
↓
ViewModel: handleIntent
↓
UseCase: linkExerciseWithWorkoutDay.invoke(exerciseId, day)
↓
Repository: bindExerciseToDay(exerciseId, day)
↓
Remote DataSource: api.bindExerciseToDay(exerciseId, workoutDayId = 1)
↓
Server API: POST /workout/exercises/bind
Body: { exerciseId: "pushups-id", workoutDayDay: 1 }
↓
Server:
1. Get or create WorkoutDay for day 1
2. Find Exercise
3. Check for duplicate binding
4. Insert ExercisesWithWorkoutDays record
5. Return success
↓
Response: 200 OK
↓
Repository: returns Result<Unit>
↓
ViewModel: emits success effect, refreshes exercise-day map
↓
Effect: ShowSnackBar("Push-ups added to Monday")
↓
View: updates day badges on exercise card5.4 Error Handling Pattern
Layer-by-Layer Error Handling:
Data Layer:
suspend fun getExercisesByDay(day: Int, dateTime: String): Result<List<Exercise>> {
return try {
val response = api.getExercisesByDay(day, dateTime)
Result.success(response.map { it.toDomain() })
} catch (e: Exception) {
Result.failure(NetworkException("Failed to fetch exercises", e))
}
}Domain Layer:
operator fun invoke(day: Int, dateTime: String): Result<List<Exercise>> {
return repository.getExercisesByDay(day, dateTime)
.map { exercises ->
exercises.sortedBy { it.name } // Apply domain logic
}
}Presentation Layer:
private fun getExercisesByDay(day: Int) {
viewModelScope.launch {
emitState { copy(isLoading = true) }
workoutUseCases.getExercisesByDay(day, getCurrentDateTime()).fold(
onSuccess = { exercises ->
emitState {
copy(
exercises = exercises,
daySelected = day,
isLoading = false
)
}
},
onFailure = { error ->
emitState { copy(isLoading = false) }
emitEffect(WorkoutEffect.ShowSnackBar(
message = error.toUserMessage(),
type = SnackBarType.ERROR
))
}
)
}
}
fun Throwable.toUserMessage(): String = when (this) {
is NetworkException -> "Network error. Please check your connection."
is ValidationException -> this.message ?: "Validation failed"
else -> "An unexpected error occurred"
}5.5 Offline and data policy (Mobile)
Workout reads with a local datasource use BaseRepository.doRequest combined with DataPolicyMode (force_online, cache_fallback, remote_only) from Remote Config / env; many endpoints remain remote-only. The canonical description lives in Offline & sync (tiers, policy resolution, soft UX for tasks/habits).
Data Models & Schema
6.1 Domain Models (Shared - Kotlin Multiplatform)
File Location: /shared/src/commonMain/kotlin/com/esteban/ruano/oter/models/
@Serializable
data class Exercise(
val id: String? = null,
val name: String,
val description: String? = null,
val restSecs: Int? = null,
val baseSets: Int? = null,
val baseReps: Int? = null,
val muscleGroup: String? = null, // FULL_BODY, CORE, LEGS, CHEST, BACK, SHOULDERS, ARMS, GLUTES, CARDIO
val equipment: List<Equipment>? = emptyList(),
val resource: Resource? = null
)
@Serializable
data class WorkoutDay(
val id: String,
val day: Int, // 1-7 (Monday-Sunday)
val time: String? = null, // HH:mm format
val name: String, // "Monday", "Leg Day", etc.
val exercises: List<Exercise> = emptyList(),
val isCompleted: Boolean = false
)
@Serializable
data class ExerciseSet(
val id: String,
val reps: Int,
val doneDateTime: String
)
@Serializable
data class ExerciseDayStatus(
val exerciseTrackId: String,
val exerciseId: String,
val setsCountDone: Int,
val totalRepsDone: Int,
val exerciseDone: Boolean,
val setsDone: List<ExerciseSet>
)
@Serializable
data class CreateExerciseSetTrack(
val exerciseId: String,
val workoutDayId: String,
val reps: Int,
val doneDateTime: String
)
@Serializable
data class CreateWorkoutTrack(
val dayId: Int,
val doneDateTime: String
)
@Serializable
data class CreateExerciseTrack(
val exerciseId: String,
val workoutDayId: String,
val doneDateTime: String
)
@Serializable
data class WorkoutTrack(
val id: String,
val workoutDayId: String,
val doneDateTime: String,
val status: String = "ACTIVE"
)
@Serializable
data class ExerciseTrack(
val id: String,
val exerciseId: String,
val workoutDayId: String,
val doneDateTime: String? = null,
val dateTime: String? = null,
val status: String = "ACTIVE"
)
@Serializable
data class UpdateWorkoutDay(
val id: String? = null,
val day: Int? = null,
val time: String? = null,
val name: String? = null,
val exercises: List<Exercise>? = null
)
@Serializable
data class WorkoutDashboard(
val workoutDays: List<WorkoutDay>,
val totalExercises: Int
)
enum class MuscleGroup {
FULL_BODY, CORE, LEGS, CHEST, BACK, SHOULDERS, ARMS, GLUTES, CARDIO
}6.2 Database Schema (Server - PostgreSQL)
WorkoutDays Table:
CREATE TABLE workout_days (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
day INTEGER NOT NULL, -- 1-7 for Mon-Sun
time TIME,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(10) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT valid_day CHECK (day >= 1 AND day <= 7),
UNIQUE(user_id, day) -- One workout day per user per weekday
);
CREATE INDEX idx_workout_days_user ON workout_days(user_id);
CREATE INDEX idx_workout_days_day ON workout_days(day);
CREATE INDEX idx_workout_days_status ON workout_days(status);Exercises Table:
CREATE TABLE exercises (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
rest_secs INTEGER,
base_sets INTEGER,
base_reps INTEGER,
muscle_group VARCHAR(20), -- FULL_BODY, CORE, LEGS, CHEST, BACK, SHOULDERS, ARMS, GLUTES, CARDIO
resource_id UUID REFERENCES resources(id),
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(10) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_exercises_user ON exercises(user_id);
CREATE INDEX idx_exercises_muscle_group ON exercises(muscle_group);
CREATE INDEX idx_exercises_status ON exercises(status);
CREATE INDEX idx_exercises_name ON exercises(name);ExercisesWithWorkoutDays Table (Junction):
CREATE TABLE exercises_with_workout_days (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
exercise_id UUID NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
workout_day_id UUID NOT NULL REFERENCES workout_days(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(exercise_id, workout_day_id) -- Prevent duplicate bindings
);
CREATE INDEX idx_ex_wd_exercise ON exercises_with_workout_days(exercise_id);
CREATE INDEX idx_ex_wd_workout_day ON exercises_with_workout_days(workout_day_id);
CREATE INDEX idx_ex_wd_user ON exercises_with_workout_days(user_id);WorkoutTracks Table:
CREATE TABLE workout_tracks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workout_day_id UUID NOT NULL REFERENCES workout_days(id) ON DELETE CASCADE,
done_date_time TIMESTAMP NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(10) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_workout_tracks_workout_day ON workout_tracks(workout_day_id);
CREATE INDEX idx_workout_tracks_user ON workout_tracks(user_id);
CREATE INDEX idx_workout_tracks_date ON workout_tracks(done_date_time);
CREATE INDEX idx_workout_tracks_status ON workout_tracks(status);ExerciseTracks Table:
CREATE TABLE exercise_tracks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
exercise_id UUID NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
workout_day_id UUID NOT NULL REFERENCES workout_days(id) ON DELETE CASCADE,
done_date_time TIMESTAMP, -- When exercise marked complete
date_time TIMESTAMP, -- Tracking timestamp (alternative)
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(10) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_exercise_tracks_exercise ON exercise_tracks(exercise_id);
CREATE INDEX idx_exercise_tracks_workout_day ON exercise_tracks(workout_day_id);
CREATE INDEX idx_exercise_tracks_user ON exercise_tracks(user_id);
CREATE INDEX idx_exercise_tracks_done_date ON exercise_tracks(done_date_time);
CREATE INDEX idx_exercise_tracks_status ON exercise_tracks(status);ExerciseSetTracks Table:
CREATE TABLE exercise_set_tracks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
exercise_track_id UUID NOT NULL REFERENCES exercise_tracks(id) ON DELETE CASCADE,
reps INTEGER NOT NULL,
done_date_time TIMESTAMP NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(10) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT positive_reps CHECK (reps > 0)
);
CREATE INDEX idx_exercise_set_tracks_track ON exercise_set_tracks(exercise_track_id);
CREATE INDEX idx_exercise_set_tracks_user ON exercise_set_tracks(user_id);
CREATE INDEX idx_exercise_set_tracks_date ON exercise_set_tracks(done_date_time);
CREATE INDEX idx_exercise_set_tracks_status ON exercise_set_tracks(status);6.3 Data Relationships
User (1) ←→ (N) WorkoutDays
User (1) ←→ (N) Exercises
WorkoutDay (1) ←→ (N) ExercisesWithWorkoutDays ←→ (N) Exercise
(Many-to-Many: One exercise on multiple days, one day has multiple exercises)
WorkoutDay (1) ←→ (N) WorkoutTracks
(One day can be completed multiple times on different dates)
Exercise (1) ←→ (N) ExerciseTracks
WorkoutDay (1) ←→ (N) ExerciseTracks
(ExerciseTrack represents one exercise session on one day)
ExerciseTrack (1) ←→ (N) ExerciseSetTracks
(One exercise session contains multiple sets)API Specifications
7.1 Base URL
Production: https://api.oterapp.com/workout
Development: http://localhost:8080/workout7.2 Authentication
All endpoints require JWT bearer token:
Authorization: Bearer <token>User ID extracted from token, not passed in request.
7.3 Endpoints
GET /workout/dashboard
Description: Get workout dashboard summary
Response:
{
"workoutDays": [
{
"id": "uuid",
"day": 1,
"time": "06:00",
"name": "Monday",
"exercises": [...],
"isCompleted": false
}
],
"totalExercises": 24
}GET /workout/days
Description: Get all workout days with exercises
Response:
[
{
"id": "uuid",
"day": 1,
"time": "06:00",
"name": "Monday",
"exercises": [
{
"id": "uuid",
"name": "Bench Press",
"description": "Chest exercise",
"restSecs": 90,
"baseSets": 3,
"baseReps": 10,
"muscleGroup": "CHEST",
"equipment": [],
"resource": null
}
],
"isCompleted": false
}
]GET /workout/days/{workoutDayId}
Description: Get specific workout day
Path Parameters:
workoutDayId(UUID): Workout day ID
Response: Single WorkoutDay object (same structure as above)
Error: 404 if not found
PATCH /workout/days/{workoutDayId}
Description: Update workout day
Path Parameters:
workoutDayId(UUID): Workout day ID
Request Body:
{
"id": "uuid",
"day": 1,
"time": "07:00",
"name": "Leg Day",
"exercises": [...]
}Response:
- 200 OK: Updated workout day
- 404 Not Found
- 403 Forbidden
GET /workout/byDay/{day}
Description: Get exercises for specific day with completion status
Path Parameters:
day(integer 1-7): Day of week
Query Parameters:
dateTime(ISO datetime, optional): Date to check completion status (default: now)
Response:
[
{
"id": "uuid",
"name": "Bench Press",
"description": "Chest exercise",
"restSecs": 90,
"baseSets": 3,
"baseReps": 10,
"muscleGroup": "CHEST",
"equipment": [],
"resource": null
}
]Note: This returns exercises only, not WorkoutDay wrapper. Use GET /workout/exercise-tracking/completed/{workoutDayId} to get completion status.
GET /workout/exercises
Description: Get paginated, filtered exercises
Query Parameters:
filter(string): Search by namelimit(integer, default: 50): Results per pageoffset(integer, default: 0): Page number
Response:
[
{
"id": "uuid",
"name": "Bench Press",
...
}
]GET /workout/exercises/{id}
Description: Get single exercise by ID
Path Parameters:
id(UUID): Exercise ID
Response: Single Exercise object
Error: 404 if not found or unauthorized
POST /workout/exercises
Description: Create new exercise
Request Body:
{
"name": "Bench Press",
"description": "Chest exercise",
"restSecs": 90,
"baseSets": 3,
"baseReps": 10,
"muscleGroup": "CHEST"
}Response:
- 201 Created
- Location header: /workout/exercises/{id}
- Body: Created exercise
Errors:
- 400 Bad Request: Validation errors
PATCH /workout/exercises/{id}
Description: Update existing exercise
Path Parameters:
id(UUID): Exercise ID
Request Body: Same as POST
Response:
- 200 OK: Updated exercise
- 404 Not Found
- 403 Forbidden
DELETE /workout/exercises/{id}
Description: Soft delete exercise
Path Parameters:
id(UUID): Exercise ID
Response:
- 204 No Content
- 404 Not Found
- 403 Forbidden
POST /workout/exercises/bind
Description: Bind exercise to workout day
Request Body:
{
"exerciseId": "uuid",
"workoutDayDay": 1
}Response:
- 200 OK
- 409 Conflict: Already bound
POST /workout/exercises/unbind
Description: Unbind exercise from workout day
Request Body:
{
"exerciseId": "uuid",
"workoutDayDay": 1
}Response:
- 200 OK
- 404 Not Found
Note: Endpoint naming inconsistency - should be DELETE /workout/exercises/bind
POST /workout/tracking/complete
Description: Mark workout as complete
Request Body:
{
"dayId": 1,
"doneDateTime": "2025-12-29T06:30:00"
}Response:
- 201 Created
- Body: Track ID
DELETE /workout/tracking/{trackId}
Description: Undo workout completion
Path Parameters:
trackId(UUID): Workout track ID
Response:
- 204 No Content
- 404 Not Found
GET /workout/tracking/week
Description: Get workouts completed per day this week
Response:
[0, 1, 1, 0, 1, 0, 0]Format: Array of 7 integers (counts for Sun-Sat or Mon-Sun)
GET /workout/tracking/range
Description: Get workout tracks by date range
Query Parameters:
startDate(ISO date): Start dateendDate(ISO date): End date
Response:
[
{
"id": "uuid",
"workoutDayId": "uuid",
"doneDateTime": "2025-12-29T06:30:00",
"status": "ACTIVE"
}
]DELETE /workout/tracking/track/{trackId}
Description: Delete specific workout track
Path Parameters:
trackId(UUID): Track ID
Response:
- 204 No Content
- 404 Not Found
POST /workout/exercise-tracking/sets/complete
Description: Log exercise set
Request Body:
{
"exerciseId": "uuid",
"workoutDayId": "uuid",
"reps": 12,
"doneDateTime": "2025-12-29T09:30:00"
}Response:
- 201 Created
- Body: Set ID
DELETE /workout/exercise-tracking/sets/{setTrackId}
Description: Delete exercise set
Path Parameters:
setTrackId(UUID): Set track ID
Response:
- 204 No Content
- 404 Not Found
POST /workout/exercise-tracking/complete
Description: Mark exercise as complete
Request Body:
{
"exerciseId": "uuid",
"workoutDayId": "uuid",
"doneDateTime": "2025-12-29T09:45:00"
}Response:
- 201 Created
- Body: Exercise track ID
DELETE /workout/exercise-tracking/{trackId}
Description: Undo exercise completion
Path Parameters:
trackId(UUID): Exercise track ID
Response:
- 204 No Content
- 404 Not Found
GET /workout/exercise-tracking/range
Description: Get exercise tracks by date range
Query Parameters:
startDate(ISO date): Start dateendDate(ISO date): End date
Response:
[
{
"id": "uuid",
"exerciseId": "uuid",
"workoutDayId": "uuid",
"doneDateTime": "2025-12-29T09:45:00",
"dateTime": "2025-12-29T09:45:00",
"status": "ACTIVE"
}
]GET /workout/exercise-tracking/completed/{workoutDayId}
Description: Get completed exercises for workout day
Path Parameters:
workoutDayId(UUID): Workout day ID
Query Parameters:
dateTime(ISO datetime, optional): Date to check (default: now)
Response:
[
{
"exerciseTrackId": "uuid",
"exerciseId": "uuid",
"setsCountDone": 3,
"totalRepsDone": 30,
"exerciseDone": true,
"setsDone": [
{
"id": "uuid",
"reps": 12,
"doneDateTime": "2025-12-29T09:30:00"
},
{
"id": "uuid",
"reps": 10,
"doneDateTime": "2025-12-29T09:32:00"
},
{
"id": "uuid",
"reps": 8,
"doneDateTime": "2025-12-29T09:34:00"
}
]
}
]UI/UX Guidelines
8.1 Visual Design Principles
Color Coding
- Exercise Completed: Green checkmark, green border
- Set Logged: Blue accent
- Workout Completed: Gold/yellow star or badge
- In Progress: Orange/amber accent
- Not Started: Neutral/gray
- Error State: Red accents
Muscle Group Colors (Optional Enhancement)
- CHEST: Blue
- BACK: Green
- LEGS: Purple
- ARMS: Orange
- SHOULDERS: Teal
- CORE: Yellow
- FULL_BODY: Gray
- GLUTES: Pink
- CARDIO: Red
Typography
- Heading 1 (Screen title): Bold, 24-28px
- Heading 2 (Day name): Semi-bold, 20-22px
- Heading 3 (Exercise name): Semi-bold, 16-18px
- Body: Regular, 14-16px
- Caption (metadata): Regular, 12-14px
- Set Data: Monospace, 14px (for alignment)
8.2 Iconography
Recommended Icons:
- Add Exercise: Plus icon
- Edit: Pencil icon
- Delete: Trash icon
- Complete Exercise: Checkmark or check-circle
- Complete Workout: Star or trophy
- Log Set: Plus-circle or clipboard
- Rest Timer: Clock or timer icon
- Muscle Group: Muscle/body icon
- Schedule: Calendar icon
- History: Clock-arrow or history icon
8.3 Loading States
Skeleton Screens:
- Exercise cards with shimmer effect
- Match layout of actual content
- Show immediately while data loads
Progress Indicators:
- Indeterminate spinner for indefinite operations
- Progress bar for known operations (e.g., syncing offline data)
8.4 Empty States
No Exercises in Library:
+----------------------------------+
| [Illustration: Dumbbell] |
| |
| No exercises yet |
| Create your first exercise to |
| start tracking workouts |
| |
| [Create Exercise button] |
+----------------------------------+No Exercises for Selected Day:
+----------------------------------+
| [Illustration: Calendar] |
| |
| No exercises for Monday |
| Assign exercises to this day |
| |
| [View All Exercises button] |
+----------------------------------+No Workout History:
+----------------------------------+
| [Illustration: Chart] |
| |
| No workout history yet |
| Start logging your workouts |
| |
| [Go to Workout button] |
+----------------------------------+8.5 Feedback & Notifications
Success Messages:
- “Exercise created successfully”
- “Set logged: 12 reps”
- “Exercise completed”
- “Workout completed”
- “Exercise added to Monday”
Error Messages:
- “Failed to load exercises. Please try again.”
- “Failed to log set. Check your connection.”
- “Exercise not found”
- “You don’t have permission to edit this exercise”
Confirmation Dialogs:
- Delete Exercise: “Are you sure you want to delete [Exercise Name]? This action cannot be undone.”
- Exceed Base Sets: “You’re adding set 4, but target is 3 sets. Continue?”
- Discard Changes: “You have unsaved changes. Discard them?“
8.6 Accessibility
Contrast:
- Ensure WCAG AA compliance (4.5:1 for normal text, 3:1 for large text)
- Use color AND icons/text to convey state
Screen Reader Support:
- Label all interactive elements
- Announce set logging: “Set 3 logged: 12 reps”
- Announce timer: “Rest timer: 90 seconds remaining”
Keyboard Navigation:
- All actions accessible via keyboard
- Visible focus indicators
- Logical tab order
Touch Targets:
- Minimum 44x44 points (iOS) or 48x48 dp (Android)
- Adequate spacing between set log buttons
Implementation Checklist
9.1 Backend Implementation Checklist
-
Database Schema
- Create WorkoutDays table
- Create Exercises table
- Create ExercisesWithWorkoutDays table
- Create WorkoutTracks table
- Create ExerciseTracks table
- Create ExerciseSetTracks table
- Set up foreign keys and cascades
- Create indexes for performance
- Add constraints (day 1-7, positive reps, etc.)
-
Server Models (DTOs)
- Define ExerciseDTO
- Define WorkoutDayDTO
- Define UpdateWorkoutDayDTO
- Define WorkoutTrackDTO
- Define ExerciseTrackDTO
- Define CreateExerciseSetTrackDTO
- Define CreateWorkoutTrackDTO
- Define CreateExerciseTrackDTO
- Define ExerciseDayStatusDTO
- Define WorkoutDashboardDTO
-
Repository Layer
- Implement WorkoutRepository
- Implement CRUD methods for exercises
- Implement CRUD methods for workout days
- Implement binding/unbinding methods
- Implement tracking methods (workout, exercise, set)
-
Service Layer
- Implement WorkoutService
- Implement getDashboard
- Implement getWorkoutDays / getWorkoutDayById
- Implement getExercises with filtering/pagination
- Implement createExercise / updateExercise / deleteExercise
- Implement getExercisesByDay with completion status
- Implement bindExerciseToDay / unbindExerciseFromDay
- Implement completeWorkout / unCompleteWorkout
- Implement completeExercise / unCompleteExercise
- Implement completeExerciseSet / unCompleteExerciseSet
- Implement getWorkoutsCompletedPerDayThisWeek
- Implement getWorkoutTracksByDateRange
- Implement getExerciseTracksByDateRange
- Implement getExerciseSetStatusForDay
-
Routing
- Implement GET /workout/dashboard
- Implement GET /workout/days
- Implement GET /workout/days/{workoutDayId}
- Implement PATCH /workout/days/{workoutDayId}
- Implement GET /workout/byDay/{day}
- Implement GET /workout/exercises
- Implement GET /workout/exercises/{id}
- Implement POST /workout/exercises
- Implement PATCH /workout/exercises/{id}
- Implement DELETE /workout/exercises/{id}
- Implement POST /workout/exercises/bind
- Implement POST /workout/exercises/unbind (fix to DELETE)
- Implement POST /workout/tracking/complete
- Implement DELETE /workout/tracking/{trackId}
- Implement GET /workout/tracking/week
- Implement GET /workout/tracking/range
- Implement DELETE /workout/tracking/track/{trackId}
- Implement POST /workout/exercise-tracking/sets/complete
- Implement DELETE /workout/exercise-tracking/sets/{setTrackId}
- Implement POST /workout/exercise-tracking/complete
- Implement DELETE /workout/exercise-tracking/{trackId}
- Implement GET /workout/exercise-tracking/range
- Implement GET /workout/exercise-tracking/completed/{workoutDayId}
- Add authentication to all endpoints
- Add request validation
-
Converters
- Implement Exercise entity to DTO converter
- Implement WorkoutDay entity to DTO converter
- Implement WorkoutTrack entity to DTO converter
- Implement ExerciseTrack entity to DTO converter
- Implement ExerciseSetTrack entity to DTO converter
- Implement DTO to entity converters
-
Testing
- Unit tests for service layer
- Integration tests for API endpoints
- Test binding/unbinding logic
- Test three-level tracking (workout, exercise, set)
- Test completion status queries
- Test authentication and authorization
- Test soft delete behavior
9.2 Desktop Implementation Checklist
-
Shared Models
- Define Exercise model
- Define WorkoutDay model
- Define ExerciseSet model
- Define ExerciseDayStatus model
- Define CreateExerciseSetTrack model
- Define CreateWorkoutTrack model
- Define CreateExerciseTrack model
- Define WorkoutTrack model
- Define ExerciseTrack model
- Define UpdateWorkoutDay model
- Define WorkoutDashboard model
- Define MuscleGroup enum
-
Data Layer
- Create WorkoutApi interface
- Implement API client
- Create WorkoutDataSource
- Create WorkoutRepository implementation
- Implement DTO to domain model mappers
- Add error handling
-
Domain Layer
- Define WorkoutRepository interface
- Create GetWorkoutDashboard use case
- Create GetWorkoutDays use case
- Create GetWorkoutDayById use case
- Create GetExercisesByDay use case
- Create GetAllExercises use case
- Create AddExercise use case
- Create UpdateExercise use case
- Create DeleteExercise use case
- Create BindExerciseToDay use case
- Create UnbindExerciseFromDay use case
- Create AddExerciseSet use case
- Create DeleteExerciseSet use case
- Create CompleteExercise use case
- Create UncompleteExercise use case
- Create CompleteWorkout use case
- Create UncompleteWorkout use case
- Create GetWorkoutsCompletedThisWeek use case
-
Presentation Layer (MVI Pattern)
- Define WorkoutState
- Define WorkoutIntent
- Define WorkoutEffect
- Create WorkoutViewModel
- Define ExerciseFormState
- Define ExerciseFormIntent
- Create ExerciseFormViewModel
-
UI Components
- Create DaySelectorChips composable
- Create ExerciseCard composable
- Create ExerciseCardExpanded composable (with set logging)
- Create SetLoggingInput composable
- Create ExerciseForm dialog/screen
- Create ManageScheduleDialog composable
- Create WorkoutSummary composable
- Create RestTimerOverlay composable (optional)
- Create ExerciseList composable
- Create ExerciseGrid composable
-
Screens
- Create WorkoutDashboardScreen
- Create DailyWorkoutScreen (day view)
- Create AllExercisesScreen (library view)
- Create WorkoutHistoryScreen
- Implement navigation between screens
-
Features
- Implement day selection (1-7 + All)
- Implement exercise filtering (name search, muscle group)
- Implement exercise sorting
- Implement pagination for exercise list
- Implement set logging with warnings
- Implement set editing (newly created only)
- Implement set deletion
- Implement exercise completion
- Implement workout completion
- Implement exercise binding/unbinding
- Implement exercise CRUD operations
- Implement weekly summary display
- Implement exercise-day mapping
-
Testing
- Unit tests for ViewModels
- Unit tests for use cases
- UI tests for critical flows (log set, complete workout)
9.3 Mobile Implementation Checklist
(Similar to desktop, but with mobile-specific considerations)
-
Mobile-Specific UI
- Implement mobile ExerciseCard (compact)
- Implement mobile SetLoggingInput (inline)
- Implement mobile FilterBottomSheet
- Implement mobile QuickActionsBottomSheet
- Implement mobile RestTimerOverlay (full-screen or notification)
- Implement swipe gestures on exercise cards
- Implement swipe gestures on logged sets
- Implement pull-to-refresh
- Implement floating action button
-
Mobile Enhancements
- Rest timer with vibration/sound
- Volume buttons for quick set logging
- Screen keep-awake during workout
- Voice commands for hands-free logging
- Workout mode (immersive full-screen)
-
Offline Support (if applicable)
- Create local database (Room/SQLDelight)
- Create LocalDataSource
- Implement caching logic for exercises and days
- Implement sync queue for offline set logging
- Add offline mode toggle in settings
- Conflict resolution strategy
End of Workout Feature Expert Guide
This document serves as the single source of truth for all workout feature implementations. Developers should refer to this guide when building or modifying workout-related functionality across any platform.
For questions or clarifications, contact the architecture team or submit a documentation update request.