# Implementation Plan: January 12, 2026 Features Based on the screenshots and explanations, this document outlines the implementation plan for 5 new features in ActaLog. --- ## Feature 1: RPE (Rate of Perceived Exertion) Tracking **Priority:** HIGH **Complexity:** MEDIUM **Files to modify:** ~12 files ### Overview Add RPE (2-10 scale) to workout logging for both movements and WODs. ### Visual Reference Based on RPE-scale_large.webp, the scale uses color coding: - **10 (Red):** Max effort - Almost impossible to keep going, completely out of breath - **9 (Red-Orange):** Very hard - Very difficult to maintain, barely breathe/speak - **7-8 (Orange):** Vigorous - Borderline uncomfortable, short of breath - **4-6 (Yellow):** Moderate - Breathing heavily, can hold short conversation - **2-3 (Green):** Light - Can maintain for hours, easy to breathe and talk ### Implementation Steps #### 1.1 Database Migration Create migration: `migrations/XXXXXX_add_rpe_columns.up.sql` ```sql ALTER TABLE user_workout_movements ADD COLUMN rpe INTEGER; ALTER TABLE user_workout_wods ADD COLUMN rpe INTEGER; ``` #### 1.2 Domain Layer Changes **File:** `internal/domain/movement.go` - Add `RPE *int` field to `UserWorkoutMovement` struct (after `Notes` field) **File:** `internal/domain/wod.go` - Add `RPE *int` field to `UserWorkoutWOD` struct (after `Notes` field) #### 1.3 Repository Layer Changes **File:** `internal/repository/user_workout_movement_repository.go` - Update INSERT queries to include `rpe` column - Update SELECT queries to retrieve `rpe` - Update UPDATE queries to modify `rpe` **File:** `internal/repository/user_workout_wod_repository.go` - Same updates as movement repository **File:** `internal/repository/database.go` - Update CREATE TABLE statements for `user_workout_movements` and `user_workout_wods` #### 1.4 Handler Layer Changes **File:** `internal/handler/user_workout_handler.go` - Add `RPE *int` to `MovementPerformance` struct - Add `RPE *int` to `WODPerformance` struct - Update request parsing and response building #### 1.5 Frontend Changes **File:** `web/src/views/LogWorkoutView.vue` - **Movement Performance section (lines 137-222):** Add RPE v-select after notes textarea (line 220) - **WOD Performance section (lines 229-350):** Add RPE v-select after notes textarea (line 348) - Add `rpe` field to `movementPerformance` and `wodPerformance` arrays in script section **File:** `web/src/views/PerformanceView.vue` - Display RPE in Performance History entries (lines 282-325) - Show RPE badge/chip next to date if recorded **File:** `web/src/components/rpe-scale.js` (NEW - shared utility) - Export RPE_SCALE constant for reuse across components #### 1.6 RPE Scale Reference Data (Color-Coded) Create static reference in frontend matching the visual reference: ```javascript const RPE_SCALE = [ { value: null, text: 'Not recorded', color: 'grey' }, { value: 2, text: '2 - Very light', description: 'Like a slow walk', color: '#4CAF50' }, { value: 3, text: '3 - Light', description: 'Comfortable, easy conversation', color: '#8BC34A' }, { value: 4, text: '4 - Light-Moderate', description: 'Comfortable pace', color: '#CDDC39' }, { value: 5, text: '5 - Moderate', description: 'Breathing heavier, can talk', color: '#FFEB3B' }, { value: 6, text: '6 - Moderate-Hard', description: 'More challenging', color: '#FFC107' }, { value: 7, text: '7 - Vigorous', description: 'Uncomfortable, short sentences', color: '#FF9800' }, { value: 8, text: '8 - Hard', description: 'Heavy breathing, few words', color: '#FF5722' }, { value: 9, text: '9 - Very hard', description: 'Can barely speak', color: '#F44336' }, { value: 10, text: '10 - Max effort', description: 'All-out, cannot continue', color: '#D32F2F' } ] ``` --- ## Feature 2: Detail View on Card Click **Priority:** MEDIUM **Complexity:** LOW-MEDIUM **Files to modify:** ~6-8 files ### Overview Clicking any WOD, Template, or Movement card should display a read-only detail view with Back button, QuickLog link, and Edit button. ### Implementation Steps #### 2.1 Create Reusable Detail Dialog Component **New file:** `web/src/components/DetailViewDialog.vue` - Props: `type` (wod|template|movement), `id`, `show` - Emits: `close`, `quicklog`, `edit` - Fetch data based on type and id - Render markdown description - Include action buttons: Back, QuickLog, Edit #### 2.2 Update Card Components Identify all locations where cards are rendered: - `web/src/views/LogWorkoutView.vue` - Template/WOD selection cards - `web/src/views/MovementListView.vue` - Movement list - `web/src/views/WODListView.vue` - WOD list - `web/src/views/WorkoutTemplateListView.vue` - Template list - `web/src/views/DashboardView.vue` - Recent activity cards For each, add: - `@click` handler on card body (not on existing action buttons) - Check if click target is an action button; if not, open detail dialog - Pass entity type and ID to dialog #### 2.3 Dialog Content Structure ```vue {{ entityName }} Back QuickLog Edit ``` --- ## Feature 3: Fix Workout Summary Stats **Priority:** HIGH **Complexity:** LOW **Files to modify:** ~2-3 files ### Overview Verify and fix workout summary statistics on User Profile/Dashboard screen, particularly "All Time" calculations. ### Investigation Steps #### 3.1 Review Current Implementation **File:** `web/src/views/DashboardView.vue` - Examine how stats are calculated/fetched - Check if "All Time" uses correct date range (no bounds) **File:** `internal/handler/user_workout_handler.go` - Review endpoints that provide stats data - Check query parameters for date filtering **File:** `internal/repository/user_workout_repository.go` - Review `ListByUserAndDateRange()` function - Check if null date bounds are handled correctly #### 3.2 Potential Issues to Check 1. Date range filters incorrectly applied to "All Time" 2. Off-by-one errors in month/week calculations 3. Timezone handling issues 4. Cached values not updating #### 3.3 Fix Implementation Based on investigation findings: - Update repository queries if date filtering is wrong - Update frontend stat calculation if client-side issue - Add tests for edge cases --- ## Feature 4: Percent of Best Section **Priority:** MEDIUM **Complexity:** MEDIUM **Files to modify:** ~1-2 files ### Overview Add "Percent of Best" section to Movement Stats screen showing weight percentages based on heaviest lift. ### Current PerformanceView.vue Structure (Movement-specific, lines 78-189) Current order: 1. Heaviest Lifts (lines 79-119) - v-card, NOT collapsible 2. Best Estimated 1RM (lines 121-153) - v-card, NOT collapsible 3. Rep Scheme Dropdown Filter (lines 155-171) 4. Performance Chart (lines 173-188) - v-card, NOT collapsible **Required Changes:** 1. Wrap all sections in `v-expansion-panels` 2. Reorder to: Best Estimated 1RM → Percent of Best (NEW) → Heaviest Lifts → Performance Chart 3. Default expanded: Heaviest Lifts, Percent of Best 4. Remove Rep Scheme Dropdown (or move inside Performance Chart panel) ### Implementation Steps #### 4.1 Frontend Component Changes **File:** `web/src/views/PerformanceView.vue` Replace lines 79-189 with collapsible expansion panels: ```vue ``` #### 4.2 Add Script Section Variables ```javascript // Default expanded panels (Heaviest Lifts and Percent of Best) const expandedPanels = ref(['heaviestLifts', 'percentOfBest']) // User's weight unit from settings const weightUnit = computed(() => settingsStore.weightUnit || 'lbs') // Get heaviest weight from data const heaviestWeight = computed(() => { if (heaviestLifts.value.length === 0) return null return heaviestLifts.value[0].weight }) // Show Percent of Best only for movements with weight data const showPercentOfBest = computed(() => { return heaviestWeight.value && heaviestWeight.value > 0 }) // Calculate percentages based on heaviest lift const percentages = computed(() => { if (!heaviestWeight.value) return [] // Percentages as specified: 95%, 90%, 85%, 80%, 75%, 70%, 65%, 60%, 55%, 50%, 25% const percents = [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 25] return percents.map(pct => ({ percent: pct, weight: Math.round(heaviestWeight.value * (pct / 100)) })) }) ``` #### 4.3 Visual Design - Two-column grid layout as shown in image_005.jpg - Column 1: "% of Heaviest" (left-aligned) - Column 2: Weight value in user's units (right-aligned, bold) - Clean, compact table styling --- ## Feature 5: Calendar Date Click Detail View **Priority:** MEDIUM **Complexity:** MEDIUM **Files to modify:** ~3-4 files ### Overview Clicking a date in the calendar should show detailed view of all workouts logged on that date. ### Implementation Steps #### 5.1 Update Calendar Component **File:** `web/src/components/WorkoutCalendar.vue` - Already emits `daySelected` event with Date object - No changes needed here #### 5.2 Create Date Workout Detail Component **New file:** `web/src/components/DateWorkoutDetail.vue` - Props: `date` (Date object), `show` (boolean) - Emits: `close`, `quicklog`, `edit` - Fetches all workouts for the selected date - Displays each workout with: - Template/WOD name - Movements performed - Scores/times - Back button, QuickLog link, Edit button #### 5.3 API Endpoint (if not exists) Check if endpoint exists for fetching workouts by date: - `GET /api/user-workouts?date=YYYY-MM-DD` If not, add to: **File:** `internal/handler/user_workout_handler.go` - Add date query parameter filtering - Return workouts with full details (movements, WODs) **File:** `internal/repository/user_workout_repository.go` - Add `GetByUserIDAndDate(userID, date)` method if not exists #### 5.4 Update Views Using Calendar **File:** `web/src/views/DashboardView.vue` - Handle `@daySelected` event - Open DateWorkoutDetail dialog **File:** `web/src/views/WorkoutCalendarView.vue` - Handle `@daySelected` event - Open DateWorkoutDetail dialog #### 5.5 Dialog Structure ```vue {{ formatDate(selectedDate) }}
No workouts logged on this date.
{{ workout.name || 'Workout' }} {{ workout.movements?.length || 0 }} movements, {{ workout.wods?.length || 0 }} WODs
Back QuickLog
``` --- ## Implementation Order (Recommended) 1. **Feature 3: Fix Workout Summary Stats** - Quick win, investigate and fix 2. **Feature 1: RPE Tracking** - Foundational data model change, do early 3. **Feature 4: Percent of Best** - Frontend-only, can be done in parallel 4. **Feature 2: Detail View on Card Click** - Reusable component, moderate effort 5. **Feature 5: Calendar Date Click** - Builds on Feature 2's patterns --- ## Testing Checklist ### RPE Feature - [ ] RPE saves correctly for movements - [ ] RPE saves correctly for WODs - [ ] RPE displays in workout history - [ ] RPE is optional (null allowed) - [ ] RPE validates range (2-10) ### Detail View Feature - [ ] WOD detail opens on card click - [ ] Template detail opens on card click - [ ] Movement detail opens on card click - [ ] Back button closes dialog - [ ] QuickLog navigates correctly - [ ] Edit navigates correctly - [ ] Existing card buttons still work ### Stats Fix - [ ] All Time shows correct total - [ ] This Month shows correct count - [ ] This Week shows correct count - [ ] Streak calculates correctly ### Percent of Best - [ ] Shows for strength movements - [ ] Hidden for non-weight movements - [ ] Calculations are correct - [ ] Section is collapsible - [ ] Default expanded state correct - [ ] Section order correct ### Calendar Date Click - [ ] Click opens detail view - [ ] Shows all workouts for date - [ ] Empty state for no workouts - [ ] Back button works - [ ] Edit button navigates correctly --- ## Migration Notes ### Database - Migrations should be backwards compatible - New columns should be nullable - No data migration needed (new fields only) ### API - New fields should be optional in requests - Existing clients should continue working - Version bump: 0.22.0 -> 0.23.0 (feature release) --- ## File Reference Summary | Feature | Backend Files | Frontend Files | |---------|---------------|----------------| | **1. RPE** | `domain/movement.go`, `domain/wod.go`, `repository/user_workout_movement_repository.go`, `repository/user_workout_wod_repository.go`, `repository/database.go`, `handler/user_workout_handler.go` | `views/LogWorkoutView.vue`, `views/PerformanceView.vue`, `components/rpe-scale.js` (new) | | **2. Detail View** | None | `components/DetailViewDialog.vue` (new), `views/LogWorkoutView.vue`, various list views | | **3. Stats Fix** | `handler/user_workout_handler.go`, `repository/user_workout_repository.go` | `views/DashboardView.vue` | | **4. Percent of Best** | None | `views/PerformanceView.vue` | | **5. Calendar Click** | `handler/user_workout_handler.go` (if date filter needed) | `components/DateWorkoutDetail.vue` (new), `views/DashboardView.vue`, `views/WorkoutCalendarView.vue` | --- ## Quick Start Commands ```bash # Create RPE migration make migrate-create name=add_rpe_columns # Run backend tests make test # Run frontend dev server cd web && npm run dev # Run frontend tests cd web && npm run test:run ``` --- ## Notes 1. **RPE Percentages Note:** The original spec listed "75%, 60%, 65%" which appears to be a typo. Implementation assumes descending order: 95%, 90%, 85%, 80%, 75%, 70%, 65%, 60%, 55%, 50%, 25%. 2. **Detail View Component:** Consider making a single reusable `DetailViewDialog.vue` component that accepts entity type (wod/template/movement) and ID, rather than three separate components. 3. **Calendar Integration:** The existing `WorkoutCalendar.vue` already emits `daySelected`. Only need to add handler in parent views. 4. **Stats Fix Priority:** This should be investigated first as it may reveal calculation bugs that affect other features.