UI Overhaul: Bottom Tab Nav, Calendar Home, Workout Tracking, Gold Design System#74
UI Overhaul: Bottom Tab Nav, Calendar Home, Workout Tracking, Gold Design System#74
Conversation
…sign system - New: DesignTokens.swift (adaptive colors, typography, spacing) - New: WorkoutModel.swift (data types + ObservableObject store) - New: ExerciseCardView.swift (sets/reps/weight + mAI Coach button) - New: WorkoutView.swift (active workout tracker with timer) - New: HomeView.swift (calendar, templates, stats) - New: MainTabView.swift (3-tab TabView with frosted glass bar) - New: ProfileView.swift (account + settings) - New: WelcomeView.swift (signed-out landing) - Updated: RootView, mAICoachApp, SignInView, BootScreen - Deleted: CoachView, MyWorkoutsView, BigRectButton - Re-encoded demo_bench.mp4 with SDR bt709 color space - Added STYLE_GUIDE.md design system documentation
- Removed BigRectButton spec (deleted) - Updated Tab Bar from placeholder to frosted glass spec - Added Exercise Card, mAI Coach Button, Calendar, Stat Card components - Updated buttons: primary text is now black, added dashed add button - Replaced screen templates with actual 3-tab layout - Added full navigation flow diagram - Added current file inventory table - Added DesignTokens.swift reference
There was a problem hiding this comment.
Pull request overview
This pull request implements a comprehensive UI overhaul for the mAI Coach iOS app, introducing a gold-themed design system, bottom tab navigation, calendar-based workout tracking, and a complete HTML/CSS/JS prototype. The PR includes 7 new files, modifies 5 existing files, and removes 3 deprecated files, along with extensive documentation in a new style guide and sprint progress report.
Changes:
- Introduced a bronze gold design system with comprehensive design tokens for colors, typography, and spacing
- Replaced previous navigation with a 3-tab bottom navigation (Home, Workout, Profile)
- Built calendar-based home screen with workout templates and stats
- Created HTML/CSS/JS prototype demonstrating the complete UI design
- Added detailed STYLE_GUIDE.md (v2.0) documenting all design patterns and conventions
Reviewed changes
Copilot reviewed 21 out of 25 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| temp/ui_prototype.html | Interactive HTML prototype with 3-tab navigation, calendar, workout tracking, and profile screens |
| temp/ui_styles.css | Complete CSS implementation of design tokens, components, and responsive phone frame |
| temp/ui_script.js | JavaScript functionality for theme switching, tab navigation, calendar, workout state management |
| temp/2026-03-01-066-Sprint9-ProgressReport.md | Sprint 9 progress report documenting ML model upgrades, rep detection, and audio coaching |
| temp/2026-03-01-066-Sprint9-ProgressReport.docx | Binary Word document version of the sprint report |
| STYLE_GUIDE.md | Comprehensive 555-line design system documentation with color palette, typography, components, and patterns |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
temp/ui_script.js
Outdated
| function setTheme(theme) { | ||
| document.documentElement.setAttribute('data-theme', theme); | ||
| document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active')); | ||
| event.target.classList.add('active'); | ||
| } |
There was a problem hiding this comment.
The setTheme function references event.target but the event parameter is not passed to the function. This will cause a ReferenceError when the theme buttons are clicked. The function should accept an event parameter: function setTheme(theme, event).
temp/ui_script.js
Outdated
|
|
||
| // --- Init --- | ||
| renderCalendar(); | ||
| renderDayWorkouts(); |
There was a problem hiding this comment.
The initial calendar state selects day 22 (line 32) but the renderDayWorkouts function is called without a selected day initially. This means when the page first loads, the day workouts section will show "Select a day" even though day 22 is marked as selected in the calendar. Consider initializing with day 22 selected or removing the initial selectedDay value.
| renderDayWorkouts(); | |
| renderDayWorkouts(22); |
temp/ui_prototype.html
Outdated
| <button class="theme-btn" onclick="setTheme('light')">Light</button> | ||
| <button class="theme-btn active" onclick="setTheme('dark')">Dark</button> |
There was a problem hiding this comment.
The HTML calls setTheme('light') and setTheme('dark') without passing the event object. To fix the bug in the JavaScript (where event.target is referenced), these onclick handlers should be updated to pass the event: onclick="setTheme('light', event)" and onclick="setTheme('dark', event)".
| <button class="theme-btn" onclick="setTheme('light')">Light</button> | |
| <button class="theme-btn active" onclick="setTheme('dark')">Dark</button> | |
| <button class="theme-btn" onclick="setTheme('light', event)">Light</button> | |
| <button class="theme-btn active" onclick="setTheme('dark', event)">Dark</button> |
temp/ui_script.js
Outdated
| const workoutDays = { | ||
| '2026-2-3': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-5': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-7': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-10': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-12': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-14': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-17': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-19': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-21': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-22': [{ name: 'Bench', detail: '2 exercises' }], | ||
| }; |
There was a problem hiding this comment.
The workout data object uses strings as keys with the format 'year-month-day', but the month is 1-indexed (e.g., '2026-2-3' means February 3). However, when constructing the key in the selectDay function, it also uses currentMonth + 1, which correctly accounts for JavaScript's 0-indexed months. This is consistent, but consider adding a comment explaining that months in the keys are 1-indexed to avoid future confusion.
temp/ui_prototype.html
Outdated
| <div class="calendar-grid" id="cal-days"></div> | ||
| </div> |
There was a problem hiding this comment.
The calendar day cells and other interactive elements should have keyboard navigation support. Currently, they only have onclick handlers. Consider adding tabindex="0" and onkeypress handlers to allow keyboard users to interact with the calendar, buttons, and other interactive elements.
temp/ui_styles.css
Outdated
| .phone-frame { | ||
| width:375px; height:812px; border-radius:44px; | ||
| overflow:hidden; position:relative; | ||
| background:var(--bg-primary); | ||
| box-shadow: 0 25px 80px rgba(0,0,0,0.5), inset 0 0 0 3px var(--phone-border); | ||
| margin-top:20px; transition:background 0.3s; | ||
| } |
There was a problem hiding this comment.
The CSS uses a hardcoded phone border color that differs between light and dark modes, but the phone frame itself is a prototype artifact. Consider adding a comment indicating this is for prototype visualization only and won't be present in the actual app implementation.
| │ │ | ||
| │ GOLD ACCENT #ad8c04 / #c9a30a │ | ||
| │ GOLD TEXT #8a7003 / #c9a30a │ | ||
| │ GOLD MUTED 12% op / 15% op │ |
There was a problem hiding this comment.
The Quick Reference Card section shows color values with "op" as a unit for opacity (e.g., "12% op / 15% op"). This is inconsistent with the standard notation used elsewhere in the guide where opacity is expressed as "0.12" or "rgba(..., 0.12)". Consider using consistent notation throughout the document.
| │ GOLD MUTED 12% op / 15% op │ | |
| │ GOLD MUTED 0.12 / 0.15 │ |
temp/ui_script.js
Outdated
| function showTab(name, btn) { | ||
| document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); | ||
| const tab = document.getElementById('tab-' + name); | ||
| if (tab) tab.classList.add('active'); | ||
| if (btn) { | ||
| document.querySelectorAll('.tab-item').forEach(b => b.classList.remove('active')); | ||
| btn.classList.add('active'); | ||
| } | ||
| // Middle tab: if workout active, show it | ||
| if (name === 'workout' && workoutActive) { | ||
| document.getElementById('workout-empty').style.display = 'none'; | ||
| document.getElementById('workout-active').style.display = 'flex'; | ||
| } | ||
| } |
There was a problem hiding this comment.
The showTab function is called from the "Add Workout" button with only the tab name parameter, but on line 17 there's a conditional check for btn. This could lead to the active tab item button not being updated visually when clicking "Add Workout". Consider ensuring all callers pass both parameters or handle the case where btn is undefined more explicitly.
temp/ui_prototype.html
Outdated
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>mAI Coach – UI Prototype v2</title> |
There was a problem hiding this comment.
The HTML title includes a non-breaking space character or similar whitespace issue. The title appears as "mAI Coach – UI Prototype v2" but uses an en-dash character. While this may be intentional for stylistic reasons, ensure it renders correctly across all browsers and platforms.
| <title>mAI Coach – UI Prototype v2</title> | |
| <title>mAI Coach – UI Prototype v2</title> |
temp/ui_prototype.html
Outdated
| <button class="tab-item active" onclick="showTab('home', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" /> | ||
| </svg> | ||
| <span class="tab-label">Home</span> | ||
| </button> | ||
| <button class="tab-item" onclick="showTab('workout', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path | ||
| d="M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z" /> | ||
| </svg> | ||
| <span class="tab-label">Workout</span> | ||
| </button> | ||
| <button class="tab-item" onclick="showTab('profile', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path | ||
| d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /> | ||
| </svg> | ||
| <span class="tab-label">Profile</span> | ||
| </button> |
There was a problem hiding this comment.
The accessibility labels for the tab items are present, but the SVG icons lack proper accessibility attributes. Consider adding role="img" and aria-label attributes to the SVG elements to improve screen reader support, or use aria-hidden="true" on the SVGs since the tab-label text provides the necessary context.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 27 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
temp/ui_styles.css
Outdated
| .phone-frame { | ||
| width:375px; height:812px; border-radius:44px; | ||
| overflow:hidden; position:relative; | ||
| background:var(--bg-primary); | ||
| box-shadow: 0 25px 80px rgba(0,0,0,0.5), inset 0 0 0 3px var(--phone-border); | ||
| margin-top:20px; transition:background 0.3s; | ||
| } |
There was a problem hiding this comment.
The phone frame uses fixed dimensions (375px × 812px) which matches iPhone X-style devices, but line 71 says "width:375px; height:812px;" This is good for a prototype but should be documented. The style guide mentions responsive design principles but the prototype is fixed-size. Consider adding a comment in the HTML noting this is for demonstration purposes and the production SwiftUI app will adapt to various device sizes.
temp/ui_script.js
Outdated
| function setTheme(theme) { | ||
| document.documentElement.setAttribute('data-theme', theme); | ||
| document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active')); | ||
| event.target.classList.add('active'); |
There was a problem hiding this comment.
The setTheme function on line 9 references 'event.target' directly without it being passed as a parameter. This will cause a ReferenceError if called from contexts where the event object is not in scope. The function should accept the event as a parameter: function setTheme(theme, event) or use 'this' with proper binding.
| function setTheme(theme) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| function setTheme(theme, evt) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active')); | |
| const e = evt || (typeof window !== 'undefined' ? window.event : null); | |
| if (e && e.target && e.target.classList) { | |
| e.target.classList.add('active'); | |
| } |
temp/ui_script.js
Outdated
| if (d === today.getDate() && currentMonth === today.getMonth() && currentYear === today.getFullYear()) { | ||
| dayEl.classList.add('today'); | ||
| } |
There was a problem hiding this comment.
The comparison on line 74 checks if the current day matches "today" by comparing individual date components. However, the 'today' constant is created once at page load. If a user leaves the page open past midnight, the calendar will incorrectly show yesterday as "today". Consider either regenerating the today value each time the calendar renders, or comparing using a date string key like the workout data uses.
temp/ui_script.js
Outdated
| @@ -0,0 +1,216 @@ | |||
| // ============================================ | |||
| // mAI Coach Prototype — JavaScript | |||
| // ============================================ | |||
There was a problem hiding this comment.
The navigation functions (showTab, changeMonth, selectDay, etc.) manipulate the DOM directly using getElementById and classList operations. For a prototype, this is acceptable, but the style guide and PR description indicate this is meant to guide SwiftUI implementation. Consider adding a comment at the top of the JavaScript file clarifying that this is prototype-only code and should not be directly translated to production, since SwiftUI uses a declarative approach rather than imperative DOM manipulation.
| // ============================================ | |
| // ============================================ | |
| // NOTE: This file contains imperative DOM-manipulation code intended for | |
| // prototype UI behavior only. It should NOT be directly translated into | |
| // production SwiftUI code. SwiftUI implementations should use a declarative | |
| // data-driven approach rather than mirroring these getElementById/classList | |
| // operations. |
temp/ui_script.js
Outdated
| } | ||
|
|
||
| function openWorkout(name) { | ||
| startSavedWorkout(name.charAt(0).toUpperCase() + name.slice(1)); |
There was a problem hiding this comment.
The template card onclick handlers call 'openWorkout' with lowercase names ('push', 'pull', 'legs'), and the openWorkout function on line 210-212 capitalizes them. However, the startSavedWorkout function expects the properly capitalized name. This creates an unnecessary step. Consider either storing the proper names in the onclick attributes or removing the capitalization logic from openWorkout.
| startSavedWorkout(name.charAt(0).toUpperCase() + name.slice(1)); | |
| startSavedWorkout(name); |
temp/ui_styles.css
Outdated
| /* ============================================ | ||
| mAI Coach Design Tokens | ||
| ============================================ */ | ||
| :root { --font: 'Inter', -apple-system, sans-serif; } | ||
|
|
||
| [data-theme="light"] { | ||
| --accent: #ad8c04; --accent-text: #8a7003; | ||
| --accent-muted: rgba(173,140,4,0.12); --accent-hover: #8a7003; | ||
| --accent-glow: rgba(173,140,4,0.2); | ||
| --bg-primary: #FFFFFF; --bg-secondary: #F5F5F5; | ||
| --bg-tertiary: #EBEBEB; --bg-elevated: #FFFFFF; | ||
| --text-primary: #1A1A1A; --text-secondary: #6B6B6B; | ||
| --text-tertiary: #999999; --text-inverse: #FFFFFF; | ||
| --border: #E0E0E0; --divider: #F0F0F0; | ||
| --success: #2D9A3F; --warning: #CC8800; --error: #D32F2F; | ||
| --shadow-sm: 0 1px 3px rgba(0,0,0,0.06); | ||
| --shadow-md: 0 4px 12px rgba(0,0,0,0.08); | ||
| --phone-border: #C0C0C0; | ||
| } | ||
| [data-theme="dark"] { | ||
| --accent: #c9a30a; --accent-text: #c9a30a; | ||
| --accent-muted: rgba(201,163,10,0.15); --accent-hover: #d4ad0d; | ||
| --accent-glow: rgba(201,163,10,0.25); | ||
| --bg-primary: #0A0A0F; --bg-secondary: #141420; | ||
| --bg-tertiary: #1E1E2C; --bg-elevated: #1A1A28; | ||
| --text-primary: #F0F0F0; --text-secondary: #8A8A8A; | ||
| --text-tertiary: #555555; --text-inverse: #FFFFFF; | ||
| --border: #2A2A34; --divider: #1E1E28; | ||
| --success: #34C759; --warning: #FF9F0A; --error: #FF453A; | ||
| --shadow-sm: none; --shadow-md: none; | ||
| --phone-border: #333; | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Base | ||
| ============================================ */ | ||
| * { margin:0; padding:0; box-sizing:border-box; } | ||
| body { | ||
| font-family: var(--font); background: #111119; | ||
| display: flex; flex-direction: column; align-items: center; | ||
| min-height: 100vh; padding: 0 20px 40px; | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Control Bar | ||
| ============================================ */ | ||
| .control-bar { | ||
| position: sticky; top: 0; width: 100%; max-width: 900px; | ||
| background: rgba(17,17,25,0.95); backdrop-filter: blur(12px); | ||
| border-bottom: 1px solid rgba(255,255,255,0.08); | ||
| z-index: 1000; padding: 14px 0; | ||
| } | ||
| .control-row { display:flex; align-items:center; gap:8px; } | ||
| .control-label { | ||
| font-size:11px; font-weight:700; color:rgba(255,255,255,0.35); | ||
| text-transform:uppercase; letter-spacing:1.5px; min-width:70px; | ||
| } | ||
| .theme-toggle { display:flex; background:rgba(255,255,255,0.08); border-radius:8px; padding:2px; gap:2px; } | ||
| .theme-btn { | ||
| padding:6px 16px; border-radius:7px; border:none; | ||
| font-size:12px; font-weight:600; cursor:pointer; | ||
| background:transparent; color:rgba(255,255,255,0.5); | ||
| transition:all 0.2s; font-family:var(--font); | ||
| } | ||
| .theme-btn.active { background:var(--accent); color:#000; } | ||
|
|
||
| /* ============================================ | ||
| Phone Frame | ||
| ============================================ */ | ||
| .phone-frame { | ||
| width:375px; height:812px; border-radius:44px; | ||
| overflow:hidden; position:relative; | ||
| background:var(--bg-primary); | ||
| box-shadow: 0 25px 80px rgba(0,0,0,0.5), inset 0 0 0 3px var(--phone-border); | ||
| margin-top:20px; transition:background 0.3s; | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Status Bar & Header | ||
| ============================================ */ | ||
| .status-bar { | ||
| height:54px; display:flex; justify-content:space-between; | ||
| align-items:flex-end; padding:0 30px 8px; | ||
| font-size:14px; font-weight:600; color:var(--text-primary); | ||
| background:var(--bg-primary); | ||
| } | ||
| .screen-header { padding:0 16px 8px; background:var(--bg-primary); } | ||
| .large-title { font-size:34px; font-weight:800; color:var(--text-primary); } | ||
|
|
||
| /* ============================================ | ||
| Scroll Area | ||
| ============================================ */ | ||
| .scroll-area { | ||
| overflow-y:auto; padding:0 16px; | ||
| height:calc(100% - 54px - 52px - 85px); | ||
| } | ||
| .settings-bg { background:var(--bg-secondary); padding-top:0; } | ||
|
|
||
| /* ============================================ | ||
| Tab Content | ||
| ============================================ */ | ||
| .tab-content { display:none; height:calc(100% - 83px); background:var(--bg-primary); } | ||
| .tab-content.active { display:flex; flex-direction:column; } | ||
|
|
||
| /* ============================================ | ||
| Bottom Tab Bar | ||
| ============================================ */ | ||
| .tab-bar { | ||
| position:absolute; bottom:0; left:0; right:0; | ||
| height:83px; /* 49 + 34 safe */ | ||
| background:var(--bg-primary); | ||
| border-top:0.5px solid var(--border); | ||
| display:flex; justify-content:space-around; | ||
| align-items:flex-start; padding-top:6px; | ||
| z-index:100; | ||
| } | ||
| .tab-item { | ||
| display:flex; flex-direction:column; align-items:center; | ||
| gap:2px; background:none; border:none; cursor:pointer; | ||
| padding:4px 20px; font-family:var(--font); | ||
| } | ||
| .tab-icon { width:24px; height:24px; fill:var(--text-tertiary); transition:fill 0.2s; } | ||
| .tab-label { font-size:10px; font-weight:500; color:var(--text-tertiary); transition:color 0.2s; } | ||
| .tab-item.active .tab-icon { fill:var(--accent); } | ||
| .tab-item.active .tab-label { color:var(--accent); } | ||
|
|
||
| .home-indicator { | ||
| position:absolute; bottom:8px; left:50%; transform:translateX(-50%); | ||
| width:134px; height:5px; border-radius:3px; | ||
| background:var(--text-tertiary); opacity:0.4; z-index:101; | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Calendar | ||
| ============================================ */ | ||
| .section-card { | ||
| background:var(--bg-secondary); border-radius:12px; | ||
| padding:14px; margin-top:12px; border:1px solid var(--border); | ||
| } | ||
| .calendar-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; } | ||
| .cal-title { font-size:17px; font-weight:700; color:var(--text-primary); } | ||
| .cal-arrow { | ||
| background:none; border:none; color:var(--accent); | ||
| font-size:24px; cursor:pointer; padding:0 8px; | ||
| font-weight:600; | ||
| } | ||
| .calendar-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:2px; text-align:center; } | ||
| .cal-day-label { font-size:11px; font-weight:600; color:var(--text-tertiary); padding:4px 0; } | ||
| .cal-day { | ||
| font-size:14px; font-weight:500; color:var(--text-primary); | ||
| padding:8px 0; border-radius:8px; cursor:pointer; | ||
| transition:all 0.15s; | ||
| } | ||
| .cal-day:hover { background:var(--accent-muted); } | ||
| .cal-day.today { background:var(--accent); color:#000; font-weight:700; border-radius:50%; } | ||
| .cal-day.selected { background:var(--accent-muted); border:1.5px solid var(--accent); } | ||
| .cal-day.has-workout::after { | ||
| content:''; display:block; width:4px; height:4px; | ||
| border-radius:50%; background:var(--accent); margin:2px auto 0; | ||
| } | ||
| .cal-day.empty { visibility:hidden; } | ||
|
|
||
| /* ============================================ | ||
| Day Workouts | ||
| ============================================ */ | ||
| .day-workouts { margin-top:16px; } | ||
| .day-workouts-header { margin-bottom:8px; } | ||
| .day-workouts-title { font-size:15px; font-weight:600; color:var(--text-secondary); } | ||
| .day-workout-card { | ||
| background:var(--bg-secondary); border:1px solid var(--border); | ||
| border-radius:12px; padding:14px 16px; margin-bottom:8px; | ||
| display:flex; align-items:center; justify-content:space-between; | ||
| cursor:pointer; transition:all 0.15s; | ||
| } | ||
| .day-workout-card:hover { border-color:var(--accent); } | ||
| .dwc-name { font-size:16px; font-weight:600; color:var(--text-primary); } | ||
| .dwc-detail { font-size:13px; color:var(--text-secondary); margin-top:2px; } | ||
| .chevron { color:var(--text-tertiary); font-size:20px; font-weight:300; } | ||
| .add-workout-btn { | ||
| width:100%; padding:12px; border-radius:12px; | ||
| background:var(--accent-muted); border:1px dashed var(--accent); | ||
| color:var(--accent-text); font-size:15px; font-weight:600; | ||
| cursor:pointer; font-family:var(--font); transition:all 0.15s; | ||
| } | ||
| .add-workout-btn:hover { background:var(--accent); color:#000; border-style:solid; } | ||
| .icon-plus { font-weight:300; font-size:18px; } | ||
|
|
||
| /* ============================================ | ||
| Templates | ||
| ============================================ */ | ||
| .section-title { | ||
| font-size:20px; font-weight:700; color:var(--text-primary); | ||
| margin:24px 0 12px; | ||
| } | ||
| .template-list { display:flex; flex-direction:column; gap:8px; } | ||
| .template-card { | ||
| background:var(--bg-secondary); border:1px solid var(--border); | ||
| border-radius:12px; padding:14px 16px; | ||
| display:flex; align-items:center; gap:12px; | ||
| cursor:pointer; transition:all 0.15s; | ||
| } | ||
| .template-card:hover { border-color:var(--accent); } | ||
| .template-icon-wrap { | ||
| width:40px; height:40px; border-radius:10px; | ||
| background:var(--accent-muted); display:flex; | ||
| align-items:center; justify-content:center; flex-shrink:0; | ||
| } | ||
| .si { width:20px; height:20px; fill:var(--accent); } | ||
| .template-info { flex:1; } | ||
| .template-name { font-size:16px; font-weight:600; color:var(--text-primary); } | ||
| .template-detail { font-size:13px; color:var(--text-secondary); } | ||
|
|
||
| /* ============================================ | ||
| Stats | ||
| ============================================ */ | ||
| .stats-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; } | ||
| .stat-card { | ||
| background:var(--bg-secondary); border:1px solid var(--border); | ||
| border-radius:12px; padding:16px; text-align:center; | ||
| } | ||
| .stat-value { font-size:32px; font-weight:800; color:var(--accent); } | ||
| .stat-label { font-size:12px; font-weight:500; color:var(--text-secondary); margin-top:4px; } | ||
| .chart-placeholder { margin-top:12px; } | ||
| .chart-title { font-size:14px; font-weight:600; color:var(--text-primary); margin-bottom:8px; } | ||
| .mini-chart { width:100%; height:80px; } | ||
| .chart-label-row { | ||
| display:flex; justify-content:space-between; | ||
| font-size:11px; color:var(--text-tertiary); margin-top:4px; | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Workout Screen | ||
| ============================================ */ | ||
| .workout-empty { display:flex; flex-direction:column; height:100%; } | ||
| .workout-active { display:flex; flex-direction:column; height:100%; } | ||
| .empty-state { | ||
| flex:1; display:flex; flex-direction:column; | ||
| align-items:center; justify-content:center; gap:12px; padding:0 32px; | ||
| } | ||
| .empty-icon { width:56px; height:56px; fill:var(--text-tertiary); opacity:0.4; } | ||
| .empty-title { font-size:22px; font-weight:700; color:var(--text-primary); } | ||
| .empty-subtitle { font-size:15px; color:var(--text-secondary); text-align:center; line-height:1.5; } | ||
| .btn-primary { | ||
| padding:14px 32px; border-radius:12px; border:none; | ||
| background:var(--accent); color:#000; font-size:17px; | ||
| font-weight:600; cursor:pointer; font-family:var(--font); | ||
| box-shadow:0 2px 8px var(--accent-glow); margin-top:8px; | ||
| } | ||
| .workout-timer { font-size:14px; font-weight:600; color:var(--accent); margin-top:4px; } | ||
|
|
||
| /* Exercise Card */ | ||
| .exercise-card { | ||
| background:var(--bg-secondary); border:1px solid var(--border); | ||
| border-radius:12px; padding:14px; margin-top:12px; | ||
| } | ||
| .exercise-header { | ||
| display:flex; align-items:center; justify-content:space-between; | ||
| margin-bottom:10px; | ||
| } | ||
| .exercise-name { font-size:18px; font-weight:700; color:var(--text-primary); } | ||
| .mai-coach-btn { | ||
| display:flex; align-items:center; gap:4px; | ||
| padding:6px 12px; border-radius:8px; | ||
| background:var(--accent-muted); border:1px solid var(--accent); | ||
| color:var(--accent); font-size:12px; font-weight:700; | ||
| cursor:pointer; font-family:var(--font); transition:all 0.15s; | ||
| } | ||
| .mai-coach-btn:hover { background:var(--accent); color:#000; } | ||
| .mai-coach-btn:hover .si-sm { fill:#000; } | ||
| .si-sm { width:14px; height:14px; fill:var(--accent); transition:fill 0.15s; } | ||
|
|
||
| .sets-header { | ||
| display:grid; grid-template-columns:40px 1fr 1fr 40px; gap:6px; | ||
| font-size:11px; font-weight:700; color:var(--text-tertiary); | ||
| text-transform:uppercase; letter-spacing:0.5px; | ||
| padding:0 4px 6px; border-bottom:1px solid var(--border); margin-bottom:6px; | ||
| } | ||
| .set-row { | ||
| display:grid; grid-template-columns:40px 1fr 1fr 40px; | ||
| gap:6px; padding:4px; align-items:center; | ||
| } | ||
| .set-num { font-size:14px; font-weight:600; color:var(--text-secondary); text-align:center; } | ||
| .set-input { | ||
| background:var(--bg-tertiary); border:1px solid var(--border); | ||
| border-radius:8px; padding:8px 10px; text-align:center; | ||
| font-size:15px; font-weight:600; color:var(--text-primary); | ||
| font-family:var(--font); outline:none; transition:border 0.15s; | ||
| } | ||
| .set-input:focus { border-color:var(--accent); } | ||
| .set-input::placeholder { color:var(--text-tertiary); } | ||
| .set-check { | ||
| width:36px; height:36px; border-radius:8px; | ||
| border:1.5px solid var(--border); background:var(--bg-tertiary); | ||
| color:var(--text-tertiary); font-size:16px; | ||
| cursor:pointer; transition:all 0.15s; display:flex; | ||
| align-items:center; justify-content:center; | ||
| } | ||
| .set-check.done { background:var(--accent); border-color:var(--accent); color:#000; } | ||
| .set-check:not(.done):hover { border-color:var(--accent); } | ||
|
|
||
| .add-set-btn { | ||
| width:100%; padding:8px; margin-top:6px; | ||
| background:none; border:1px dashed var(--border); | ||
| border-radius:8px; color:var(--text-secondary); | ||
| font-size:13px; font-weight:600; cursor:pointer; | ||
| font-family:var(--font); transition:all 0.15s; | ||
| } | ||
| .add-set-btn:hover { border-color:var(--accent); color:var(--accent); } | ||
|
|
||
| .exercise-notes { | ||
| width:100%; margin-top:8px; padding:8px 10px; | ||
| background:var(--bg-tertiary); border:1px solid var(--border); | ||
| border-radius:8px; font-size:13px; color:var(--text-primary); | ||
| font-family:var(--font); outline:none; | ||
| } | ||
| .exercise-notes:focus { border-color:var(--accent); } | ||
| .exercise-notes::placeholder { color:var(--text-tertiary); } | ||
|
|
||
| .add-exercise-btn { | ||
| width:100%; padding:14px; margin-top:12px; | ||
| background:var(--accent-muted); border:1px dashed var(--accent); | ||
| border-radius:12px; color:var(--accent-text); | ||
| font-size:15px; font-weight:600; cursor:pointer; | ||
| font-family:var(--font); transition:all 0.15s; | ||
| } | ||
| .add-exercise-btn:hover { background:var(--accent); color:#000; border-style:solid; } | ||
|
|
||
| .btn-finish { | ||
| width:100%; padding:16px; margin-top:20px; | ||
| background:var(--accent); border:none; border-radius:12px; | ||
| color:#000; font-size:17px; font-weight:700; | ||
| cursor:pointer; font-family:var(--font); | ||
| box-shadow:0 2px 12px var(--accent-glow); | ||
| } | ||
|
|
||
| /* ============================================ | ||
| Profile / Settings | ||
| ============================================ */ | ||
| .profile-card { | ||
| display:flex; align-items:center; gap:14px; | ||
| padding:16px; margin-top:12px; | ||
| background:var(--bg-secondary); border:1px solid var(--border); | ||
| border-radius:12px; | ||
| } | ||
| .profile-avatar { | ||
| width:50px; height:50px; border-radius:50%; | ||
| background:var(--accent); color:#000; | ||
| font-size:18px; font-weight:800; | ||
| display:flex; align-items:center; justify-content:center; | ||
| } | ||
| .profile-name { font-size:18px; font-weight:700; color:var(--text-primary); } | ||
| .profile-email { font-size:14px; color:var(--text-secondary); margin-top:2px; } | ||
|
|
||
| .settings-section { margin-top:24px; } | ||
| .settings-header { | ||
| padding:0 0 6px; font-size:13px; font-weight:600; | ||
| color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.5px; | ||
| } | ||
| .settings-group { | ||
| background:var(--bg-primary); border:0.5px solid var(--border); border-radius:12px; | ||
| overflow:hidden; | ||
| } | ||
| .settings-row { | ||
| padding:14px 16px; display:flex; align-items:center; | ||
| justify-content:space-between; border-bottom:0.5px solid var(--border); | ||
| } | ||
| .settings-row:last-child { border-bottom:none; } | ||
| .row-label { font-size:17px; color:var(--text-primary); } | ||
| .setting-label { font-size:17px; font-weight:600; color:var(--text-primary); margin-bottom:2px; } | ||
| .setting-desc { font-size:14px; color:var(--text-secondary); margin-bottom:10px; } | ||
| .settings-footer { padding:8px 0; font-size:13px; color:var(--text-secondary); line-height:1.4; } | ||
|
|
||
| .segmented-control { display:flex; background:var(--bg-tertiary); border-radius:8px; padding:2px; } | ||
| .segment { | ||
| flex:1; padding:6px; border-radius:7px; font-size:13px; | ||
| font-weight:600; cursor:pointer; background:transparent; | ||
| border:none; color:var(--text-primary); text-align:center; | ||
| transition:all 0.2s; font-family:var(--font); | ||
| } | ||
| .segment.active { background:var(--bg-primary); } | ||
| [data-theme="dark"] .segment.active { background:var(--bg-elevated); } | ||
|
|
||
| .toggle-switch { | ||
| width:51px; height:31px; border-radius:16px; | ||
| background:var(--bg-tertiary); position:relative; | ||
| cursor:pointer; transition:background 0.2s; flex-shrink:0; | ||
| } | ||
| .toggle-switch.on { background:var(--accent); } | ||
| .toggle-switch::after { | ||
| content:''; width:27px; height:27px; border-radius:50%; | ||
| background:#fff; position:absolute; top:2px; left:2px; | ||
| transition:transform 0.2s; box-shadow:0 1px 3px rgba(0,0,0,0.2); | ||
| } | ||
| .toggle-switch.on::after { transform:translateX(20px); } | ||
|
|
||
| /* ============================================ | ||
| Session Overlay | ||
| ============================================ */ | ||
| .overlay { | ||
| position:absolute; top:0; left:0; right:0; bottom:0; | ||
| z-index:200; background:#000; | ||
| } | ||
| .session-screen { width:100%; height:100%; position:relative; } | ||
| .session-top-bar { | ||
| position:absolute; top:54px; left:0; right:0; | ||
| display:flex; align-items:center; padding:0 16px; | ||
| z-index:10; | ||
| } | ||
| .session-close { | ||
| width:32px; height:32px; border-radius:50%; | ||
| background:rgba(255,255,255,0.15); border:none; | ||
| color:#fff; font-size:16px; cursor:pointer; | ||
| display:flex; align-items:center; justify-content:center; | ||
| } | ||
| .session-label { | ||
| margin-left:10px; font-size:15px; font-weight:600; | ||
| color:rgba(255,255,255,0.8); | ||
| } | ||
| .camera-feed { | ||
| width:100%; height:100%; | ||
| background:linear-gradient(135deg, #0A0A14 0%, #1a1a2e 50%, #0A0A14 100%); | ||
| display:flex; align-items:center; justify-content:center; | ||
| color:rgba(255,255,255,0.12); font-size:16px; font-weight:600; | ||
| } | ||
| .hud-overlay { | ||
| position:absolute; top:100px; left:16px; right:16px; | ||
| display:flex; flex-wrap:wrap; gap:8px; | ||
| } | ||
| .hud-pill { | ||
| display:inline-flex; align-items:center; gap:4px; | ||
| background:rgba(0,0,0,0.65); backdrop-filter:blur(12px); | ||
| padding:8px 14px; border-radius:20px; | ||
| color:#fff; font-size:13px; font-weight:600; | ||
| } | ||
| .hud-pill.good { border:1px solid rgba(52,199,89,0.4); } | ||
| .hud-pill.warn { border:1px solid rgba(255,159,10,0.4); } | ||
| .hud-bottom { | ||
| position:absolute; bottom:100px; left:0; right:0; | ||
| display:flex; justify-content:center; | ||
| } | ||
| .rep-counter { | ||
| background:rgba(0,0,0,0.7); backdrop-filter:blur(12px); | ||
| padding:16px 40px; border-radius:24px; text-align:center; | ||
| border:1px solid rgba(201,163,10,0.3); | ||
| } | ||
| .rep-counter .count { font-size:52px; font-weight:800; color:var(--accent); } | ||
| .rep-counter .count-label { | ||
| font-size:12px; color:rgba(255,255,255,0.4); | ||
| font-weight:700; text-transform:uppercase; letter-spacing:2px; | ||
| } |
There was a problem hiding this comment.
The CSS file defines colors with CSS custom properties (variables) but also includes extensive component styling. For maintainability, consider splitting this into separate files: one for design tokens (colors, typography, spacing) and another for component styles. This would make it easier to keep the tokens in sync with the SwiftUI DesignTokens.swift file and follows the separation of concerns principle mentioned in the style guide.
| - [INSERT SCREENSHOT OF CI CHECKS ALL GREEN ON PR #68] | ||
|
|
||
| **Potential risks:** | ||
| 1. **PR merge blocked on review** — PR #68 needs an approving review to merge. *Action:* Cole to review by Feb 25. |
There was a problem hiding this comment.
In the Sprint 9 Progress Report, line 81 mentions "Cole to review by Feb 25" but the report is dated March 1, 2026. This creates a timeline inconsistency - either the action item is in the past and should be updated with the actual outcome, or the report date is incorrect. The report should reflect the actual status as of the report date.
temp/ui_script.js
Outdated
| // --- Calendar --- | ||
| const months = ['January','February','March','April','May','June', | ||
| 'July','August','September','October','November','December']; | ||
| let currentYear = 2026, currentMonth = 1; // 0-indexed: 1 = February |
There was a problem hiding this comment.
Line 31 in the JavaScript uses a hardcoded date "let currentYear = 2026, currentMonth = 1;" (February 2026). This should either be initialized dynamically based on the current date (new Date().getFullYear(), new Date().getMonth()) or clearly documented as demo/prototype-only code that will need to be replaced in production.
| let currentYear = 2026, currentMonth = 1; // 0-indexed: 1 = February | |
| const today = new Date(); | |
| let currentYear = today.getFullYear(), currentMonth = today.getMonth(); |
temp/ui_prototype.html
Outdated
| <div class="tab-bar"> | ||
| <button class="tab-item active" onclick="showTab('home', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" /> | ||
| </svg> | ||
| <span class="tab-label">Home</span> | ||
| </button> | ||
| <button class="tab-item" onclick="showTab('workout', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path | ||
| d="M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z" /> | ||
| </svg> | ||
| <span class="tab-label">Workout</span> | ||
| </button> | ||
| <button class="tab-item" onclick="showTab('profile', this)"> | ||
| <svg class="tab-icon" viewBox="0 0 24 24"> | ||
| <path | ||
| d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /> | ||
| </svg> | ||
| <span class="tab-label">Profile</span> | ||
| </button> | ||
| </div> |
There was a problem hiding this comment.
Accessibility: The tab bar implementation uses button elements with onclick handlers, which is good for keyboard navigation. However, the active tab should have aria-selected="true" and the tab items should have role="tab" with the parent having role="tablist". Additionally, aria-controls attributes should link tabs to their content panels (tab-home, tab-workout, tab-profile).
temp/ui_prototype.html
Outdated
| placeholder="—"><input class="set-input" value="10" placeholder="—"><button | ||
| class="set-check done">✓</button></div> | ||
| <div class="set-row"><span class="set-num">2</span><input class="set-input" value="155" | ||
| placeholder="—"><input class="set-input" value="8" placeholder="—"><button | ||
| class="set-check done">✓</button></div> | ||
| <div class="set-row"><span class="set-num">3</span><input class="set-input" value="175" | ||
| placeholder="—"><input class="set-input" placeholder="—"><button | ||
| class="set-check">✓</button></div> |
There was a problem hiding this comment.
Accessibility: The form input fields (set-input class) lack associated labels. For screen reader users, there's no programmatic way to know what each input field represents (weight vs. reps). Consider adding visually-hidden labels or aria-label attributes. The visual column headers (sets-header) on line 177-182 are not programmatically associated with the inputs below them.
| placeholder="—"><input class="set-input" value="10" placeholder="—"><button | |
| class="set-check done">✓</button></div> | |
| <div class="set-row"><span class="set-num">2</span><input class="set-input" value="155" | |
| placeholder="—"><input class="set-input" value="8" placeholder="—"><button | |
| class="set-check done">✓</button></div> | |
| <div class="set-row"><span class="set-num">3</span><input class="set-input" value="175" | |
| placeholder="—"><input class="set-input" placeholder="—"><button | |
| class="set-check">✓</button></div> | |
| placeholder="—" aria-label="Weight (pounds)"><input class="set-input" value="10" placeholder="—" | |
| aria-label="Repetitions"><button class="set-check done">✓</button></div> | |
| <div class="set-row"><span class="set-num">2</span><input class="set-input" value="155" | |
| placeholder="—" aria-label="Weight (pounds)"><input class="set-input" value="8" placeholder="—" | |
| aria-label="Repetitions"><button class="set-check done">✓</button></div> | |
| <div class="set-row"><span class="set-num">3</span><input class="set-input" value="175" | |
| placeholder="—" aria-label="Weight (pounds)"><input class="set-input" placeholder="—" | |
| aria-label="Repetitions"><button class="set-check">✓</button></div> |
- MainTabView: use Tab API with .tabViewStyle(.tabBarOnly) for classic full-width bar - WorkoutView: replace Binding($store.activeWorkout) with explicit index-based bindings - Pass onCoachTap as named parameter for reliable closure connection
1b1f9ec to
82daf13
Compare
…ayer - Reverted demo_bench.mp4 to original (re-encoded version caused blank video) - Applied item.videoComposition = playerComp in DemoPlayerPipeline so the player renders with correct transform (fixes green tint) - Cleaned up temporary video files
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 26 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ### 2.1 — Upgrade MLP Model to v2 (128→64→32) | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [Issue #42](https://github.com/TWhit229/mAI_Coach/issues/42), [Issue #54](https://github.com/TWhit229/mAI_Coach/issues/54) | ||
| - **PR:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commit [`555da57`](https://github.com/TWhit229/mAI_Coach/commit/555da574745d44147d3fa7c3f04af19eb586eaa0) — trained on 1,572 augmented examples (mirror augmentation), final loss 0.0634. Exported `bench_mlp_v2_weights.json` for iOS. Archived all v1 model files to `models/archive/`. | ||
| - **CI:** All 3 checks pass (Python Quality, iOS Build Verification, test). [PR #68 checks](https://github.com/TWhit229/mAI_Coach/pull/68/checks) | ||
| - [INSERT SCREENSHOT OF TRAINING LOSS CURVE OR MODEL METRICS IF AVAILABLE] | ||
|
|
||
| ### 2.2 — Wrist-Y Peak Rep Detection | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [Issue #56](https://github.com/TWhit229/mAI_Coach/issues/56) | ||
| - **PR:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commit [`a838821`](https://github.com/TWhit229/mAI_Coach/commit/a838821) — replaced elbow-angle detector with streaming wrist-Y peak detection. Accuracy improved from 8% exact match to 49% exact / 87% within ±1 rep on labeled test clips. | ||
| - **Notes:** Also added `repDetector.reset()` to session reset flow to ensure clean state between sets. | ||
|
|
||
| ### 2.3 — Audio Coaching System | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [Issue #57](https://github.com/TWhit229/mAI_Coach/issues/57) | ||
| - **PR:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commits [`43596e1`](https://github.com/TWhit229/mAI_Coach/commit/43596e1), [`a9dd24a`](https://github.com/TWhit229/mAI_Coach/commit/a9dd24a) — 78 MP3 clips generated via Google Cloud TTS (male + female voices). `AudioCoach.swift` plays tag-based feedback (6 variations per tag), rep counts, and positive reinforcement. Settings page added for voice selection. | ||
| - [INSERT SCREENSHOT OF SETTINGS PAGE SHOWING VOICE SELECTION] | ||
|
|
||
| ### 2.4 — Dynamic Alignment Guide | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commit [`555da57`](https://github.com/TWhit229/mAI_Coach/commit/555da574745d44147d3fa7c3f04af19eb586eaa0) — replaced static `bench_setup_overlay` image with a pose-aware overlay. Dashed guide box turns green when upper body landmarks are detected inside. Auto-dismisses after 10 consecutive centered frames (~0.3s at 30fps). | ||
| - [INSERT SCREENSHOT OF ALIGNMENT GUIDE IN ACTION — GREEN STATE] | ||
|
|
||
| ### 2.5 — Session HUD Rework + My Workouts Button | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [Issue #65](https://github.com/TWhit229/mAI_Coach/issues/65), [Issue #71](https://github.com/TWhit229/mAI_Coach/issues/71) | ||
| - **PR:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commit [`c501d24`](https://github.com/TWhit229/mAI_Coach/commit/c501d249599b31117f7c95e916b0b365907f9b8f) — rearranged HUD: form issues at top, rep count + tracking in bottom-left. Tag names now display as Title Case (e.g., "Barbell Tilted" instead of "barbell_tilted"). Added "My Workouts" button on home screen with placeholder view. Moved Dev Data toggle from session to Settings > Developer. | ||
| - [INSERT SCREENSHOT OF NEW HUD LAYOUT DURING DEMO] | ||
| - [INSERT SCREENSHOT OF HOME SCREEN SHOWING BOTH BUTTONS] | ||
|
|
||
| ### 2.6 — Repo Cleanup & README Updates | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) | ||
| - **Artifact:** Commit [`c501d24`](https://github.com/TWhit229/mAI_Coach/commit/c501d249599b31117f7c95e916b0b365907f9b8f) — updated root, App Core, and Dev Tools READMEs with current architecture and features. Added `*.xcuserstate` to `.gitignore`. Removed stale `Documentation/` reference. | ||
| - **Notes:** All 3 READMEs now reflect the actual file structure and current model (v2). | ||
|
|
There was a problem hiding this comment.
The progress report references multiple commits (555da57, a838821, 43596e1, etc.) and GitHub issue/PR links. Verify that these commits and issues exist in the repository and are related to this PR. The report describes ML model improvements and audio coaching work, which is not mentioned in the current PR description about UI overhaul.
temp/ui_script.js
Outdated
| let currentYear = 2026, currentMonth = 1; // 0-indexed: 1 = February | ||
| let selectedDay = 22; |
There was a problem hiding this comment.
The currentMonth variable is initialized to 1 (representing February as 0-indexed), but the comment indicates this is the current month. However, line 45 in the HTML shows a demo workout on 2026-2-22 which is labeled as "today" in the demo data. Ensure this date consistency is intentional for the prototype demo.
temp/ui_prototype.html
Outdated
| <div class="large-title">Workout</div> | ||
| </div> | ||
| <div class="empty-state"> | ||
| <svg class="empty-icon" viewBox="0 0 24 24"> |
There was a problem hiding this comment.
The empty state SVG icon on line 147-150 and other SVG icons throughout the document lack 'aria-label' or 'role' attributes. For better accessibility, consider adding appropriate ARIA attributes or making these decorative with aria-hidden="true" if they're accompanied by text.
| <svg class="empty-icon" viewBox="0 0 24 24"> | |
| <svg class="empty-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"> |
temp/ui_styles.css
Outdated
| transition:all 0.15s; | ||
| } | ||
| .cal-day:hover { background:var(--accent-muted); } | ||
| .cal-day.today { background:var(--accent); color:#000; font-weight:700; border-radius:50%; } |
There was a problem hiding this comment.
The calendar day hover effect on line 154 uses background color transition, but the 'today' class on line 155 sets the border-radius to 50% (circle), while the default border-radius for cal-day is 8px (line 151). This creates an inconsistent shape when hovering over the today cell. Consider applying border-radius: 50% to the hover state as well, or keep all day cells at 8px radius for consistency.
| .cal-day.today { background:var(--accent); color:#000; font-weight:700; border-radius:50%; } | |
| .cal-day.today { background:var(--accent); color:#000; font-weight:700; } |
temp/ui_script.js
Outdated
| let selectedDay = 22; | ||
|
|
||
| // Days that have workouts (demo data) | ||
| const workoutDays = { | ||
| '2026-2-3': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-5': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-7': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-10': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-12': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-14': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-17': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-19': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-21': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-22': [{ name: 'Bench', detail: '2 exercises' }], | ||
| }; | ||
|
|
||
| function renderCalendar() { | ||
| const title = document.getElementById('cal-title'); | ||
| title.textContent = months[currentMonth] + ' ' + currentYear; | ||
|
|
||
| const container = document.getElementById('cal-days'); | ||
| container.innerHTML = ''; | ||
|
|
||
| const firstDay = new Date(currentYear, currentMonth, 1).getDay(); | ||
| const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); | ||
| const today = new Date(); | ||
|
|
||
| // Empty cells before first day | ||
| for (let i = 0; i < firstDay; i++) { | ||
| const empty = document.createElement('span'); | ||
| empty.className = 'cal-day empty'; | ||
| container.appendChild(empty); | ||
| } | ||
|
|
||
| for (let d = 1; d <= daysInMonth; d++) { | ||
| const dayEl = document.createElement('span'); | ||
| dayEl.className = 'cal-day'; | ||
| dayEl.textContent = d; | ||
|
|
||
| const key = currentYear + '-' + (currentMonth + 1) + '-' + d; | ||
| if (workoutDays[key]) dayEl.classList.add('has-workout'); | ||
|
|
||
| if (d === today.getDate() && currentMonth === today.getMonth() && currentYear === today.getFullYear()) { | ||
| dayEl.classList.add('today'); | ||
| } | ||
| if (d === selectedDay) dayEl.classList.add('selected'); | ||
|
|
||
| dayEl.onclick = () => selectDay(d); | ||
| container.appendChild(dayEl); | ||
| } | ||
| } | ||
|
|
||
| function changeMonth(dir) { | ||
| currentMonth += dir; | ||
| if (currentMonth > 11) { currentMonth = 0; currentYear++; } | ||
| if (currentMonth < 0) { currentMonth = 11; currentYear--; } | ||
| selectedDay = null; |
There was a problem hiding this comment.
The variable 'selectedDay' is initialized to 22, but when the user navigates to a different month using changeMonth(), selectedDay is set to null (line 88). This will cause the calendar to not show any selected day when navigating months. Consider initializing selectedDay to null or the current day based on today's date consistently.
| ``` | ||
| ┌─────────────────────────────────────────┐ | ||
| │ mAI Coach │ | ||
| │ Design Tokens │ | ||
| ├─────────────────────────────────────────┤ | ||
| │ │ | ||
| │ GOLD ACCENT #ad8c04 / #c9a30a │ | ||
| │ GOLD TEXT #8a7003 / #c9a30a │ | ||
| │ GOLD MUTED 12% op / 15% op │ | ||
| │ │ | ||
| │ BACKGROUND #FFFFFF / #0A0A0F │ | ||
| │ SURFACE #F5F5F5 / #141420 │ | ||
| │ TEXT PRIMARY #1A1A1A / #F0F0F0 │ | ||
| │ TEXT SECONDARY #6B6B6B / #8A8A8A │ | ||
| │ BORDER #E0E0E0 / #2A2A34 │ | ||
| │ │ | ||
| │ FONT SF Rounded │ | ||
| │ CORNER RADIUS 12pt (cards/btns) │ | ||
| │ SCREEN PADDING 16pt horizontal │ | ||
| │ SPACING BASE 4pt multiples │ | ||
| │ │ | ||
| │ SUCCESS #2D9A3F / #34C759 │ | ||
| │ WARNING #CC8800 / #FF9F0A │ | ||
| │ ERROR #D32F2F / #FF453A │ | ||
| │ │ | ||
| └─────────────────────────────────────────┘ | ||
| LEFT = Light / RIGHT = Dark | ||
| ``` |
There was a problem hiding this comment.
The Quick Reference Card section shows design tokens in a fixed-width ASCII art format. While visually appealing, this may not render correctly in all markdown viewers or when viewed on different screen sizes. Consider using a markdown table instead for better compatibility and accessibility.
| ### Current File Inventory | ||
|
|
||
| | File | Role | | ||
| |------|------| | ||
| | `DesignTokens.swift` | Color, typography, spacing constants + `CardStyle` modifier | | ||
| | `WorkoutModel.swift` | `Workout`, `Exercise`, `ExerciseSet` types + `WorkoutStore` | | ||
| | `MainTabView.swift` | 3-tab `TabView` with frosted glass bar | | ||
| | `HomeView.swift` | Calendar, day workouts, templates, stats | | ||
| | `WorkoutView.swift` | Active workout tracker | | ||
| | `ExerciseCardView.swift` | Exercise card with sets grid + mAI Coach button | | ||
| | `ProfileView.swift` | Account card + settings | | ||
| | `WelcomeView.swift` | Signed-out landing screen | | ||
| | `RootView.swift` | Boot → welcome or tab view router | | ||
| | `BootScreen.swift` | Animated launch screen | | ||
| | `SignInView.swift` | Email/password auth form | | ||
| | `BenchSessionView.swift` | Live/demo camera coaching session | | ||
| | `mAICoachApp.swift` | App entry, injects `AuthSession` + `WorkoutStore` | |
There was a problem hiding this comment.
The style guide references several Swift files in section 11.3 (Current File Inventory) including DesignTokens.swift, WorkoutModel.swift, MainTabView.swift, etc. However, the PR description mentions these as "New Files" but they are not visible in the diff. Either these files exist in a different location not shown in the temp/ folder, or they should be included in the PR diff. Verify that all referenced files are actually part of this PR.
| ## 1. Summary | ||
|
|
||
| - **Upgraded the ML model** from a 32→16 MLP to a 128→64→32 architecture with dropout, improving F1 from 0.57 to 0.76 on bench press form detection. | ||
| - **Replaced the rep detection algorithm** from angle-based to wrist-Y peak detection, improving accuracy from 8% exact match to 49% exact / 87% ±1 rep. | ||
| - **Shipped a full audio coaching system** using Google Cloud TTS with male/female voice selection, providing real-time form feedback and rep counting. | ||
| - **Built a dynamic alignment guide** that auto-dismisses when the user is properly centered, replacing the static image overlay. | ||
| - **Blocker:** PR #68 passes all 3 CI checks but awaits a teammate review to merge (branch protection rule). | ||
|
|
||
| --- | ||
|
|
||
| ## 2. Sprint Work Log | ||
|
|
||
| ### 2.1 — Upgrade MLP Model to v2 (128→64→32) | ||
| - **Status:** Done | ||
| - **Owner:** Travis Whitney | ||
| - **Source:** [Issue #42](https://github.com/TWhit229/mAI_Coach/issues/42), [Issue #54](https://github.com/TWhit229/mAI_Coach/issues/54) | ||
| - **PR:** [PR #68](https://github.com/TWhit229/mAI_Coach/pull/68) |
There was a problem hiding this comment.
The report mentions PR #68 awaiting teammate review as a blocker, but this is PR appears to be about a UI overhaul (based on the title). Clarify whether this Sprint 9 Progress Report is related to the current PR or if it's documenting previous work. The dates and content suggest this might be documentation for a different set of changes (ML model upgrades, rep detection, audio coaching) than what's described in the current PR description (UI overhaul).
| | Token | Light Mode | Dark Mode | Usage | | ||
| |-------|-----------|-----------|-------| | ||
| | `accent` | `#ad8c04` | `#c9a30a` | Primary buttons, active nav items, key icons | | ||
| | `accentText` | `#8a7003` | `#c9a30a` | Small text that needs to be gold (passes WCAG AA) | |
There was a problem hiding this comment.
The style guide states that accentText color (#8a7003 in light mode, #c9a30a in dark mode) "passes WCAG AA", but this claim should be verified against the backgrounds where this color will be used. The same color value is also used for the main 'accent' in dark mode, which might create confusion. Ensure the contrast ratios are tested for all use cases mentioned in the guide.
temp/ui_styles.css
Outdated
| --phone-border: #C0C0C0; | ||
| } | ||
| [data-theme="dark"] { | ||
| --accent: #c9a30a; --accent-text: #c9a30a; | ||
| --accent-muted: rgba(201,163,10,0.15); --accent-hover: #d4ad0d; | ||
| --accent-glow: rgba(201,163,10,0.25); | ||
| --bg-primary: #0A0A0F; --bg-secondary: #141420; | ||
| --bg-tertiary: #1E1E2C; --bg-elevated: #1A1A28; | ||
| --text-primary: #F0F0F0; --text-secondary: #8A8A8A; | ||
| --text-tertiary: #555555; --text-inverse: #FFFFFF; | ||
| --border: #2A2A34; --divider: #1E1E28; | ||
| --success: #34C759; --warning: #FF9F0A; --error: #FF453A; | ||
| --shadow-sm: none; --shadow-md: none; | ||
| --phone-border: #333; |
There was a problem hiding this comment.
The phone-border variable is defined in both light and dark themes but uses different colors (#C0C0C0 for light, #333 for dark). However, this variable is only used once in the .phone-frame selector on line 74. Consider whether this needs to be a CSS variable or if it could be a hardcoded value, especially since this is a prototype meant to showcase the design.
…-to-edge bar - configureWithOpaqueBackground forces flat bar, no rounded corners - Solid systemBackground color instead of translucent blur - Dropped iOS 18 Tab API, using .tabItem only for compatibility
- Add .buttonStyle(.plain) to template and day workout buttons in HomeView - Add startWorkoutFromTemplate() that creates fresh IDs for exercises/sets - MainTabView: walk UIKit hierarchy to find actual UITabBar and apply configureWithOpaqueBackground directly (bypasses SwiftUI override)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
temp/ui_script.js
Outdated
| const workoutDays = { | ||
| '2026-2-3': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-5': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-7': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-10': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-12': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-14': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-17': [{ name: 'Push', detail: '6 exercises' }], | ||
| '2026-2-19': [{ name: 'Pull', detail: '5 exercises' }], | ||
| '2026-2-21': [{ name: 'Legs', detail: '5 exercises' }], | ||
| '2026-2-22': [{ name: 'Bench', detail: '2 exercises' }], | ||
| }; |
There was a problem hiding this comment.
The calendar data structure uses strings as keys (e.g., '2026-2-3') which is inconsistent with JavaScript date handling. Month is 0-indexed in JavaScript Date objects but 1-indexed in these keys. This works but could be confusing. Consider using a consistent date key format like '2026-02-03' or ISO strings for better maintainability.
temp/ui_script.js
Outdated
| listEl.innerHTML = workouts.map(w => ` | ||
| <div class="day-workout-card" onclick="startSavedWorkout('${w.name}')"> | ||
| <div> | ||
| <div class="dwc-name">${w.name}</div> | ||
| <div class="dwc-detail">${w.detail}</div> | ||
| </div> | ||
| <span class="chevron">›</span> | ||
| </div> | ||
| `).join(''); |
There was a problem hiding this comment.
Using innerHTML to build the workout cards creates a potential XSS vulnerability if workout data (w.name, w.detail) comes from user input or an API. While this is demo/prototype code, it's important to sanitize data or use textContent/createElement for production code.
temp/ui_script.js
Outdated
| timerInterval = setInterval(() => { | ||
| timerSeconds++; | ||
| const m = Math.floor(timerSeconds / 60); | ||
| const s = (timerSeconds % 60).toString().padStart(2, '0'); | ||
| document.getElementById('workout-timer').textContent = m + ':' + s; |
There was a problem hiding this comment.
The timer display pads seconds but not minutes. When the timer exceeds 9 minutes and 59 seconds, the display will show "10:00", "11:00" etc., which is correct. However, for consistency with common timer UIs, consider whether minutes should also be zero-padded for values under 10 minutes (e.g., "09:45" instead of "9:45").
DemoPlayerPipeline: remove item.videoComposition = playerComp - This forced AVPlayer through composition pipeline, causing freeze - AVPlayer renders video correctly without a composition - Only the AVAssetReader needs the composition for pose inference MainTabView: use @available(iOS 18.0, *) with Tab API + .tabBarOnly - On iOS 18+, SwiftUI ignores UITabBar.appearance() and UIKit hacks - Only way to force classic full-width bar is Tab API + .tabBarOnly - @available attribute on separate computed property gates the APIs - Falls back to .tabItem on iOS 17 (classic bar is the default)
Tab bar: iOS 26 Liquid Glass styling cannot be disabled via any API. Replaced system TabView with custom implementation: - ZStack for content views (opacity + allowsHitTesting) - HStack button bar at bottom with solid background - Verified flat, full-width in simulator screenshot Demo video: DemoPlayerPipeline.player was 'private(set) var', not '@published'. SwiftUI didn't re-render BenchSessionView when the player was assigned asynchronously, causing blank video. Changed to '@published var player' so view updates when set. Both fixes verified in iPhone 17 Pro simulator (Xcode 26.2)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Sprint 9 Progress Report | ||
|
|
||
| **Team 066 — mAI Coach** | ||
| **Date:** 2026-03-01 | ||
| **Sprint Period:** Feb 14 – Feb 28, 2026 |
There was a problem hiding this comment.
The Sprint 9 Progress Report is dated March 1, 2026 but describes a sprint period of Feb 14 – Feb 28, 2026. However, the PR description states "Last Updated: February 22, 2026" in the style guide. This creates a timeline inconsistency - the progress report appears to be dated after the sprint ended (March 1) but the style guide and other changes reference February 22. This discrepancy should be resolved to ensure accurate project documentation.
temp/ui_prototype.html
Outdated
| <span class="hud-pill warn">Depth</span> | ||
| </div> | ||
| <div class="hud-bottom"> | ||
| <div class="rep-counter"> |
There was a problem hiding this comment.
The rep counter display on line 303 shows a hardcoded value of "5" reps. This static value doesn't update based on any workout logic in the JavaScript. Consider either connecting this to the demo workout state or adding a comment indicating this is a static mockup value.
| <div class="rep-counter"> | |
| <div class="rep-counter"> | |
| <!-- Static mock value for prototype UI; not wired to live workout logic --> |
temp/ui_script.js
Outdated
| let currentYear = 2026, currentMonth = 1; // 0-indexed: 1 = February | ||
| let selectedDay = 22; |
There was a problem hiding this comment.
In line 31, the calendar is hardcoded to February 2026 (currentMonth = 1, which is 0-indexed). This means the calendar will always display February 2026 on initial load, even when viewed in other months or years. Consider initializing these values from the current date using JavaScript's Date object to ensure the calendar shows the current month when the prototype loads.
| let currentYear = 2026, currentMonth = 1; // 0-indexed: 1 = February | |
| let selectedDay = 22; | |
| const initialToday = new Date(); | |
| let currentYear = initialToday.getFullYear(), currentMonth = initialToday.getMonth(); // 0-indexed month | |
| let selectedDay = initialToday.getDate(); |
Updated in: - PRIVACY_POLICY.md (contact section) - ProfileView.swift (hardcoded fallback email) - temp/ui_prototype.html (profile display)
New files: - ExerciseCatalog.swift: 70 exercises across 7 muscle groups (Chest, Back, Shoulders, Legs, Biceps, Triceps, Core) - ExercisePickerView.swift: searchable picker with equipment filter pills (Barbell, Dumbbell, Cable, Machine, Bodyweight, Other) Model changes: - EquipmentType enum with 6 cases, icons, and labels - Exercise now has 'equipment' field - All models (ExerciseSet, Exercise, Workout) are Codable - BenchSessionView.Mode is Codable WorkoutStore persistence: - save() writes workouts to JSON in documents dir - loadSavedData() loads on init, falls back to demo data - finishWorkout() calls save() automatically WorkoutView: - '+Add Exercise' opens ExercisePickerView as sheet - Tap exercise in picker to add it to active workout
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 117 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,580 @@ | |||
| # mAI Coach — UI Style Guide | |||
|
|
|||
| > **Version**: 3.0 | |||
There was a problem hiding this comment.
The PR description states "Updated STYLE_GUIDE.md to v2.0 matching implementation", but the actual file content shows > **Version**: 3.0. This is a discrepancy between the PR description and the code change — either the PR description is wrong (the version was actually bumped to 3.0, not 2.0), or the version number in the file is incorrect.
| | `temp/` | Scratch files (gitignored). | | ||
| | `archive/` | Archived files (gitignored). | |
There was a problem hiding this comment.
The scripts/ entry was removed from the Repository Structure table and replaced with temp/ and archive/, but scripts/ is still actively referenced in the Quick Start section just below (lines 43–44), as well as in CONTRIBUTING.md and Dev_tools/README.md. The scripts/ directory appears to still exist and be needed. Adding temp/ and archive/ to this table is misleading because both are gitignored and therefore not present in a fresh clone; they are scratch directories, not part of the repo structure that a new contributor would see.
- Reduced to 19 focused steps (from 28) - Creates demo workout with Bench Press, Barbell Rows + Laterals/Pushdowns superset - Auto-creates demo when entering workout phase, cleans up on finish/skip - Added 8 spotlight anchors to WorkoutDetailView: exerciseCard, supersetBlock, workoutNotes, tags, addExercise, addSuperset, actionButton - Added prSection spotlight to ProfileView - Fixed inverseMask (compositingGroup inside mask) - Superset button shown during tutorial even outside edit mode
- Auto-switch to Workout tab when a workout is started/resumed from any tab - Restore activeWorkout on app launch from persisted data (survives force-quit) - Only viewing a past workout stays on its tab; Resume/Start takes over middle tab - Finish clears activeWorkout and shows empty state
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 117 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| - **Profile Card** — User name and email display. | ||
| - **Coach Voice** — Male or female coach voice toggle. | ||
| - **Appearance** — Light/Dark mode toggle. |
There was a problem hiding this comment.
The Appearance feature is described as "Light/Dark mode toggle", but the actual implementation in ProfileView.swift uses a three-option segmented picker supporting Light, Dark, and System modes (matching the AppAppearance enum). The description should be updated to reflect the full set of options, such as "Light/Dark/System mode picker (follows device setting by default)".
| | `bgPrimary` | `#FFFFFF` | `#0A0A0F` | Main screen background | | ||
| | `bgSecondary` | `#F5F5F5` | `#141420` | Cards, grouped sections, modals | | ||
| | `bgTertiary` | `#EBEBEB` | `#1E1E2C` | Nested cards, input fields | | ||
| | `bgElevated` | `#FFFFFF` | `#1A1A28` | Elevated surfaces (sheets, popovers) | |
There was a problem hiding this comment.
The dark mode value for bgElevated (#1A1A28) is darker than bgTertiary (#1E1E2C), which contradicts the elevation hierarchy described in Section 8 where bgElevated (Level 3) is supposed to be the lightest surface. The RGB values confirm this: bgTertiary (30, 30, 44) is visually lighter than bgElevated (26, 26, 40). This means sheets, popovers, and modals will appear visually lower (darker) than input fields, which is the opposite of the intended layering. The bgElevated dark mode value should be lighter than #1E1E2C to correctly represent elevated surfaces.
- Rewrote TutorialOverlay positioning: uses safe area insets, clamps bubble Y position within visible bounds at all times (fixes lockup) - Moved calendar spotlight from outer section to .cardStyle() card - Moved templateShortcuts spotlight to first template card only - Removed section-level anchors that were overwriting inner ones - Empty anchor IDs (from conditional spotlights) now properly skipped - Tip bubble calculates above/below/fallback positions with safe margins
…ights
Root cause: anchorPreference replaces child preferences, causing
spotlight frames to resolve to parent container bounds instead of
the specific tagged element.
Fix: New TutorialFrameStore singleton where each .tutorialSpotlight()
modifier reports its frame via GeometryReader in a named coordinate
space ('tutorialRoot'). This completely avoids preference key merging
issues and gives exact, reliable element frames.
When a tutorial step highlights an element below the fold, the ScrollView automatically scrolls to bring it into view. Added section IDs and onChange handler in HomeView that maps each spotlight ID to the correct scroll anchor.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 117 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,580 @@ | |||
| # mAI Coach — UI Style Guide | |||
|
|
|||
| > **Version**: 3.0 | |||
There was a problem hiding this comment.
The PR description states "Updated STYLE_GUIDE.md to v2.0 matching implementation," but the STYLE_GUIDE.md file itself declares > **Version**: 3.0. The PR description is incorrect — it should say v3.0.
- Reordered workout steps to flow top-to-bottom: tags → exercise card → complete set → superset block → add exercise → add superset → notes → finish - Added ScrollViewReader to WorkoutDetailView with section IDs - Auto-scrolls to each spotlight element as tutorial advances - Fixed 'Complete a Set' step to use tutorial_exerciseCard spotlight (removed reference to non-existent tutorial_setCheckmark)
- ProfileView: Added ScrollViewReader with auto-scroll to profile card, PR section, and settings when tutorial highlights them - HomeView: Navigation/tab bar step now scrolls back to top so the tab bar is visible and calendar is shown (not templates) - Added .id() anchors on all ProfileView spotlight targets
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 117 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ### Current File Inventory | ||
|
|
||
| | File | Role | | ||
| |------|------| | ||
| | `DesignTokens.swift` | Color, typography, spacing constants + `CardStyle` modifier | | ||
| | `WorkoutModel.swift` | `Workout`, `WorkoutItem`, `SupersetGroup`, `Exercise`, `ExerciseSet` types + `WorkoutStore` | | ||
| | `WorkoutStatsEngine.swift` | Stats computation: totals, PRs, exercise trends, streaks | | ||
| | `RestTimerManager.swift` | Observable rest timer with auto-start on set completion | | ||
| | `ExerciseCatalog.swift` | Equipment types enum, exercise catalog loader | | ||
| | `MainTabView.swift` | 3-tab `TabView` with frosted glass bar | | ||
| | `HomeView.swift` | Calendar, day workouts, templates, stats | | ||
| | `WorkoutView.swift` | Active workout tracker | | ||
| | `WorkoutDetailView.swift` | Workout/template detail: exercise cards, supersets, edit mode, notes | | ||
| | `WorkoutBuilderView.swift` | Create new workouts | | ||
| | `AllTemplatesView.swift` | Template management: favorites, archive, reorder | | ||
| | `ExerciseCardView.swift` | Exercise card with sets grid + mAI Coach button | | ||
| | `SupersetBlockView.swift` | Superset group block with label, notes, inner exercises | | ||
| | `ExercisePickerView.swift` | Searchable exercise catalog picker | | ||
| | `ExerciseStatsView.swift` | Per-exercise historical stats | | ||
| | `PersonalRecordsView.swift` | All-time PR board | | ||
| | `ConfettiView.swift` | PR celebration confetti animation | | ||
| | `ProfileView.swift` | Account card + settings | | ||
| | `SettingsView.swift` | User preferences | | ||
| | `WelcomeView.swift` | Signed-out landing screen | | ||
| | `RootView.swift` | Boot → welcome or tab view router | | ||
| | `BootScreen.swift` | Animated launch screen | | ||
| | `SignInView.swift` | Email/password auth form | | ||
| | `AuthSession.swift` | Authentication manager | | ||
| | `BenchSessionView.swift` | Live/demo camera coaching session | | ||
| | `mAICoachApp.swift` | App entry, injects `AuthSession` + `WorkoutStore` | |
There was a problem hiding this comment.
The "Current File Inventory" in Section 11 is missing a substantial number of Swift files that exist in the repository. Files like ArchivedTemplatesView.swift, AudioCoach.swift, BenchInferenceEngine.swift, CameraController.swift, CameraPreview.swift, DataBackupManager.swift, DemoPlayerPipeline.swift, ExerciseHistorySheet.swift, PoseLandmarkerService.swift, PoseOverlay.swift, StatsView.swift, TemplateDetailView.swift, TemplatePickerForDayView.swift, TutorialManager.swift, TutorialOverlay.swift, WorkoutHistorySheet.swift, and WorkoutTemplatePickerSheet.swift all exist in App Core/Resources/Swift Code/ but are not listed here. A developer consulting this section as a reference could be misled about the project's actual structure.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 117 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Complete SwiftUI UI overhaul to match the approved prototype.
New Files (7)
Modified Files (5)
Deleted Files (3)
Other