Skip to content

Instantly share code, notes, and snippets.

@harshvishu
Created May 21, 2026 05:01
Show Gist options
  • Select an option

  • Save harshvishu/dccc60a6a75e6e669aa1dcd169942d5f to your computer and use it in GitHub Desktop.

Select an option

Save harshvishu/dccc60a6a75e6e669aa1dcd169942d5f to your computer and use it in GitHub Desktop.
Repository Wiki — generated by GitNexus
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BigRoosterWorkouts — Wiki</title>
<script src="https://cdn.jsdelivr.net/npm/marked@11.0.0/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#ffffff;--sidebar-bg:#f8f9fb;--border:#e5e7eb;
--text:#1e293b;--text-muted:#64748b;--primary:#2563eb;
--primary-soft:#eff6ff;--hover:#f1f5f9;--code-bg:#f1f5f9;
--radius:8px;--shadow:0 1px 3px rgba(0,0,0,.08);
}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
line-height:1.65;color:var(--text);background:var(--bg)}
.layout{display:flex;min-height:100vh}
.sidebar{width:280px;background:var(--sidebar-bg);border-right:1px solid var(--border);
position:fixed;top:0;left:0;bottom:0;overflow-y:auto;padding:24px 16px;
display:flex;flex-direction:column;z-index:10}
.content{margin-left:280px;flex:1;padding:48px 64px;max-width:960px}
.sidebar-header{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border)}
.sidebar-title{font-size:16px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}
.sidebar-title svg{flex-shrink:0}
.sidebar-meta{font-size:11px;color:var(--text-muted);margin-top:6px}
.nav-section{margin-bottom:2px}
.nav-item{display:block;padding:7px 12px;border-radius:var(--radius);cursor:pointer;
font-size:13px;color:var(--text);text-decoration:none;transition:all .15s;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nav-item:hover{background:var(--hover)}
.nav-item.active{background:var(--primary-soft);color:var(--primary);font-weight:600}
.nav-item.overview{font-weight:600;margin-bottom:4px}
.nav-children{padding-left:14px;border-left:1px solid var(--border);margin-left:12px}
.nav-group-label{font-size:11px;font-weight:600;color:var(--text-muted);
text-transform:uppercase;letter-spacing:.5px;padding:12px 12px 4px;user-select:none}
.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);
font-size:11px;color:var(--text-muted);text-align:center}
.content h1{font-size:28px;font-weight:700;margin-bottom:8px;line-height:1.3}
.content h2{font-size:22px;font-weight:600;margin:32px 0 12px;padding-bottom:6px;border-bottom:1px solid var(--border)}
.content h3{font-size:17px;font-weight:600;margin:24px 0 8px}
.content h4{font-size:15px;font-weight:600;margin:20px 0 6px}
.content p{margin:12px 0}
.content ul,.content ol{margin:12px 0 12px 24px}
.content li{margin:4px 0}
.content a{color:var(--primary);text-decoration:none}
.content a:hover{text-decoration:underline}
.content blockquote{border-left:3px solid var(--primary);padding:8px 16px;margin:16px 0;
background:var(--primary-soft);border-radius:0 var(--radius) var(--radius) 0;
color:var(--text-muted);font-size:14px}
.content code{font-family:'SF Mono',Consolas,'Courier New',monospace;font-size:13px;
background:var(--code-bg);padding:2px 6px;border-radius:4px}
.content pre{background:#1e293b;color:#e2e8f0;border-radius:var(--radius);padding:16px;
overflow-x:auto;margin:16px 0}
.content pre code{background:none;padding:0;font-size:13px;line-height:1.6;color:inherit}
.content table{border-collapse:collapse;width:100%;margin:16px 0}
.content th,.content td{border:1px solid var(--border);padding:8px 12px;text-align:left;font-size:14px}
.content th{background:var(--sidebar-bg);font-weight:600}
.content img{max-width:100%;border-radius:var(--radius)}
.content hr{border:none;border-top:1px solid var(--border);margin:32px 0}
.content .mermaid{margin:20px 0;text-align:center}
.menu-toggle{display:none;position:fixed;top:12px;left:12px;z-index:20;
background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
padding:8px 12px;cursor:pointer;font-size:18px;box-shadow:var(--shadow)}
@media(max-width:768px){
.sidebar{transform:translateX(-100%);transition:transform .2s}
.sidebar.open{transform:translateX(0);box-shadow:2px 0 12px rgba(0,0,0,.1)}
.content{margin-left:0;padding:24px 20px;padding-top:56px}
.menu-toggle{display:block}
}
.empty-state{text-align:center;padding:80px 20px;color:var(--text-muted)}
.empty-state h2{font-size:20px;margin-bottom:8px;border:none}
</style>
</head>
<body>
<button class="menu-toggle" id="menu-toggle" aria-label="Toggle menu">&#9776;</button>
<div class="layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
BigRoosterWorkouts
</div>
<div class="sidebar-meta" id="meta-info"></div>
</div>
<div id="nav-tree"></div>
<div class="sidebar-footer">Generated by GitNexus</div>
</nav>
<main class="content" id="content">
<div class="empty-state"><h2>Loading…</h2></div>
</main>
</div>
<script>
var PAGES = {"achievements-badges":"# Achievements & Badges\n\n# Achievements & Badges\n\nThis module owns the app’s achievement system end-to-end:\n\n- detects new achievements from completed workout data\n- persists achievement events in SwiftData\n- exposes catalog metadata for badge screens and profile UI\n- queues home-screen celebrations\n- optionally schedules local notifications\n- renders the badge artwork and celebration overlay used across the app\n\nIt is split across two files:\n\n- `BigRoosterWorkouts/Utilities/AchievementService.swift`\n- `BigRoosterWorkouts/Views/Components/AchievementBadgeViews.swift`\n\n## Core concepts\n\n### `AchievementEvent`\nThe persisted model for an unlocked achievement. `AchievementService.rebuildAchievements(in:focusingOn:)` creates these records from completed `WorkoutSession` data.\n\n### `AchievementDescriptor`\nA lightweight, UI-friendly representation of an achievement. It is used for:\n\n- previewing achievements before persistence\n- converting persisted `AchievementEvent` records into display data\n- scheduling notifications\n- queuing home celebrations\n\n### `AchievementCatalogItem`\nStatic metadata for the badge catalog. Each item defines:\n\n- title/subtitle/detail\n- artwork asset or fallback SF Symbol\n- unlock rule via `AchievementUnlockRule`\n\n### `PendingAchievementCelebration`\nA queued celebration item shown on the home screen. It is created from newly unlocked `AchievementEvent` records and consumed after display.\n\n## High-level flow\n\n```mermaid\nflowchart LR\n A[Completed WorkoutSession] --> B[AchievementService.rebuildAchievements]\n B --> C[AchievementEvent records]\n B --> D[PendingAchievementCelebration queue]\n B --> E[Local notifications]\n C --> F[Profile / Records / Badge catalog UI]\n D --> G[HomeView celebration overlay]\n H[AchievementService.previewAchievements] --> I[UI preview]\n```\n\n## `AchievementService`\n\n`AchievementService` is a `@MainActor` singleton (`AchievementService.shared`) that centralizes achievement generation and lookup.\n\n### Responsibilities\n\n- rebuild all achievements from persisted workout history\n- preview achievements for an in-progress session\n- queue and consume home celebrations\n- schedule local notifications for newly unlocked achievements\n- provide catalog lookup helpers for badge screens\n\n### Important configuration\n\nThe service uses a few internal thresholds:\n\n- `workoutMilestones = [1, 10, 25, 50, 100]`\n- `streakMilestones = [7, 30, 60, 100]`\n- `caloriesPerMinute = 5.0` fallback estimate when no logged calories exist\n\n### `previewAchievements(for:exerciseLog:in:)`\n\nBuilds provisional achievements for a single `ExerciseLog` in the context of a session.\n\nExecution flow:\n\n1. fetch completed sessions from SwiftData\n2. build baseline record values from historical data\n3. run `provisionalAchievements(for:session:baselines:)`\n4. return only the achievements that would be unlocked by the current log\n\nThis is used when the UI wants to show “what you just earned” before the workout is finalized.\n\n### `rebuildAchievements(in:focusingOn:)`\n\nRecomputes the entire achievement history from scratch.\n\nWhat it does:\n\n1. fetches and deletes all existing `AchievementEvent` records\n2. loads all completed `WorkoutSession` records\n3. sorts sessions chronologically\n4. walks each session and generates:\n - exercise personal records\n - workout records\n - workout-count milestones\n - streak milestones\n5. inserts new `AchievementEvent` records\n6. marks only the latest event for each `recordKey` as `isCurrent`\n7. saves the context\n8. optionally returns only the events belonging to `targetSession`\n\nThis method is the canonical source of truth for achievement persistence. It is called after finishing a workout.\n\n#### Record de-duplication and `isCurrent`\n\nThe service tracks the latest event index per `recordKey` while rebuilding. After all events are created, it marks only the most recent event for each key as current. This allows the UI to distinguish the active record from historical superseded records.\n\n### `scheduleNotifications(for:)`\n\nSchedules up to three local notifications for newly unlocked achievements.\n\nBehavior:\n\n- no-op if notifications are disabled in `UserDefaults`\n- no-op if the achievement list is empty\n- requests authorization if notification status is `.notDetermined`\n- schedules one notification per achievement, capped at the first three items\n\nNotification content uses:\n\n- `achievement.title` as the title\n- `achievement.detail` as the body\n- default sound\n\n### `queueHomeCelebrations(for:in:)`\n\nCreates `PendingAchievementCelebration` records for achievements that should be shown on the home screen.\n\nBehavior:\n\n- fetches existing pending celebrations\n- deduplicates by a generated celebration identifier\n- assigns increasing `sortOrder`\n- inserts new pending records and saves the context\n\nThis is typically called after `rebuildAchievements` returns the newly created events for the just-finished workout.\n\n### `consumeHomeCelebration(_:in:)`\n\nDeletes a pending celebration after the home overlay has been dismissed.\n\nThis is the cleanup step for the home celebration queue.\n\n### `descriptor(from:)`\n\nConverts a persisted `AchievementEvent` into an `AchievementDescriptor`.\n\nUse this when a view needs display data but should not depend directly on the persistence model.\n\n### `catalogItems`\n\nStatic badge catalog metadata used by profile and records screens.\n\nThe catalog includes:\n\n- workout milestones\n- streak milestones\n- personal records for:\n - max weight\n - reps at weight\n - estimated one-rep max\n - volume\n - workout duration\n - calories burned\n\nEach item defines an `unlockRule`:\n\n- `.recordKey(String)` for milestone-style achievements\n- `.kind(AchievementKind)` for record categories\n\n### `unlockedCatalogItemIDs(from:)`\n\nReturns the set of catalog item IDs that are unlocked by the provided achievement list.\n\nThis is used by screens that need to show locked/unlocked badge states without duplicating unlock logic.\n\n### `latestAchievement(for:in:)`\n\nReturns the most recent `AchievementEvent` matching a catalog item’s unlock rule.\n\nThis is useful for showing the latest earned instance of a badge in the catalog UI.\n\n## Achievement generation rules\n\n### Exercise achievements\n\n`exerciseAchievements(for:session:maxValues:)` and `exerciseAchievements(for:log:session:maxValues:)` generate personal records from strength sets.\n\nOnly working strength sets are considered:\n\n- `set.type == .strength`\n- excludes `.warmUp`\n- excludes `.dropSet`\n\nGenerated record types:\n\n- `AchievementKind.maxWeight`\n- `AchievementKind.repsAtWeight`\n- `AchievementKind.estimatedOneRepMax`\n- `AchievementKind.volume`\n\n#### Max weight\n\nFor each exercise, the heaviest working set becomes a candidate record if it exceeds the previous best for that exercise.\n\n#### Reps at weight\n\nFor each working set with valid reps and weight, the service tracks the best rep count at a normalized weight key.\n\n#### Estimated 1RM\n\nUses a simple formula:\n\n`weight * (1 + reps / 30)`\n\nThis is stored as a personal record per exercise.\n\n#### Volume\n\nCalculates total working volume for the exercise:\n\n`sum(weight * reps)`\n\n### Workout achievements\n\n`workoutAchievements(for:maxValues:)` generates workout-level records.\n\nGenerated record types:\n\n- `AchievementKind.duration`\n- `AchievementKind.caloriesBurned`\n\n#### Duration\n\nUses `session.duration` and formats it with `formatDuration(_:)`.\n\n#### Calories burned\n\nUses `estimatedCalories(for:)`:\n\n1. sum logged calories from all sets if available\n2. otherwise estimate from duration using `caloriesPerMinute`\n\n### Milestones\n\n#### Workout count milestones\n\n`workoutMilestoneAchievement(count:achievedAt:session:)` unlocks at:\n\n- 1\n- 10\n- 25\n- 50\n- 100\n\nThe first workout uses special copy:\n\n- title: `First Workout Completed`\n- detail: `You logged your first finished workout.`\n\nOther milestones use the count in the title and detail.\n\n#### Streak milestones\n\n`streakAchievement(streak:achievedAt:session:)` unlocks at:\n\n- 7\n- 30\n- 60\n- 100\n\nStreak logic is based on calendar days:\n\n- multiple sessions on the same day do not advance the streak\n- consecutive days increment the streak\n- any gap resets the streak to 1\n\n## Helper methods\n\n### `fetchCompletedSessions(in:)`\n\nFetches non-active `WorkoutSession` records sorted by date ascending.\n\n### `buildBaselines(from:)`\n\nReplays historical sessions to build the current best values for each record key. This is what makes previewing accurate without mutating persisted achievements.\n\n### `provisionalAchievements(for:session:baselines:)`\n\nRuns exercise achievement generation against a copy of the baseline map so the preview can show only newly unlocked records.\n\n### `append(_:to:currentEventIndexByKey:)`\n\nAdds a descriptor to the rebuild list and records the latest index for its `recordKey`.\n\n### `descriptor(...)`\n\nFactory helper used throughout the service to construct `AchievementDescriptor` values consistently.\n\n### Formatting helpers\n\n- `normalizedKeyValue(_:)` formats weights for stable dictionary keys\n- `formatWeight(_:)` formats weights for display\n- `formatDuration(_:)` formats workout duration as `Xh Ym` or `Ym`\n\n### `celebrationIdentifier(for:)`\n\nBuilds a stable identifier for queued celebrations from:\n\n- `recordKey`\n- `achievedAt`\n- `title`\n\nThis is used to prevent duplicate pending celebrations.\n\n### `matches(_:achievement:)`\n\nEvaluates an `AchievementUnlockRule` against an `AchievementEvent`.\n\nUsed by:\n\n- `unlockedCatalogItemIDs(from:)`\n- `latestAchievement(for:in:)`\n\n## UI components\n\n## `AchievementBadgeArtworkView`\n\nA reusable badge image view that renders either:\n\n1. a named asset image if it exists in the asset catalog\n2. a styled fallback badge with an SF Symbol\n\n### Inputs\n\n- `assetName: String?`\n- `placeholderSymbolName: String`\n- `size: CGFloat = 52`\n\n### Behavior\n\n- checks `UIImage(named:)` to determine whether the asset exists\n- uses a rounded gradient tile as the fallback background\n- clips to a rounded rectangle matching the badge shape\n\nThis view is used throughout the app wherever badge artwork appears, including:\n\n- profile dashboard\n- records history\n- home celebrations\n- badge catalog cards\n\n## `AchievementCelebrationOverlayView`\n\nA full-screen overlay shown on the home screen when a pending celebration is active.\n\n### Inputs\n\n- `celebration: PendingAchievementCelebration`\n- `onDismiss: () -> Void`\n\n### Behavior\n\n- draws a scrim over the entire screen\n- shows a large badge artwork card\n- animates confetti on appear and when the celebration changes\n- supports drag-based parallax tilt on the hero badge\n- dismisses when the background or button is tapped\n\n### Internal state\n\n- `heroTilt: CGSize`\n- `animateConfetti: Bool`\n\n### Interaction details\n\nThe badge card uses a `DragGesture(minimumDistance: 0)` to create a subtle 3D tilt effect. The tilt is clamped to a small range using `clamp(_:min:max:)` so the interaction stays controlled.\n\n### `ConfettiBurstView`\n\nA private helper view that renders the confetti particles.\n\nIt animates a small set of `ConfettiParticle` values, each with:\n\n- color\n- offset\n- size\n- rotation\n- delay\n\nThe particles are intentionally few and lightweight to keep the effect simple and predictable.\n\n## How the module connects to the rest of the app\n\n### Workout completion\n\n`WorkoutSessionView.finishWorkout` calls:\n\n1. `AchievementService.rebuildAchievements(in:focusingOn:)`\n2. `AchievementService.queueHomeCelebrations(for:in:)`\n3. `AchievementService.scheduleNotifications(for:)`\n\nThis is the main entry point for achievement creation.\n\n### Home screen\n\n`HomeView` displays `AchievementCelebrationOverlayView` for pending celebrations and calls `consumeHomeCelebration(_:in:)` when the user dismisses one.\n\n### Profile and records screens\n\n`ProfileView`, `SettingsView`, and `RecordsHistoryView` use:\n\n- `unlockedCatalogItemIDs(from:)`\n- `latestAchievement(for:in:)`\n\nto render badge states and highlight the latest earned record.\n\n### Badge artwork reuse\n\n`AchievementBadgeArtworkView` is shared by:\n\n- `HomeView`\n- `ProfileDashboardViews`\n- `RecordsHistoryView`\n- badge catalog cards\n\nThis keeps badge rendering consistent across the app.\n\n## Implementation notes\n\n- `AchievementService` is `@MainActor`, so all persistence and notification orchestration happens on the main actor.\n- Achievement generation is replay-based: rebuilding from completed sessions ensures consistency even if the underlying rules change.\n- `recordKey` is the primary identity for “same achievement type over time.”\n- `kind` is used for catalog unlock matching, while `recordKey` is used for milestone-specific unlocks.\n- The service intentionally separates:\n - persistence (`AchievementEvent`)\n - display data (`AchievementDescriptor`)\n - catalog metadata (`AchievementCatalogItem`)\n\nThis separation keeps the UI flexible and makes it easier to evolve achievement rules without rewriting presentation code.","app-entry-root-navigation":"# App Entry & Root Navigation\n\n# App Entry & Root Navigation\n\nThis module defines the application entry point and the top-level navigation shell for BigRoosterWorkouts. It is responsible for:\n\n- bootstrapping the SwiftUI app\n- creating and sharing the SwiftData store\n- injecting app-wide managers into the environment\n- configuring TipKit\n- seeding exercise data on launch\n- routing all navigation and sheet presentation from a single root view\n\nThe module is split across two files:\n\n- `BigRoosterWorkoutsApp.swift` — app lifecycle, persistence setup, and launch-time initialization\n- `ContentView.swift` — root navigation container and destination/sheet routing\n\n---\n\n## High-level flow\n\n1. `BigRoosterWorkoutsApp` launches as the `@main` app.\n2. It creates a shared `ModelContainer` backed by an App Group SQLite store.\n3. It presents `ContentView` inside a `WindowGroup`.\n4. `ContentView` creates an `AppRouter` and binds it to a `NavigationStack` and sheet presentation.\n5. On first appearance, the app:\n - configures TipKit\n - imports seed exercise data into SwiftData if needed\n6. Navigation is driven by `Destination` and `Sheet` values stored in `AppRouter`.\n\n```mermaid\nflowchart TD\n A[BigRoosterWorkoutsApp] --> B[ModelContainer]\n A --> C[ContentView]\n C --> D[AppRouter]\n C --> E[NavigationStack]\n C --> F[sheet(item:)]\n C --> G[HomeView]\n E --> H[Destination routing]\n F --> I[Sheet routing]\n A --> J[Tips.configure()]\n A --> K[importExercisesIfNeeded()]\n K --> L[SeedDataImporter.importData(into:)]\n```\n\n---\n\n## `BigRoosterWorkoutsApp`\n\n```swift\n@main\nstruct BigRoosterWorkoutsApp: App\n```\n\nThis is the application entry point. It owns the shared persistence container and wires global dependencies into the root scene.\n\n### Responsibilities\n\n- define the SwiftData schema used by the app\n- create a persistent store in the App Group container\n- recover from a corrupted store in debug builds\n- inject shared managers into the SwiftUI environment\n- trigger one-time launch work\n\n---\n\n## Shared state and managers\n\nThe app keeps three long-lived objects as `@State`:\n\n- `themeManager = ThemeManager.shared`\n- `homeBackgroundManager = HomeBackgroundManager.shared`\n- `healthKitManager = HealthKitManager()`\n\nThese are injected into the root view hierarchy so downstream views can access them via `@Environment`.\n\n### Why this matters\n\nThis module establishes the app-wide dependency graph. Any view below `ContentView` can rely on these managers being present without constructing them locally.\n\n---\n\n## SwiftData container setup\n\n`sharedModelContainer` is created as a stored property on the app struct.\n\n### Schema\n\nThe schema includes the core persisted models used across workouts, templates, achievements, and user profile data:\n\n- `Exercise`\n- `ExerciseSearchIndex`\n- `ExerciseTemplate`\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `AchievementEvent`\n- `PendingAchievementCelebration`\n- `UserProfile`\n- `WorkoutTemplate`\n- `WorkoutPlan`\n- `UserWorkoutPlan`\n\n### Storage location\n\nThe store is not placed in the default app sandbox location. Instead, it uses an App Group container:\n\n```swift\nlet appGroupIdentifier = \"group.com.vishwakarma.BigRoosterWorkouts\"\n```\n\nThe SQLite file is stored at:\n\n```swift\nBigRoosterWorkouts.sqlite\n```\n\ninside the shared App Group directory.\n\n### Why App Group storage is used\n\nThis allows the main app and extensions/widgets to access the same persistent store. The comment in the code explicitly notes shared access with widgets.\n\n### Failure handling\n\nIf the App Group container cannot be resolved, the app aborts immediately:\n\n```swift\nfatalError(\"App Group container not accessible\")\n```\n\nIf `ModelContainer` creation fails:\n\n- in `DEBUG`, the app attempts to delete the existing store and recreate it\n- in non-debug builds, the app aborts with a fatal error\n\nThis is a pragmatic recovery path for local development when schema changes or store corruption make the existing SQLite file unusable.\n\n---\n\n## Debug store recovery\n\nIn `DEBUG`, the app can remove the main SQLite file and its WAL/SHM sidecar files:\n\n- `BigRoosterWorkouts.sqlite`\n- `BigRoosterWorkouts.sqlite-shm`\n- `BigRoosterWorkouts.sqlite-wal`\n\nThis is handled by:\n\n```swift\nprivate static func deleteStoreIfPresent(at storeURL: URL) throws\n```\n\n### Behavior\n\nThe method checks each file and removes it if present. This ensures the store is fully reset before retrying container creation.\n\n### Important detail\n\nThis recovery path is only compiled in debug builds. Production builds do not attempt destructive recovery and instead fail fast if the store cannot be created.\n\n---\n\n## Root scene configuration\n\nThe app body defines a single `WindowGroup`:\n\n```swift\nWindowGroup {\n ContentView()\n .environment(themeManager)\n .environment(homeBackgroundManager)\n .environment(healthKitManager)\n .task { ... }\n}\n.modelContainer(sharedModelContainer)\n```\n\n### Environment injection\n\nThe root view receives:\n\n- `ThemeManager`\n- `HomeBackgroundManager`\n- `HealthKitManager`\n\nThis makes them available throughout the navigation tree.\n\n### Model container injection\n\n`.modelContainer(sharedModelContainer)` attaches the shared SwiftData container to the entire scene. All descendant views and models use the same persistent context unless they explicitly override it.\n\n---\n\n## Launch-time task\n\nThe root view runs a `.task` modifier that performs two startup actions:\n\n1. `try? Tips.configure()`\n2. `await importExercisesIfNeeded()`\n\n### TipKit configuration\n\n`Tips.configure()` is called opportunistically and ignored on failure. This keeps TipKit setup from blocking app launch.\n\n### Seed data import\n\n`importExercisesIfNeeded()` is called after the view appears. It uses the shared model container’s `mainContext` and delegates to `SeedDataImporter.importData(into:)`.\n\nThis launch-time import is the bridge between app startup and the data seeding pipeline.\n\n---\n\n## `importExercisesIfNeeded()`\n\n```swift\n@MainActor\nprivate func importExercisesIfNeeded() async\n```\n\nThis helper performs launch-time data import into the main SwiftData context.\n\n### Execution\n\n- obtains `sharedModelContainer.mainContext`\n- calls `SeedDataImporter.importData(into:)`\n- logs failures to the console\n\n### Error handling\n\nErrors are caught and printed:\n\n```swift\nprint(\"❌ Failed to import data: \\(error)\")\n```\n\nThe app does not crash if seed import fails. This is intentional: the UI should still launch even if data seeding is incomplete.\n\n### Main actor isolation\n\nThe method is annotated `@MainActor`, which matches its use of the main SwiftData context and keeps persistence work aligned with UI lifecycle expectations.\n\n---\n\n## `ContentView`\n\n```swift\nstruct ContentView: View\n```\n\n`ContentView` is the root navigation shell for the app. It owns the router and maps navigation state to concrete screens.\n\n### Responsibilities\n\n- host the `NavigationStack`\n- bind navigation path state to `AppRouter`\n- present sheets from router state\n- map `Destination` values to destination views\n- map `Sheet` values to sheet content\n- apply theme synchronization and preferred color scheme\n\n---\n\n## Router-driven navigation\n\n`ContentView` creates a local router:\n\n```swift\n@State private var router = AppRouter()\n```\n\nThe router is injected into the environment:\n\n```swift\n.environment(router)\n```\n\nThis pattern centralizes navigation state while still allowing child views to push destinations or present sheets through the shared router.\n\n### Navigation stack\n\nThe root stack is declared as:\n\n```swift\nNavigationStack(path: $router.path)\n```\n\nThe stack starts at `HomeView()` and resolves pushed destinations using:\n\n```swift\n.navigationDestination(for: Destination.self) { destination in ... }\n```\n\n### Sheet presentation\n\nSheets are driven by:\n\n```swift\n.sheet(item: $router.presentedSheet) { sheet in ... }\n```\n\nThis means sheet presentation is also state-based and controlled by the router.\n\n---\n\n## Destination routing\n\n`destinationView(for:)` maps `Destination` enum cases to concrete views.\n\n### Supported destinations\n\nThe module routes to screens such as:\n\n- `ProfileView`\n- `SettingsView`\n- `WorkoutSessionView`\n- `WorkoutHistoryView`\n- `RecordsHistoryView`\n- `WorkoutDetailView`\n- `DetailedStatsView`\n- `TemplateListView`\n- `WorkoutPlansView`\n- `WorkoutPlanDetailView`\n- `ExerciseGuideView`\n- `CustomExercisesView`\n- `ThemeListView`\n- `HomeBackgroundEditorView`\n- `CreditsView`\n- `ThemeEditorView`\n- `BackgroundBuilderView`\n- `DesignSystemShowcaseView`\n- `CustomExerciseDetailView`\n\n### Special cases\n\nA few destinations currently resolve to placeholder text views:\n\n- `.exercisePicker` → `Text(\"Exercise Picker View\")`\n\nThis indicates the route exists in the navigation model, but the destination UI is not yet implemented in this module.\n\n### Reused views\n\nSome destinations intentionally reuse the same view type for different navigation intents:\n\n- `.workoutSession(let session)` → `WorkoutSessionView(session: session)`\n- `.workoutEdit(let session)` → `WorkoutSessionView(session: session)`\n\nThis suggests the same editor/view can serve both session creation and session editing flows.\n\n---\n\n## Sheet routing\n\n`sheetView(for:)` maps `Sheet` enum cases to modal content.\n\n### Supported sheets\n\n- `.editProfile` → `EditProfileSheetContainer`\n- `.workoutStart` → `WorkoutStartSheet`\n- `.templateEditor` → `WorkoutTemplateBuilderView`\n- `.exerciseBuilder` → `ExerciseTemplateBuilderView`\n- `.workoutPlanBuilder` → `WorkoutPlanBuilderView`\n- `.share(let url)` → `ShareSheet(items: [url])`\n\n### Placeholder sheets\n\nLike the destination router, some sheet cases currently render placeholder text:\n\n- `.exercisePicker` → `Text(\"Exercise Picker Sheet\")`\n- `.themePicker` → `Text(\"Theme Picker Sheet\")`\n\nThese placeholders preserve routing structure while the UI is still pending or intentionally deferred.\n\n---\n\n## Theme integration\n\n`ContentView` reads `ThemeManager` from the environment:\n\n```swift\n@Environment(ThemeManager.self) private var themeManager\n```\n\nIt then applies:\n\n```swift\n.syncThemeWithColorScheme()\n.preferredColorScheme(themeManager.preferredColorScheme)\n```\n\n### What this does\n\n- `syncThemeWithColorScheme()` keeps the app’s theme state aligned with the system color scheme\n- `preferredColorScheme(...)` forces the UI into the theme manager’s chosen appearance\n\nThis module is the point where theme state becomes visible at the root of the UI tree.\n\n---\n\n## Debug navigation logging\n\nIn `DEBUG`, `ContentView` logs navigation changes for easier troubleshooting.\n\n### Path logging\n\n```swift\n.onChange(of: router.path) { oldValue, newValue in\n debugLogNavigationPathChange(old: oldValue, new: newValue)\n}\n```\n\nThis prints a readable before/after representation of the navigation stack.\n\n### Sheet logging\n\n```swift\n.onChange(of: router.presentedSheet) { oldValue, newValue in\n print(\"[NavigationDebug] sheet changed: ...\")\n}\n```\n\nThis helps diagnose unexpected modal presentation changes.\n\n### `debugLogNavigationPathChange(old:new:)`\n\nThis helper formats the path as a chain of destination names and identifiers.\n\n### `debugDestination(_:)`\n\nThis helper converts each `Destination` case into a concise string, including identifiers where useful:\n\n- session IDs\n- plan IDs\n- exercise IDs\n- theme IDs\n- optional dates\n\nThis makes debug output much more actionable than printing raw enum values.\n\n---\n\n## Preview support\n\nThe `#Preview` block creates a self-contained preview environment:\n\n- uses an in-memory SwiftData container\n- includes the core models needed by the root UI\n- injects `HomeBackgroundManager.shared`\n\nThis allows `ContentView` to render without the production App Group store.\n\n---\n\n## Relationship to the rest of the codebase\n\nThis module sits at the top of the app architecture and connects several subsystems:\n\n### Persistence\n\n- defines the canonical SwiftData schema\n- provides the shared `ModelContainer`\n- seeds data through `SeedDataImporter`\n\n### Navigation\n\n- depends on `AppRouter`\n- routes to many feature views through `Destination` and `Sheet`\n\n### App-wide services\n\n- `ThemeManager`\n- `HomeBackgroundManager`\n- `HealthKitManager`\n\n### Launch-time utilities\n\n- `TipKit`\n- `SeedDataImporter`\n\n### Feature entry points\n\nThe root router exposes the main feature surfaces of the app, including:\n\n- profile and settings\n- workout sessions and history\n- workout plans and templates\n- exercise browsing and detail views\n- theme and background customization\n- credits and design system showcase\n\n---\n\n## Implementation notes\n\n### State ownership\n\n`BigRoosterWorkoutsApp` owns long-lived app services and persistence. `ContentView` owns navigation state. This separation keeps lifecycle concerns clear:\n\n- app-level resources live in the app struct\n- UI routing lives in the root view\n\n### Single source of truth for navigation\n\nAll navigation and modal presentation is driven by `AppRouter`. Child views should mutate router state rather than presenting screens directly.\n\n### Store reset behavior is development-only\n\nThe automatic store deletion path is intentionally limited to debug builds. If schema changes break the store in production, the app will fail fast rather than silently deleting user data.\n\n### Seed import is non-blocking\n\nSeed import errors are logged but not surfaced as fatal launch failures. This keeps the app usable even if the import pipeline has issues.\n\n---\n\n## Key extension points\n\nWhen adding new top-level screens or modals:\n\n1. add a new case to `Destination` or `Sheet`\n2. update `destinationView(for:)` or `sheetView(for:)`\n3. ensure the target view can access required environment objects\n4. if the screen needs persisted data, confirm the model is included in `sharedModelContainer`’s schema\n\nWhen adding new persisted models:\n\n1. include the model in the schema array in `BigRoosterWorkoutsApp`\n2. verify the model is compatible with the App Group store\n3. update any seed/import logic if the model needs initial data\n\nWhen changing startup behavior:\n\n1. keep `Tips.configure()` and seed import inside the root `.task` unless there is a strong reason to move them\n2. preserve the non-fatal error handling for launch-time import\n3. be careful with debug-only store deletion, since it affects local development workflows\n\n---\n\n## Summary\n\nThis module is the app’s bootstrap and navigation hub. It establishes the shared persistence layer, injects global services, initializes launch-time dependencies, and routes the entire UI through a single router-backed `NavigationStack` and sheet presenter. If you need to understand how the app starts, where the database comes from, or how screens are reached, this is the place to begin.","core-domain-models":"# Core Domain Models\n\n# Core Domain Models\n\nThis module defines the app’s primary data model layer: workout content, session tracking, theming, background configuration, muscle anatomy metadata, widget payloads, and achievement/event records. Most types are either `@Model` SwiftData entities or `Codable` value types used for persistence, export/import, widgets, and UI configuration.\n\nThe module is intentionally split between:\n\n- **Persistent domain entities** backed by SwiftData (`@Model`)\n- **Serializable configuration/value objects** used for theme export, widget data, and background presets\n- **Derived helpers and enums** that normalize raw stored strings into typed app concepts\n\n## Responsibilities\n\n- Persist workout sessions, exercises, templates, plans, logs, and profile data\n- Represent theme palettes and background composition in a Codable format\n- Provide typed wrappers around raw string storage for enums and relationships\n- Support widget and Live Activity data transfer\n- Map muscle names to regions and illustration assets\n- Track achievements and queued celebrations\n\n## High-level model groups\n\n```mermaid\nflowchart LR\n A[Workout Content] --> B[Exercise]\n A --> C[WorkoutTemplate]\n A --> D[WorkoutPlan]\n A --> E[WorkoutSession]\n\n E --> F[ExerciseLog]\n F --> G[SetLog]\n\n H[Theme & Background] --> I[AppTheme]\n H --> J[ThemeColorSet]\n H --> K[BackgroundConfiguration]\n H --> L[HomeBackgroundConfiguration]\n\n M[Anatomy] --> N[MuscleRegion]\n M --> O[TargetMuscle]\n\n P[Insights & Widgets] --> Q[WidgetData]\n P --> R[WorkoutStats]\n P --> S[WorkoutDayData]\n\n T[Achievements] --> U[AchievementEvent]\n T --> V[PendingAchievementCelebration]\n```\n\n---\n\n## Workout content models\n\n### `Exercise`\n\n`Exercise` is the core catalog entity for a workout movement. It stores descriptive metadata, muscle targeting, difficulty, rest defaults, and the logging mode used when recording sets.\n\n#### Key properties\n\n- `name`, `equipment`, `variation`, `utility`, `mechanics`, `force`\n- `preparation`, `execution`\n- `targetMuscles`, `synergistMuscles`, `stabilizerMuscles`, `antagonistMuscles`, `dynamicStabilizerMuscles`\n- `mainMuscle`, `secondaryMuscles`\n- `difficulty`\n- `parentId` for lineage/import mapping\n- `isStretch`\n- `defaultRestTime`\n- `loggingModeRawValue` with typed access via `loggingMode`\n\n#### `ExerciseLoggingMode`\n\n`ExerciseLoggingMode` controls which fields are valid when logging a set:\n\n- `.weightAndReps`\n- `.repsOnly`\n- `.durationAndReps`\n- `.durationOnly`\n\nConvenience flags:\n\n- `allowsWeight`\n- `allowsDuration`\n- `allowsReps`\n\nThese are used by workout session logic to validate input and determine which fields should be shown or persisted.\n\n#### Derived anatomy helpers\n\nThe `Exercise` extension provides muscle-resolution helpers:\n\n- `mainMuscleRegion`\n- `mainMuscleIllustrationGroup`\n- `resolvedTargetMuscles`\n- `resolvedTargetRegions`\n- `preferredMuscleIllustrationAssetName(view:)`\n\nThese helpers normalize raw comma-separated muscle strings into typed anatomy concepts and choose the best illustration asset for UI display.\n\n#### Muscle string normalization\n\nThe private `String.normalizedMuscleToken` helper lowercases and strips punctuation so values like `\"Pectoralis Major Sternocostal\"` or `\"hip abductors\"` can be matched consistently.\n\n---\n\n### `ExerciseTemplate`\n\n`ExerciseTemplate` is a persisted template record for reusable exercise definitions. It overlaps with `Exercise`, but adds ownership/source metadata for local, imported, shared, and cloud-synced content.\n\n#### Important fields\n\n- Exercise metadata similar to `Exercise`\n- `isCustom`\n- `createdAt`\n- `sourceExerciseId`\n- `defaultRestTime`\n- `ownershipRawValue` / `ownership`\n- `sourceRawValue` / `source`\n- `localContentID`\n- source tracking fields:\n - `sourceCatalogExerciseID`\n - `sourceCatalogExerciseName`\n - `sourceWorkoutPlanID`\n - `sourceUserID`\n - `cloudRecordID`\n\n#### Behavior\n\n- `ownership` and `source` are computed wrappers around raw stored strings.\n- `resolvedLocalContentID` falls back to `name` when no local ID exists.\n- `convenience init(from exercise: Exercise)` converts a catalog exercise into a template, preserving source metadata and marking it as imported.\n\nThis model is the bridge between the exercise catalog and user-authored or imported content.\n\n---\n\n### `WorkoutTemplate` and `TemplateExerciseDefinition`\n\n`WorkoutTemplate` stores a reusable workout structure. It can be backed by either:\n\n- legacy `exerciseIds`\n- structured `templateExercisesData`\n\n`templateExercises` resolves to structured definitions when available, otherwise it synthesizes definitions from `exerciseIds` using `defaultSets` and `defaultReps`.\n\n#### `TemplateExerciseDefinition`\n\nA codable, hashable, identifiable value type describing one exercise in a template:\n\n- `id`\n- `exerciseName`\n- `sets`\n- `reps`\n- optional superset metadata:\n - `supersetGroupID`\n - `supersetOrder`\n - `supersetRestTimeOverride`\n\nThis type is used by template builders and persistence tests to ensure structured templates survive round-trips.\n\n---\n\n### `WorkoutPlan`, `WorkoutPlanDayDefinition`, `WorkoutPlanExerciseDefinition`\n\n`WorkoutPlan` represents a structured training plan, typically imported or seeded content.\n\n#### `WorkoutPlan`\n\nKey fields include:\n\n- `planID` marked `@Attribute(.unique)`\n- descriptive metadata:\n - `sourceURL`, `title`, `summary`, `mainGoal`, `workoutType`, `trainingLevel`\n- schedule metadata:\n - `durationText`, `durationWeeks`\n - `daysPerWeekText`, `daysPerWeek`\n - `timePerWorkoutText`, `timePerWorkoutMinutesMinimum`, `timePerWorkoutMinutesMaximum`\n- equipment and audience:\n - `equipmentText`, `equipmentTags`, `targetGender`\n- `days`: `[WorkoutPlanDayDefinition]`\n- provenance fields:\n - ownership/source/local/cloud identifiers\n\nComputed helpers:\n\n- `ownership`\n- `source`\n- `resolvedLocalContentID`\n- `totalDayCount`\n- `workoutDayCount`\n- `restDayCount`\n\n#### `WorkoutPlanDayDefinition`\n\nRepresents one day in a plan:\n\n- `id`\n- `dayIndex`\n- `displayName`\n- `isRestDay`\n- `exercises`\n\n#### `WorkoutPlanExerciseDefinition`\n\nRepresents one exercise prescription inside a plan day:\n\n- `id`\n- `displayName`\n- optional source/catalog matching metadata\n- `setsText`, `repsText`\n- `sortOrder`\n- `notes`\n- `isSuperset`\n- `isCardio`\n\n`prescriptionSummary` joins `setsText` and `repsText` into a compact display string.\n\n---\n\n### `UserWorkoutPlan`\n\n`UserWorkoutPlan` tracks a user’s enrollment in a `WorkoutPlan`.\n\n#### Key fields\n\n- `enrollmentID` marked unique\n- `planID`\n- scheduling state:\n - `startedAt`\n - `nextScheduledDayIndex`\n - `nextScheduledDate`\n - `lastCompletedWorkoutDate`\n- lifecycle state:\n - `statusRawValue` / `status`\n - `completedAt`\n - `endedAt`\n- progress tracking:\n - `lastCompletedDayIndex`\n - `completedDayIndices`\n - `skippedDayIndices`\n\nThis model is the mutable runtime state for a plan the user is actively following.\n\n---\n\n### `WorkoutSession`\n\n`WorkoutSession` is the central runtime entity for a workout in progress or completed.\n\n#### Core fields\n\n- `date`\n- `startTime`\n- `endTime`\n- `isActive`\n- `lastOpenedExerciseOrder`\n- source plan metadata:\n - `sourceWorkoutPlanEnrollmentID`\n - `sourceWorkoutPlanID`\n - `sourceWorkoutPlanDayIndex`\n - `sourceWorkoutPlanTitle`\n - `sourceWorkoutPlanDayTitle`\n - completion counts and threshold metadata\n- notes and tags:\n - `sessionNotes`\n - `gymLocation`\n - `workoutTagsRawValue`\n - `muscleGroupsRawValue`\n- rest timer state:\n - `restTimerEndTime`\n - `isRestTimerActive`\n - `isRestTimerPaused`\n - `restTimerDuration`\n - `restTimerPausedRemaining`\n\n#### Relationships\n\n- `exerciseLogs: [ExerciseLog]`\n - cascade deletes to `ExerciseLog.workoutSession`\n\n#### Computed properties\n\n- `duration`\n - uses `endTime` when available, otherwise computes elapsed time from `startTime`\n- `workoutTags`\n- `muscleGroups`\n\nThe tag/group arrays are stored as newline-delimited strings and normalized through private helpers:\n\n- `decodeList(_:)`\n- `encodeList(_:)`\n\n`encodeList(_:)` trims whitespace, removes empties, deduplicates via `Set`, and sorts case-insensitively before joining with `\\n`.\n\nThis makes the model resilient to repeated edits and stable across persistence round-trips.\n\n---\n\n### `ExerciseLog`\n\n`ExerciseLog` stores one exercise entry inside a workout session.\n\n#### Fields\n\n- `exerciseName`\n- `exerciseId`\n- `order`\n- `notes`\n- planned prescription text:\n - `plannedSetsText`\n - `plannedRepsText`\n- `isSkipped`\n- rest overrides:\n - `restTimeOverride`\n - `supersetRestTimeOverride`\n- superset metadata:\n - `supersetGroupID`\n - `supersetOrder`\n- `workoutSession`\n- `sets: [SetLog]`\n\n#### Computed properties\n\n- `isInSuperset`\n- `performedSetCount`\n\n`sets` cascades to `SetLog.exerciseLog`, so deleting an exercise log removes its sets.\n\n---\n\n### `SetLog`\n\n`SetLog` stores one performed set.\n\n#### Types\n\n- `SetType`\n - `.strength`\n - `.cardio`\n- `SetTag`\n - `.none`\n - `.warmUp`\n - `.dropSet`\n - `.failure`\n\n`SetTag.title` maps the stored tag to UI labels such as `\"Working\"` or `\"Warm-up\"`.\n\n#### Fields\n\nStrength-specific:\n\n- `weight`\n- `reps`\n- `restTime`\n\nCardio-specific:\n\n- `duration`\n- `distance`\n- `calories`\n\nOther fields:\n\n- `setNumber`\n- `type`\n- `tagRawValue` / `tag`\n- `completedAt`\n- `exerciseLog`\n\n`tag` is stored as an optional raw string but exposed as a typed enum with `.none` fallback.\n\n---\n\n### `WorkoutContentOwnership` and `WorkoutContentSource`\n\nThese enums classify content provenance.\n\n#### `WorkoutContentOwnership`\n\n- `.seeded`\n- `.localUser`\n- `.sharedUser`\n- `.cloudUser`\n\n#### `WorkoutContentSource`\n\n- `.seed`\n- `.scratch`\n- `.catalogImport`\n- `.userPlan`\n- `.sharedImport`\n- `.cloudSync`\n\nThey are used by `ExerciseTemplate` and `WorkoutPlan` to distinguish seeded content from user-created or synced content.\n\n---\n\n## Theme and background models\n\n### `ThemeColorSet`\n\n`ThemeColorSet` is a codable palette definition for app UI colors.\n\nIt stores hex strings for:\n\n- backgrounds\n- surfaces\n- text\n- accents\n- semantic colors\n- borders/dividers\n- shadows\n\nIt also exposes computed `Color` values such as:\n\n- `backgroundPrimaryColor`\n- `surfacePrimaryColor`\n- `textPrimaryColor`\n- `accentPrimaryColor`\n- `successColor`\n- `shadowColor`\n\n#### Built-in palettes\n\nThe module includes predefined light/dark variants for:\n\n- `defaultLight` / `defaultDark`\n- `oceanLight` / `oceanDark`\n- `forestLight` / `forestDark`\n- `sunsetLight` / `sunsetDark`\n- `monochromeLight` / `monochromeDark`\n- `highContrastLight` / `highContrastDark`\n\n#### `Color.init(hex:)`\n\nA `Color` extension parses:\n\n- 3-digit RGB\n- 6-digit RGB\n- 8-digit ARGB\n\nThis is the foundation for theme rendering throughout the app.\n\n---\n\n### `AppTheme`\n\n`AppTheme` is the top-level theme object used by the app.\n\n#### Properties\n\n- `id`\n- `name`\n- `isCustom`\n- `createdAt`\n- `parentThemeId`\n- `lightColors`\n- `darkColors`\n- `backgroundConfig`\n- `usesSystemDarkMode`\n\n#### Behavior\n\n- `copy()` creates a new custom theme with a fresh UUID and `\" Copy\"` suffix.\n- `applyInheritance(from:)` copies the parent’s colors and background config into the child theme.\n\n#### Predefined themes\n\nThe `@MainActor` extension defines:\n\n- `defaultLight`\n- `defaultDark`\n- `ocean`\n- `forest`\n- `sunset`\n- `monochrome`\n- `highContrast`\n\n`allPredefined` returns the full list.\n\n#### Export/import\n\n`ThemeExport` wraps an `AppTheme` with a version string:\n\n- `version`\n- `theme`\n\n`ThemeExport.currentVersion` is `\"1.0\"`.\n\nThis is the module’s stable serialization format for theme sharing.\n\n---\n\n### `BackgroundConfiguration`\n\n`BackgroundConfiguration` describes how a theme background should be rendered.\n\n#### `BackgroundType`\n\n- `.solidColor`\n- `.gradient`\n- `.animatedGradient`\n- `.layerStack`\n- `.customImage`\n\n#### Stored configuration\n\nOnly one mode is typically active at a time, but the struct includes optional payloads for each mode:\n\n- `solidColor`\n- `gradientConfig`\n- `animatedGradientConfig`\n- `layerStack`\n- `customImagePath`\n\n#### Defaults and previews\n\n- `BackgroundConfiguration.default` is a white solid background.\n- `sampleGradient` and `sampleLayerStack` provide preview-friendly examples.\n\n#### Related types\n\n- `GradientConfiguration`\n- `AnimatedGradientConfiguration`\n- `LayerStackConfiguration`\n- `CanvasLayer`\n- `LayerType`\n- `ShapeLayer`\n- `GradientLayer`\n- `ImageLayer`\n- `TextLayer`\n- `FillType`\n- `StrokeConfiguration`\n\nThese types support a composable background editor.\n\n---\n\n### `GradientConfiguration`\n\nDefines a gradient:\n\n- `type`: `.linear`, `.radial`, `.angular`\n- `startPoint`, `endPoint`, `centerPoint` as `UnitPointData`\n- `stops`: array of `GradientStop`\n\n`GradientConfiguration.default` is a blue-to-purple linear gradient.\n\n---\n\n### `AnimatedGradientConfiguration`\n\nWraps a base gradient with animation settings:\n\n- `baseGradient`\n- `animationSpeed`\n- `animationDirection`\n- `easing`\n\nAnimation options are intentionally constrained to a small set of enum values for predictable rendering.\n\n---\n\n### `LayerStackConfiguration`\n\nRepresents a layered canvas background.\n\n- `layers: [CanvasLayer]`\n- `canvasSize`\n\n`maxLayers` is `3`, which matches the editor’s intended complexity limit.\n\n`CanvasSize.iPhone14ProMax` is the default canvas reference size.\n\n---\n\n### `CanvasLayer`\n\nA single layer in a layer stack.\n\nFields:\n\n- `id`\n- `name`\n- `type`\n- `position`\n- `size`\n- `rotation`\n- `scale`\n- `opacity`\n- `blendMode`\n- `zIndex`\n\nThis is the atomic unit for custom layered backgrounds.\n\n---\n\n### `LayerType` and custom Codable encoding\n\n`LayerType` is an enum with associated values:\n\n- `.shape(ShapeLayer)`\n- `.gradient(GradientLayer)`\n- `.image(ImageLayer)`\n- `.text(TextLayer)`\n\nBecause associated-value enums do not synthesize `Codable` cleanly in the desired format, the module implements custom encoding/decoding using:\n\n- `type`\n- `data`\n\nUnknown types throw a `DecodingError.dataCorruptedError`.\n\nThis format is used by the background builder and any persisted custom layer stacks.\n\n---\n\n### Supporting background types\n\n#### `ShapeLayer`\n\nDescribes a shape layer:\n\n- `shape`\n- `fill`\n- `stroke`\n- `cornerRadius`\n- `starPoints`\n- `polygonSides`\n- `blur`\n\n#### `GradientLayer`\n\n- `gradientType`\n- `gradientConfig`\n\n#### `ImageLayer`\n\n- `imagePath`\n- `contentMode`\n\n#### `TextLayer`\n\n- `text`\n- `fontSize`\n- `fontWeight`\n- `color`\n- `alignment`\n\n#### `FillType`\n\n- `mode`: `.solid` or `.gradient`\n- `solidColor`\n- `gradientConfig`\n\n#### `StrokeConfiguration`\n\n- `color`\n- `width`\n- `style`: `.solid`, `.dashed`, `.dotted`\n\n#### Helper geometry types\n\n- `UnitPointData`\n- `Position`\n- `Size`\n- `Scale`\n\n`UnitPointData.unitPoint` converts stored coordinates into SwiftUI `UnitPoint`.\n\n#### `BlendModeData`\n\nMaps stored blend mode strings to SwiftUI `BlendMode` values:\n\n- `.normal`\n- `.multiply`\n- `.screen`\n- `.overlay`\n- `.softLight`\n- `.hardLight`\n- `.colorDodge`\n- `.colorBurn`\n- `.difference`\n- `.exclusion`\n\n---\n\n### `HomeBackgroundConfiguration`\n\nThis model controls the Home screen background experience.\n\n#### `HomeBackgroundConfiguration`\n\nFields:\n\n- `isEnabled`\n- `mode`\n- `selectedPresetID`\n- `fallbackPresetID`\n- `previewDayPart`\n- `previewWeatherCondition`\n\n`default` disables the feature and uses `clear-day` as both selected and fallback preset.\n\n#### `HomeBackgroundMode`\n\n- `.staticImage`\n- `.dynamic`\n\nIncludes user-facing `title` and `subtitle`.\n\n#### `HomeBackgroundDayPart`\n\n- `.morning`\n- `.day`\n- `.evening`\n- `.night`\n\nIncludes:\n\n- `title`\n- `isNightLike`\n- `current(at:calendar:)`\n\n`current(at:)` maps the current hour into a day part and is used by runtime background selection logic.\n\n#### `HomeWeatherCondition`\n\n- `.clear`\n- `.cloudy`\n- `.rain`\n- `.storm`\n- `.snow`\n- `.fog`\n- `.unknown`\n\n`unknown` is labeled `\"Auto\"` in the UI.\n\n#### `HomeBackgroundReadabilityTone`\n\n- `.light`\n- `.mixed`\n- `.dark`\n\n#### `HomeBackgroundPreset`\n\nA non-persisted preset descriptor with:\n\n- `id`\n- `title`\n- `subtitle`\n- `assetName`\n- `dayPart`\n- `weatherCondition`\n- `readabilityTone`\n\n`all` returns the built-in preset list, and `preset(withID:)` resolves a preset by identifier.\n\n---\n\n## Muscle anatomy models\n\n### `MuscleIllustrationView`\n\n- `.anterior`\n- `.posterior`\n\nUsed to choose the front/back illustration asset variant.\n\n### `MuscleIllustrationDissection`\n\n- `.outerInnerMuscles`\n\nProvides the dissection layer used in asset naming.\n\n### `MuscleIllustrationGroup`\n\nA large enum of illustration groups such as:\n\n- `back`\n- `chest`\n- `quadriceps`\n- `tricepsBrachii`\n- `latissimusDorsiTeresMajor`\n\nEach case exposes an `assetSlug`, and `assetName(view:dissection:)` builds the final asset identifier, for example:\n\n`muscle-back-anterior-outer-inner-muscles`\n\n### `MuscleRegion`\n\nA higher-level grouping used for UI and analytics:\n\n- `neck`\n- `shoulder`\n- `upperArms`\n- `forearm`\n- `back`\n- `chest`\n- `hips`\n- `thighs`\n- `calves`\n\nKey helpers:\n\n- `illustrationGroup`\n- `resolve(_:)`\n\n`resolve(_:)` normalizes raw strings and maps common aliases like `\"shoulders\"`, `\"upper arm\"`, `\"calf\"`, and `\"hip\"` to canonical regions.\n\n### `TargetMuscle`\n\nA more specific muscle taxonomy used by exercise metadata.\n\nKey helpers:\n\n- `region`\n- `illustrationGroup`\n- `resolve(_:)`\n\n`resolve(_:)` handles many aliases and naming variants, including:\n\n- `\"medialdeltoid\"` → `.lateralDeltoid`\n- `\"pectoralismajorsternocostal\"` → `.pectoralisMajorSternal`\n- `\"abductors\"` → `.hipAbductors`\n\n### Exercise anatomy extensions\n\nThe `Exercise` extension uses these enums to derive:\n\n- the main muscle region\n- the illustration group\n- the preferred illustration asset\n\nResolution order for `preferredMuscleIllustrationAssetName(view:)`:\n\n1. first resolved target muscle\n2. main muscle illustration group\n3. fallback to back illustration\n\nThis keeps UI rendering stable even when exercise metadata is incomplete.\n\n---\n\n## Widget and Live Activity models\n\n### `HeatmapWidgetConfiguration`\n\nImplements `WidgetConfigurationIntent` for the workout heatmap widget.\n\nParameters:\n\n- `timeRange`\n- `metric`\n- `calorieGoal`\n\n#### `TimeRangeOption`\n\n- `.thirty`\n- `.sixty`\n- `.ninety`\n\nMaps to `WidgetTimeRange` via `widgetTimeRange`.\n\n#### `MetricOption`\n\n- `.calories`\n- `.volume`\n- `.duration`\n- `.count`\n\nMaps to `HeatmapMetric` via `heatmapMetric`.\n\n---\n\n### `WidgetDataModels`\n\nThese types define the payloads used by widgets and related summary surfaces.\n\n#### `HeatmapMetric`\n\n- `.calories`\n- `.volume`\n- `.duration`\n- `.count`\n\n#### `WidgetTimeRange`\n\n- `.thirty`\n- `.sixty`\n- `.ninety`\n\n`days` returns the integer day count.\n\n#### `WorkoutDayData`\n\nRepresents one day in the heatmap.\n\nFields:\n\n- `date`\n- `workoutCount`\n- `totalVolume`\n- `totalDuration`\n- `caloriesBurned`\n\nHelpers:\n\n- `value(for:)`\n- `intensity(for:maxValue:)`\n\n`intensity(for:maxValue:)` delegates to `value(for:)` and clamps the normalized result to `1.0`.\n\n#### Other DTOs\n\n- `WorkoutStats`\n- `DailyVolume`\n- `PersonalRecord`\n- `MuscleGroupStats`\n- `ExerciseVarietyData`\n- `ExerciseFrequency`\n- `RestDayData`\n- `WidgetData`\n\n`WidgetData` is the top-level container for widget rendering and includes heatmap, stats, records, muscle distribution, variety, rest-day guidance, and `lastUpdated`.\n\n---\n\n### `WorkoutActivityAttributes`\n\nDefines Live Activity attributes for an active workout.\n\n- `workoutName`\n- nested `ContentState`:\n - `duration`\n - `exerciseCount`\n - `setCount`\n - `caloriesBurned`\n - `restTimerEndTime`\n - `isTimerActive`\n\nThis is the state payload used by ActivityKit to update the workout Live Activity UI.\n\n---\n\n## Achievement models\n\n### `AchievementCategory`\n\nCategories for achievement events:\n\n- `.personalRecord`\n- `.workoutRecord`\n- `.milestone`\n- `.streak`\n\n`title` provides user-facing grouping labels.\n\n### `AchievementKind`\n\nSpecific achievement types:\n\n- `.maxWeight`\n- `.repsAtWeight`\n- `.estimatedOneRepMax`\n- `.volume`\n- `.caloriesBurned`\n- `.duration`\n- `.workoutCount`\n- `.streakLength`\n\n`title` maps each kind to a display label.\n\n### `AchievementScope`\n\n- `.exercise`\n- `.workout`\n- `.profile`\n\n### `AchievementEvent`\n\nA persisted achievement record with rich display metadata.\n\nFields include:\n\n- `recordKey`\n- category/kind/scope raw values and typed wrappers\n- `title`, `subtitle`, `detail`\n- `value`, `secondaryValue`, `unit`\n- badge and placeholder asset names\n- optional exercise metadata:\n - `exerciseId`\n - `exerciseName`\n- `achievedAt`\n- `isCurrent`\n- `workoutSession`\n\nThis model is suitable for both historical achievement tracking and “current best” state.\n\n### `PendingAchievementCelebration`\n\nRepresents an achievement queued for UI celebration.\n\nFields:\n\n- `achievementIdentifier`\n- `recordKey`\n- `title`\n- `subtitle`\n- `detail`\n- `badgeAssetName`\n- `placeholderSymbolName`\n- `achievedAt`\n- `enqueuedAt`\n- `sortOrder`\n\nThis is a lightweight queue item used to sequence celebration presentation.\n\n---\n\n## Profile and filtering models\n\n### `UserProfile`\n\nStores the user’s profile and progress summary.\n\nFields:\n\n- `username`\n- `photoData`\n- `weight`\n- `targetWeight`\n- `height`\n- `workoutStreak`\n- `totalWorkouts`\n- `createdAt`\n\nThis model is referenced by the Home and Profile views and is the source of user-facing progress metrics.\n\n### `FilterState`\n\nAn observable UI state object for exercise filtering.\n\nSelected filter sets:\n\n- `selectedMuscles`\n- `selectedEquipment`\n- `selectedMechanics`\n- `selectedUtility`\n- `selectedForce`\n- `selectedDifficulty`\n\nHelpers:\n\n- `hasActiveFilters`\n- `clearAll()`\n\nThis is intentionally not a SwiftData model; it is transient view state.\n\n---\n\n## Search and discovery\n\n### `ExerciseSearchIndex`\n\nA persisted search document for exercise lookup.\n\nFields:\n\n- `exerciseName`\n- `searchableText`\n- `mainMuscle`\n- `equipment`\n- `mechanics`\n- `utility`\n- `force`\n- `difficulty`\n- `isStretch`\n\nThis model supports fast filtering/searching without querying the full `Exercise` entity directly.\n\n---\n\n## How the models work together\n\n### Workout creation and execution\n\n1. `WorkoutPlan` or `WorkoutTemplate` defines the structure.\n2. `WorkoutSession` is created when the user starts a workout.\n3. Each exercise becomes an `ExerciseLog`.\n4. Each performed set becomes a `SetLog`.\n5. Session metadata such as tags, muscle groups, and rest timer state are stored on `WorkoutSession`.\n\n### Content provenance\n\n- `WorkoutContentOwnership` and `WorkoutContentSource` annotate imported or user-created content.\n- `ExerciseTemplate` and `WorkoutPlan` both preserve source identifiers for syncing and migration.\n- `resolvedLocalContentID` provides a stable local fallback when imported IDs are absent.\n\n### Theme rendering\n\n1. `AppTheme` selects light/dark `ThemeColorSet` values.\n2. `BackgroundConfiguration` determines the background rendering mode.\n3. `ThemeExport` serializes the theme for sharing or backup.\n\n### Anatomy-driven UI\n\n1. `Exercise` stores raw muscle strings.\n2. `TargetMuscle.resolve(_:)` and `MuscleRegion.resolve(_:)` normalize them.\n3. UI can render the correct illustration asset via `preferredMuscleIllustrationAssetName(view:)`.\n\n### Widgets and Live Activities\n\n- `WidgetData` aggregates workout summaries for widgets.\n- `HeatmapWidgetConfiguration` configures widget behavior.\n- `WorkoutActivityAttributes` powers Live Activity updates during active workouts.\n\n---\n\n## Persistence and serialization patterns\n\nThis module uses a few recurring patterns:\n\n### Raw-value wrappers\n\nSeveral models store enums as strings for persistence compatibility:\n\n- `AchievementEvent`\n- `Exercise`\n- `ExerciseLog`\n- `SetLog`\n- `ExerciseTemplate`\n- `WorkoutPlan`\n- `UserWorkoutPlan`\n- `WorkoutSession`\n\nThe computed enum properties provide safe typed access with sensible defaults.\n\n### Codable value objects\n\nTypes such as `ThemeColorSet`, `BackgroundConfiguration`, `WorkoutPlanDayDefinition`, `TemplateExerciseDefinition`, and widget DTOs are designed for JSON persistence, export/import, or cross-process transfer.\n\n### Custom Codable for associated-value enums\n\n`LayerType` implements manual encoding/decoding to preserve the active layer kind and its payload.\n\n### Derived data over duplication\n\nMany models store canonical raw data and expose computed helpers instead of duplicating state:\n\n- `WorkoutSession.workoutTags`\n- `WorkoutSession.muscleGroups`\n- `Exercise.loggingMode`\n- `ExerciseTemplate.ownership`\n- `WorkoutPlan.source`\n- `SetLog.tag`\n\nThis keeps persistence simple while preserving a typed API for the rest of the app.\n\n---\n\n## Contribution notes\n\n- Prefer adding computed wrappers around raw stored values rather than exposing raw strings directly.\n- Keep Codable formats stable; several models are used for export/import and widget payloads.\n- When adding new muscle names or aliases, update both `TargetMuscle.resolve(_:)` and `MuscleRegion.resolve(_:)` as needed.\n- If you add a new background layer type, update `LayerType` encoding/decoding and any editor UI that constructs `CanvasLayer`.\n- Preserve fallback behavior in computed properties; many views rely on safe defaults when data is incomplete.\n\n","data-seed-assets":"# Data & Seed Assets\n\n# Data & Seed Assets\n\nThis module contains the static data files used to bootstrap, validate, and preserve workout and exercise content for the application. It is not an executable code module: there are no functions, classes, or runtime call paths here. Instead, these assets support import, seeding, normalization, and quality checks for the workout database.\n\n## Contents\n\n- `mongo_seed_validation_summary.json` — summary metrics from a MongoDB seed/validation pass\n- `stretch_exercise_dataset.backup.json` — backup copy of stretch exercise records\n- `stretch_exercise_dataset.json` — current stretch exercise dataset\n- `workout_programs.txt` — raw workout program source data in a line-oriented text format\n\n## Purpose\n\nThe data in this directory serves three related roles:\n\n1. **Seed source** \n Provides structured workout program and exercise records that can be imported into the database.\n\n2. **Validation artifact** \n Captures the results of a seed/cleanup process so maintainers can assess data quality and matching coverage.\n\n3. **Recovery and audit support** \n Keeps a backup of stretch exercise data so the current dataset can be restored or compared against prior content.\n\nThese files are especially useful when rebuilding the database, debugging import logic, or reviewing how scraped workout content was normalized into MongoDB documents.\n\n## Data flow at a glance\n\n```mermaid\nflowchart LR\n A[workout_programs.txt] --> B[Seed / import process]\n C[stretch_exercise_dataset.backup.json] --> D[stretch_exercise_dataset.json]\n B --> E[MongoDB collections]\n E --> F[mongo_seed_validation_summary.json]\n```\n\n## File reference\n\n### `workout_programs.txt`\n\nThis file contains the raw workout program source data. It is organized as repeated blocks with fields such as:\n\n- `Name`\n- `Program Title`\n- `Main Goal`\n- `Workout Type`\n- `Training Level`\n- `Duration`\n- `Days/Week`\n- `Time/Workout`\n- `Equipment`\n- `Target Gender`\n- `Days`\n\nThe `Days` field is followed by a serialized list of day objects, each containing:\n\n- `day_name`\n- `day_number`\n- `is_rest_day`\n- `exercises`\n\nEach exercise entry typically includes:\n\n- `exercise_name`\n- `sets`\n- `reps`\n\n#### Notes on structure\n\n- The file is not strict JSON; it is a text export with embedded Python-like list/dict syntax.\n- Some records include repeated or malformed values, such as duplicated labels, inconsistent spacing, or concatenated text.\n- Some workout days are rest days with empty `exercises` arrays.\n- Exercise names may include supersets, trisets, warm-up notes, drop sets, or other training instructions embedded in the name or reps field.\n\n#### How it is used\n\nThis file is the likely source for generating or refreshing workout program seed data. Any importer consuming it must be tolerant of:\n\n- inconsistent whitespace\n- multiline values\n- mixed formatting in exercise names\n- optional or missing fields\n- rest-day entries with no exercises\n\n### `stretch_exercise_dataset.json`\n\nThis is the active stretch exercise dataset. In the provided snapshot it is currently empty (`[]`), which suggests one of the following:\n\n- the dataset has not yet been populated\n- the data was intentionally cleared\n- the current source of truth is elsewhere and this file is a placeholder\n\nBecause the file is empty, any code that expects stretch exercise records should handle the no-data case gracefully.\n\n### `stretch_exercise_dataset.backup.json`\n\nThis file is a backup copy of the stretch exercise dataset and contains a large array of stretch exercise records. Each record uses a flat object shape with fields such as:\n\n- `Exercise Name`\n- `Equipment`\n- `Preparation`\n- `Execution`\n- `Target_Muscles`\n- `Synergist_Muscles`\n- `Main_muscle`\n\n#### Characteristics of the backup data\n\n- Records are grouped by body region and movement pattern, including neck, shoulders, upper arms, forearms, back, chest, hips, thighs, and calves.\n- Several entries contain duplicate or near-duplicate exercise names with different equipment or execution cues.\n- Some records include malformed or concatenated values, such as combined equipment labels or stray whitespace/control characters.\n- A few entries appear incomplete, with empty `Exercise Name`, `Equipment`, `Preparation`, or `Execution` fields.\n\n#### Why the backup matters\n\nThis file is useful for:\n\n- restoring the stretch dataset if the active file is lost or cleared\n- comparing normalized output against the original source\n- identifying data-cleaning rules needed before import\n- preserving historical content while the active dataset is regenerated\n\n### `mongo_seed_validation_summary.json`\n\nThis file records the outcome of a MongoDB seed validation run. It provides a compact snapshot of import quality and matching coverage.\n\n#### Key metrics\n\n- `programCount`: 617\n- `exerciseCount`: 793\n- `invalidRecords`: 0\n- `duplicateIDs`: 0\n- `duplicateTitles`: 0\n- `rewrittenIDs`: 0\n- `skippedWorkouts`: 40\n- `createdMissingExercises`: 219\n- `lowConfidenceMatches`: 1889\n- `unmatchedExercises`: 1263\n- `skippedRows`: 3270\n\n#### Match breakdown\n\nThe `matchCounts` object shows how records were resolved during seeding:\n\n- `curated`: 12\n- `fuzzy`: 303\n- `generatedFromUrl`: 219\n- `slug`: 4\n- `title`: 191\n- `unmatched`: 1889\n- `url`: 13100\n\n#### Source metadata\n\nThe `source` object identifies the database and collections involved:\n\n- `database`: `workout_scraping`\n- `exerciseCollection`: `exercises`\n- `workoutCollection`: `workouts`\n- `mongoExerciseDocuments`: 601\n- `mongoWorkoutDocuments`: 657\n\n#### Interpretation\n\nThis summary indicates that the seed pipeline handled a large volume of URL-based matching, with a significant number of low-confidence and unmatched exercise references. That makes this file especially valuable when:\n\n- tuning matching heuristics\n- reviewing import completeness\n- diagnosing why certain workouts or exercises were skipped\n- validating whether the current database state matches the expected seed output\n\n## Data quality considerations\n\nThe assets in this module reflect scraped and normalized content, so contributors should expect imperfect source data.\n\n### Common issues present in the datasets\n\n- inconsistent naming conventions\n- duplicate exercise titles with different contexts\n- embedded formatting artifacts\n- missing or empty fields\n- mixed representations of the same concept\n- concatenated labels or malformed equipment strings\n\n### Practical implications\n\nAny importer, validator, or editor working with these files should:\n\n- trim and normalize whitespace\n- tolerate missing optional fields\n- preserve original source text where possible\n- avoid assuming one-to-one mappings between exercise names and canonical records\n- treat backup files as authoritative historical snapshots, not necessarily cleaned production data\n\n## Working with the assets\n\n### Updating workout seed data\n\nWhen refreshing workout programs:\n\n1. regenerate or edit the raw source in `workout_programs.txt`\n2. run the seed/import pipeline that converts the text into database documents\n3. validate the resulting MongoDB collections\n4. update `mongo_seed_validation_summary.json` with the latest metrics if the pipeline produces a new summary\n\n### Restoring stretch exercises\n\nIf `stretch_exercise_dataset.json` needs to be repopulated:\n\n1. use `stretch_exercise_dataset.backup.json` as the source snapshot\n2. normalize records into the active JSON schema expected by the application\n3. write the cleaned output to `stretch_exercise_dataset.json`\n4. verify that the active file contains the expected records and fields\n\n### Reviewing seed health\n\nUse `mongo_seed_validation_summary.json` to answer questions such as:\n\n- How many workouts were imported?\n- How many exercises were created from missing references?\n- How many matches were fuzzy versus curated?\n- How many records were skipped or left unmatched?\n\n## Contribution guidelines\n\nWhen editing files in this module:\n\n- preserve JSON validity in `.json` files\n- avoid reformatting raw source text unless necessary for correctness\n- keep backup files intact unless intentionally replacing the snapshot\n- document any schema changes that affect downstream importers\n- update validation summaries after rerunning the seed pipeline\n\n## Relationship to the rest of the codebase\n\nThis module is typically consumed by:\n\n- database seed scripts\n- import/normalization utilities\n- validation or reconciliation jobs\n- admin/debug tooling that inspects workout and exercise content\n\nBecause there are no internal functions or classes here, the integration point is file-based: other parts of the application read these assets, transform them into structured records, and persist them to MongoDB collections such as `workouts` and `exercises`.\n\n## Summary\n\nThe `Data & Seed Assets` module is the content backbone for workout seeding and validation. It stores the raw workout program source, the stretch exercise dataset and its backup, and the latest MongoDB seed validation results. Together, these files support reproducible imports, data recovery, and ongoing quality control for the workout database.","exercise-creation":"# Exercise Creation\n\n# Exercise Creation\n\nThe Exercise Creation module is responsible for building and persisting `ExerciseTemplate` records from either:\n\n- a blank draft created from scratch, or\n- an imported catalog exercise that is edited before saving.\n\nIt includes the data model used while editing, the service that converts a draft into a persisted template, and the SwiftUI screens for creating and browsing custom exercises.\n\n## Responsibilities\n\nThis module handles three related concerns:\n\n1. **Drafting exercise templates**\n - Captures editable fields in `ExerciseTemplateDraft`\n - Supports both blank and imported starting states\n\n2. **Validating and normalizing input**\n - Requires a non-empty exercise name\n - Trims whitespace from text fields\n - Clamps difficulty to the supported range\n\n3. **Presenting creation and browsing UI**\n - `ExerciseTemplateBuilderView` for creating templates\n - `CustomExercisesView` for listing saved custom exercises\n - `CustomExerciseDetailView` for inspecting a saved template\n - `ExerciseGuideView` for viewing catalog exercise guidance\n\n---\n\n## Architecture overview\n\n```mermaid\nflowchart LR\n A[ExerciseTemplateBuilderView] --> B[ExerciseTemplateBuilderViewModel]\n B --> C[ExerciseTemplateDraft]\n B --> D[ExerciseCreationService]\n D --> E[ExerciseTemplate]\n A --> F[ExercisePickerSheet]\n G[CustomExercisesView] --> E\n H[ExerciseGuideView] --> I[Exercise]\n```\n\nThe flow is intentionally simple:\n\n- The view owns UI state and user interaction.\n- The view model owns the current draft and mode.\n- The service performs the final conversion into `ExerciseTemplate`.\n- SwiftData persists the resulting model through `modelContext.insert(...)`.\n\n---\n\n## Core types\n\n### `ExerciseTemplateDraft`\n\n`ExerciseTemplateDraft` is the editable form state for exercise creation.\n\nIt stores all fields needed to build an `ExerciseTemplate`:\n\n- `name`\n- `equipment`\n- `variation`\n- `utility`\n- `mechanics`\n- `force`\n- `preparation`\n- `execution`\n- `targetMuscles`\n- `mainMuscle`\n- `difficulty`\n- `defaultRestTime`\n- `selectedCatalogExerciseID`\n- `selectedCatalogExerciseName`\n\n#### Key behavior\n\n- `trimmedName` returns the name with whitespace and newlines removed.\n- `isValid` currently checks only that `trimmedName` is not empty.\n\n#### Factory methods\n\n- `scratch(defaultRestTime:)`\n - Creates an empty draft with `difficulty = 1`\n - Uses the provided default rest time\n - Clears catalog source metadata\n\n- `imported(from:)`\n - Seeds the draft from an existing `Exercise`\n - Copies the exercise’s descriptive fields\n - Preserves source identity using:\n - `selectedCatalogExerciseID`\n - `selectedCatalogExerciseName`\n\nThis is the main entry point for “Import & Modify” behavior.\n\n---\n\n### `ExerciseCreationService`\n\n`ExerciseCreationService` converts a validated draft into an `ExerciseTemplate`.\n\n#### `makeTemplate(from:source:)`\n\nThis method:\n\n1. Reads `draft.trimmedName`\n2. Throws `ExerciseCreationError.missingName` if the name is empty\n3. Builds an `ExerciseTemplate` with normalized values\n\nNormalization rules:\n\n- Text fields are trimmed with `trimmingCharacters(in: .whitespacesAndNewlines)`\n- `difficulty` is clamped to `1...5`\n- `isCustom` is set when `source == .scratch`\n- Ownership is always set to `.localUser`\n- Source metadata is copied from the draft:\n - `sourceExerciseId`\n - `sourceCatalogExerciseID`\n - `sourceCatalogExerciseName`\n\n#### Error handling\n\n`ExerciseCreationError` currently defines one case:\n\n- `missingName`\n\nThis is the only validation failure surfaced by the service.\n\n---\n\n### `ExerciseTemplateBuilderViewModel`\n\n`ExerciseTemplateBuilderViewModel` is the state container for the builder screen.\n\nIt is marked `@MainActor` and `@Observable`, so it is designed for direct use from SwiftUI.\n\n#### Properties\n\n- `mode: ExerciseTemplateBuilderMode`\n - `.scratch`\n - `.importExercise`\n\n- `selectedExercise: Exercise?`\n - The catalog exercise chosen for import\n\n- `draft: ExerciseTemplateDraft`\n - The editable form state\n\n#### Initialization\n\nThe initializer takes:\n\n- `defaultRestTime: TimeInterval`\n- optional `service: ExerciseCreationService`\n\nIt starts in scratch mode with a blank draft created by:\n\n- `ExerciseTemplateDraft.scratch(defaultRestTime:)`\n\n#### Behavior\n\n- `canSave`\n - Delegates to `draft.isValid`\n\n- `selectExercise(_:)`\n - Stores the selected catalog exercise\n - Replaces the draft with `ExerciseTemplateDraft.imported(from:)`\n\n- `resetForScratchMode()`\n - Clears the selected exercise\n - Recreates a blank scratch draft using the original default rest time\n\n- `makeTemplate()`\n - Chooses the source based on `mode`\n - `.scratch` → `.scratch`\n - `.importExercise` → `.catalogImport`\n - Delegates to `ExerciseCreationService.makeTemplate(from:source:)`\n\n#### Mode titles\n\n`ExerciseTemplateBuilderMode` provides user-facing titles:\n\n- `.scratch` → `\"From Scratch\"`\n- `.importExercise` → `\"Import & Modify\"`\n\n---\n\n### `ExerciseSelection`\n\n`ExerciseSelection` is a lightweight, searchable representation of either:\n\n- a catalog `Exercise`, or\n- an `ExerciseTemplate`\n\nIt conforms to `Identifiable` and `Hashable`.\n\n#### Purpose\n\nThis type is useful when the UI needs a stable selection/search model that abstracts over source type.\n\n#### Source kinds\n\n`ExerciseSelection.SourceKind` distinguishes:\n\n- `.catalog`\n- `.template`\n\n#### Factory methods\n\n- `catalog(_:)`\n - Builds a selection from an `Exercise`\n - Uses a stable `sourceID` derived from:\n - `exercise.parentId` if present\n - otherwise `exercise.name`\n - Sets `isCustomTemplate = false`\n\n- `template(_:)`\n - Builds a selection from an `ExerciseTemplate`\n - Uses `template.resolvedLocalContentID`\n - Sets `loggingMode` to `.weightAndReps`\n - Sets `isCustomTemplate = true`\n\n#### Matching\n\n`matches(_:)` performs a case-insensitive localized standard search across:\n\n- `displayName`\n- `equipment`\n- `mainMuscle`\n\nAn empty or whitespace-only query matches everything.\n\n---\n\n## UI components\n\n### `ExerciseTemplateBuilderView`\n\nThis is the main creation screen.\n\nIt uses:\n\n- `@Environment(\\.dismiss)` to close the sheet\n- `@Environment(\\.modelContext)` to persist the new template\n- `@Query private var exercises: [Exercise]` to load catalog exercises for import\n- `@State private var viewModel` for editing state\n- `@State private var showExercisePicker` to present the picker\n- `@State private var validationMessage` for save errors\n\n#### Initial default rest time\n\nThe view reads `UserDefaults.standard.integer(forKey: \"defaultRestTime\")`.\n\n- If the stored value is greater than zero, it uses that value.\n- Otherwise it falls back to `90` seconds.\n\nThat value is passed into `ExerciseTemplateBuilderViewModel`.\n\n#### Layout\n\nThe form is divided into sections:\n\n- **Mode**\n - segmented picker for `.scratch` / `.importExercise`\n\n- **Import selection**\n - shown only in import mode\n - opens `ExercisePickerSheet`\n\n- **Basic Info**\n - name\n - equipment\n - variation\n\n- **Details**\n - utility\n - mechanics\n - force\n - main muscle\n - target muscles\n - difficulty stepper\n - rest timer stepper\n\n- **Instructions**\n - preparation\n - execution\n\n#### Save flow\n\nThe `save()` method is the critical path:\n\n1. Calls `viewModel.makeTemplate()`\n2. Inserts the resulting `ExerciseTemplate` into `modelContext`\n3. Dismisses the view\n\nIf validation fails:\n\n- `ExerciseCreationError.missingName`\n - shows: `\"Add an exercise name before saving.\"`\n\n- Any other error\n - shows: `\"Review the exercise before saving.\"`\n\n#### Mode switching behavior\n\nWhen `viewModel.mode` changes to `.scratch`, the view calls:\n\n- `viewModel.resetForScratchMode()`\n\nThis ensures the draft is cleared when the user switches away from import mode.\n\n---\n\n### `ExercisePickerSheet`\n\nThis sheet lets the user choose a catalog exercise to import.\n\n#### Behavior\n\n- Displays a searchable list of exercises\n- Filters by `exercise.name.localizedStandardContains(searchText)`\n- Shows only the first 100 exercises when no search text is entered\n- Calls `onSelect(exercise)` and dismisses when a row is tapped\n\n#### Display\n\nEach row shows:\n\n- exercise name\n- equipment, if present\n\nThe list uses `id: \\.name`, so exercise names are expected to be stable enough for row identity in this context.\n\n---\n\n### `CustomExercisesView`\n\nThis view lists saved `ExerciseTemplate` records.\n\nIt uses:\n\n- `@Environment(\\.modelContext)` for deletion\n- `@Environment(AppRouter.self)` for navigation\n- `@Query(sort: \\ExerciseTemplate.createdAt, order: .reverse)` to load templates\n\n#### Behavior\n\n- Tapping a row navigates to `.customExerciseDetail(exercise)`\n- The trailing toolbar button presents `.exerciseBuilder`\n- Swipe-to-delete removes templates from SwiftData\n- An empty state is shown when there are no custom exercises\n\n#### Row content\n\nEach row shows:\n\n- template name\n- equipment, if present\n- a star icon when `exercise.isCustom` is true\n\n---\n\n### `CustomExerciseDetailView`\n\nThis view presents a saved `ExerciseTemplate` in a read-only detail layout.\n\nIt conditionally renders sections for:\n\n- variation\n- mechanics\n- force\n- main muscle\n- target muscles\n- preparation\n- execution\n\nIt also shows a difficulty indicator using five stars.\n\n#### Supporting views\n\n- `DetailSection`\n - reusable titled content block\n- `InfoBadge`\n - compact label/value badge used for metadata\n\nThese are shared presentation components used across the exercise detail screens.\n\n---\n\n### `ExerciseGuideView`\n\n`ExerciseGuideView` displays a catalog `Exercise` in a guide-style layout.\n\nIt is not part of template creation directly, but it sits in the same content flow because users often inspect catalog exercises before importing or comparing them.\n\n#### Content\n\nThe view shows:\n\n- exercise name\n- main muscle\n- equipment\n- muscle illustration via `MuscleIllustrationCard`\n- preparation\n- execution\n- overview metadata grid\n- target muscles\n- synergist muscles\n- stabilizer muscles\n\n#### Overview grid\n\nThe grid is built from `overviewItems`, which includes only non-empty values for:\n\n- mechanics\n- force\n- utility\n- variation\n- rest time\n- difficulty\n\nThis keeps the UI compact and avoids empty placeholders.\n\n---\n\n### `TemplateListView`\n\n`TemplateListView` is the broader templates hub.\n\nIt is adjacent to exercise creation because it provides the entry point to custom exercises.\n\n#### Behavior\n\n- A top section links to `CustomExercisesView`\n- The workout template list is shown below\n- The plus button opens `.templateEditor`\n- Empty state appears when there are no workout templates\n\n#### `TemplateRow`\n\n`TemplateRow` renders a `WorkoutTemplate` summary:\n\n- template name\n- number of exercises\n- default sets and reps\n\nThis view is not part of exercise creation itself, but it is part of the navigation path that leads users into the custom exercise workflow.\n\n---\n\n## Execution flow: saving a new exercise template\n\nThe save path is intentionally narrow:\n\n1. `ExerciseTemplateBuilderView.save()`\n2. `ExerciseTemplateBuilderViewModel.makeTemplate()`\n3. `ExerciseCreationService.makeTemplate(from:source:)`\n4. `ExerciseTemplate(...)`\n5. `modelContext.insert(template)`\n\nThis separation keeps UI concerns, state management, and model construction isolated.\n\n### Why this matters\n\n- The view does not construct models directly.\n- The view model decides whether the source is scratch or import.\n- The service enforces normalization and validation.\n- The resulting `ExerciseTemplate` is ready for persistence.\n\n---\n\n## Source identity and import behavior\n\nA recurring theme in this module is preserving the origin of imported content.\n\nWhen importing from a catalog `Exercise`:\n\n- `ExerciseTemplateDraft.imported(from:)` copies the source exercise fields\n- `selectedCatalogExerciseID` and `selectedCatalogExerciseName` are populated\n- `ExerciseCreationService.makeTemplate(...)` carries those values into the final `ExerciseTemplate`\n\nThis allows the app to distinguish:\n\n- templates created from scratch\n- templates derived from catalog content\n\nThat distinction is reflected in:\n\n- `isCustom`\n- source metadata fields\n- downstream UI and tests\n\n---\n\n## Validation and normalization rules\n\n### Validation\n\nOnly one hard validation rule is enforced here:\n\n- the exercise name must not be empty after trimming\n\nThis is checked in both:\n\n- `ExerciseTemplateDraft.isValid`\n- `ExerciseCreationService.makeTemplate(from:source:)`\n\nThe UI disables Save based on `canSave`, but the service still validates defensively.\n\n### Normalization\n\nBefore persistence, the service:\n\n- trims all text fields\n- clamps difficulty to `1...5`\n- preserves default rest time as entered\n- sets ownership to `.localUser`\n\nThis ensures the stored template is consistent even if the UI allows intermediate invalid or messy input.\n\n---\n\n## Integration points\n\n### SwiftData\n\nThe module reads and writes SwiftData models directly:\n\n- `@Query` loads `Exercise`, `ExerciseTemplate`, and `WorkoutTemplate`\n- `modelContext.insert(...)` persists new templates\n- `modelContext.delete(...)` removes templates\n\n### App routing\n\n`CustomExercisesView` and `TemplateListView` use `AppRouter` to navigate or present sheets:\n\n- `.customExercises`\n- `.customExerciseDetail(...)`\n- `.exerciseBuilder`\n- `.templateEditor`\n\n### Design system\n\nThe views rely on shared styling helpers and tokens such as:\n\n- `themedListRow()`\n- `themedListBackground()`\n- `DesignSystem.Colors.*`\n- `DesignSystem.Typography.*`\n- `DesignSystem.CornerRadius.*`\n\n### Exercise and template models\n\nThis module depends on the broader model layer:\n\n- `Exercise`\n- `ExerciseTemplate`\n- `WorkoutContentSource`\n- `ExerciseLoggingMode`\n\nThe creation code assumes these models already define the fields being copied into the draft and template.\n\n---\n\n## Testing relevance\n\nThe call graph indicates this module is covered by content-creation tests, including cases for:\n\n- rejecting drafts with missing names\n- preserving source identity during catalog import\n- distinguishing catalog and template selections\n- creating scratch exercises with ownership metadata\n\nThat test coverage aligns with the module’s main responsibilities:\n\n- validation\n- source tracking\n- stable identity\n- persistence-ready model creation\n\n---\n\n## Contribution notes\n\nWhen modifying this module, keep these constraints in mind:\n\n- Preserve the service/view-model split; avoid moving persistence logic into the view.\n- Keep `ExerciseTemplateDraft` as the single editable state object.\n- Maintain source metadata when importing from catalog exercises.\n- Update both `isValid` and `makeTemplate(from:source:)` if validation rules change.\n- Be careful with identity fields in `ExerciseSelection`; downstream matching and navigation depend on stable IDs.\n- If new fields are added to `ExerciseTemplate`, update:\n - `ExerciseTemplateDraft`\n - `ExerciseTemplateDraft.imported(from:)`\n - `ExerciseTemplateDraft.scratch(defaultRestTime:)`\n - `ExerciseCreationService.makeTemplate(from:source:)`\n - the builder UI\n\n---\n\n## Summary\n\nThe Exercise Creation module provides the full path from user input to persisted custom exercise template:\n\n- `ExerciseTemplateDraft` stores editable state\n- `ExerciseTemplateBuilderViewModel` manages mode and draft transitions\n- `ExerciseCreationService` validates and builds the final model\n- `ExerciseTemplateBuilderView` collects input and saves\n- `CustomExercisesView` and `CustomExerciseDetailView` expose saved templates\n- `ExerciseGuideView` supports catalog exercise inspection\n- `ExerciseSelection` provides stable selection/search identity for exercise sources\n\nThe module is intentionally small and layered, making it straightforward to extend while keeping validation and source tracking consistent.","health-activity-widgets":"# Health, Activity & Widgets\n\n# Health, Activity & Widgets\n\nThis module bridges three app subsystems:\n\n- **HealthKit integration** for reading and writing steps, water, and calorie data\n- **Live Activities** for surfacing workout progress on the Lock Screen / Dynamic Island\n- **Widget data generation and refresh** for keeping widget timelines backed by up-to-date workout summaries\n\nThe code is centered around four utility types:\n\n- `HealthKitManager`\n- `LiveActivityManager`\n- `WidgetDataProvider`\n- `WidgetDataRefresher`\n\nTogether, they provide the app’s health-data pipeline and the refresh path that keeps widgets synchronized with workout changes.\n\n---\n\n## Architecture overview\n\n```mermaid\nflowchart LR\n A[WorkoutSessionView] -->|saveSet / finishWorkout / deleteWorkout| D[WidgetDataRefresher]\n B[SettingsView] -->|requestAuthorization| H[HealthKitManager]\n C[RestTimerManager] -->|updateWorkoutActivity| L[LiveActivityManager]\n D --> P[WidgetDataProvider]\n P --> S[SharedContainer.writeData]\n P --> R[WorkoutHistoryRepository]\n H --> HK[HealthKit]\n L --> AK[ActivityKit]\n```\n\n### Responsibilities by layer\n\n- **`HealthKitManager`** owns HealthKit authorization, reads time-series health data, and writes samples back to HealthKit.\n- **`LiveActivityManager`** manages a single workout Live Activity instance.\n- **`WidgetDataProvider`** queries SwiftData and repository helpers to build the JSON payload consumed by widgets.\n- **`WidgetDataRefresher`** is the orchestration entry point used by workout screens to regenerate widget data and reload timelines.\n\n---\n\n## HealthKitManager\n\n`HealthKitManager` is an `@Observable` class that encapsulates all HealthKit access used by the app.\n\n### State\n\nIt maintains three 288-element arrays, each representing a full day in 5-minute buckets:\n\n- `stepsData`\n- `waterData`\n- `calorieData`\n\nIt also exposes derived totals:\n\n- `totalSteps`\n- `totalWater`\n- `totalCalories`\n\n`isAuthorized` reflects whether at least one of the tracked HealthKit types is currently authorized for sharing.\n\n### Initialization and authorization flow\n\nThe initializer immediately kicks off an async authorization check on the main actor:\n\n- `init()` launches `checkAuthorizationStatus()`\n- `checkAuthorizationStatus()` verifies HealthKit availability and inspects authorization for:\n - `.stepCount`\n - `.dietaryWater`\n - `.dietaryEnergyConsumed`\n\nAuthorization is considered enabled if **any** of those types is `.sharingAuthorized`.\n\n`requestAuthorization()` requests read/write access for all three types and then re-checks authorization status. If the request fails, `isAuthorized` is set to `false`.\n\n### Reading health data\n\nThe fetch methods all follow the same pattern:\n\n- `fetchSteps(for:)`\n- `fetchWater(for:)`\n- `fetchCalories(for:)`\n\nEach calls the private `fetchQuantity(identifier:unit:date:)` helper, which:\n\n1. Verifies HealthKit availability\n2. Builds a day range from midnight to midnight\n3. Executes an `HKStatisticsCollectionQuery`\n4. Aggregates values into 5-minute buckets\n5. Returns a `[Int]` array of length 288\n\nThe returned arrays are assigned back to the corresponding published state properties.\n\n### Writing health data\n\nThe save methods are:\n\n- `saveWater(amount:date:)`\n- `saveSteps(count:date:)`\n- `saveCalories(amount:date:)`\n\nEach method:\n\n1. Checks HealthKit availability\n2. Reads the authorization status for the relevant quantity type\n3. If status is `.notDetermined`, requests authorization and retries once\n4. Requires `.sharingAuthorized` before saving\n5. Creates an `HKQuantitySample`\n6. Saves it to HealthKit\n7. Refreshes the corresponding in-memory day array by calling the matching fetch method\n\nAll three methods return `Bool` to indicate success.\n\n### Important implementation detail\n\nThe save methods use `Date()` for the sample’s `start` and `end`, but refresh data for the `date` argument passed in. This means the saved sample is written at the current time, while the UI refresh is anchored to the caller’s target day.\n\n### Data model shape\n\nThe 288-slot arrays imply a fixed 24-hour day divided into 5-minute intervals:\n\n- `24 * 60 / 5 = 288`\n\nThis is the core shape used by any UI that renders intraday charts or heatmaps from HealthKit data.\n\n### Notes for contributors\n\n- `getCombinedSteps(for:)` currently returns `stepsData` unchanged. The signature suggests future support for date-specific aggregation or merging multiple sources.\n- `fetchQuantity` uses `.cumulativeSum`, which is appropriate for step counts and cumulative intake metrics.\n- The code assumes the requested HealthKit quantity types are available and force-unwraps them with `!`.\n\n---\n\n## LiveActivityManager\n\n`LiveActivityManager` is a `@MainActor` singleton responsible for creating and updating a workout Live Activity.\n\n### Singleton access\n\nUse:\n\n```swift\nLiveActivityManager.shared\n```\n\nThe initializer is private, so this is the only supported entry point.\n\n### Starting a workout Live Activity\n\n`startWorkoutActivity(workoutName:)`:\n\n1. Reads `ActivityAuthorizationInfo`\n2. Logs whether Live Activities and frequent pushes are enabled\n3. Returns early if Live Activities are disabled by the user\n4. Creates `WorkoutActivityAttributes` with the provided workout name\n5. Creates an initial `ContentState`\n6. Requests a new `Activity<WorkoutActivityAttributes>`\n\nThe initial state sets:\n\n- `duration = 0`\n- `exerciseCount = 0`\n- `setCount = 0`\n- `caloriesBurned = 0`\n- `isTimerActive = false`\n\nIf the request succeeds, the activity is stored in `currentActivity`.\n\n### Updating a workout Live Activity\n\n`updateWorkoutActivity(duration:exerciseCount:setCount:restTimerEndTime:isTimerActive:)` builds a new `ContentState` and computes calories burned using:\n\n```swift\nInt(duration / 60 * 5.0)\n```\n\nThis method currently constructs the updated state but does not actually call `activity.update(...)`; the update code is present but commented out.\n\n### Ending a workout Live Activity\n\n`endWorkoutActivity()` checks for an active activity, but the actual `end(...)` call is also commented out.\n\n### Integration points\n\nThis manager is called indirectly from timer logic and workout session flows:\n\n- `RestTimerManager` calls `updateWorkoutActivity(...)`\n- `WorkoutSessionView` calls into the Live Activity path when sets are saved\n\n### Contributor note\n\nBecause the update and end operations are commented out, the class currently behaves more like a state-preparation layer than a fully active Live Activity controller. If you re-enable those calls, keep the `@MainActor` isolation and ensure `currentActivity` is cleared after ending.\n\n---\n\n## WidgetDataProvider\n\n`WidgetDataProvider` builds the data payload written to shared storage for widgets.\n\nIt is `@MainActor` and initialized with a `ModelContext`, which it uses to query SwiftData entities.\n\n### Primary entry point\n\n`refreshAllWidgetData()` assembles a `WidgetData` value and writes it to the shared container:\n\n- `heatmapData`\n- `stats`\n- `personalRecords`\n- `muscleGroups`\n- `exerciseVariety`\n- `restDay`\n- `lastUpdated`\n\nThe final step is:\n\n```swift\nSharedContainer.writeData(data, to: \"widgetData.json\")\n```\n\nThis is the handoff point between app data and widget consumption.\n\n### Data sources\n\nThe provider reads from:\n\n- `WorkoutSession`\n- `Exercise`\n- `WorkoutHistoryRepository`\n\nIt uses repository helpers for workout-derived calculations such as streaks and volume.\n\n---\n\n### Heatmap data\n\n`fetchHeatmapData(range:metric:)`:\n\n- Computes a date window based on `WidgetTimeRange`\n- Fetches non-active `WorkoutSession` records in that range\n- Uses `WorkoutHistoryRepository.daySummaries(from:achievements:)`\n- Maps summaries into `WorkoutDayData`\n\nThe returned `WorkoutDayData` includes:\n\n- `date`\n- `workoutCount`\n- `totalVolume`\n- `totalDuration`\n- `caloriesBurned`\n\nThe `metric` parameter is currently accepted but not used in the implementation.\n\n### Workout stats\n\n`fetchWorkoutStats()` builds a `WorkoutStats` object from all completed sessions.\n\nIt calculates:\n\n- `currentStreak` via `WorkoutHistoryRepository.currentStreakDays(from:)`\n- `lastWorkoutDate`\n- `weeklyWorkouts`\n- `weeklyGoal` hardcoded to `5`\n- `totalWorkouts`\n- `weeklyVolume`\n- `weeklyVolumeData` for the last 7 days\n\nThe helper `calculateTotalVolume(sessions:)` delegates to `WorkoutHistoryRepository.sessionVolume(_:)`.\n\n### Personal records\n\n`fetchPersonalRecords()` scans all completed sessions and all strength sets to find the best result per exercise.\n\nFor each `exerciseLog.exerciseId`, it keeps the strongest set by:\n\n1. Higher weight wins\n2. If weight ties, higher reps wins\n\nIt returns the top 10 records sorted by weight descending.\n\n### Muscle group stats\n\n`fetchMuscleGroupStats()`:\n\n- Looks at completed sessions from the last 30 days\n- Fetches all `Exercise` records once\n- Builds a lookup map keyed by `parentId`\n- Counts exercise-log occurrences by `mainMuscle`\n\nIf `mainMuscle` is empty, the muscle group is labeled `\"Other\"`.\n\nThe result is a top-8 list of `MuscleGroupStats` with percentages normalized against the total frequency.\n\n### Exercise variety\n\n`fetchExerciseVariety()` computes:\n\n- `uniqueExercises`\n- `totalSessions`\n- `varietyScore`\n- `topExercises`\n\nThe score is normalized as:\n\n```swift\nmin(Double(uniqueCount) / Double(totalSessions * 3), 1.0)\n```\n\nThis is a heuristic rather than a strict statistical measure.\n\n### Rest day data\n\n`fetchRestDayData()` inspects the most recent completed workout and returns a `RestDayData` recommendation.\n\nBehavior:\n\n- If there are no workouts, it returns:\n - `daysSinceLastWorkout = 0`\n - `isRestDay = true`\n - `lastWorkoutDate = nil`\n - `recommendation = \"Start your first workout!\"`\n- Otherwise it computes days since the last workout and maps that to a short recommendation string.\n\n### Widget refresh pipeline\n\n`refreshAllWidgetData()` calls the following methods in sequence:\n\n1. `fetchHeatmapData(range:metric:)`\n2. `fetchWorkoutStats()`\n3. `fetchPersonalRecords()`\n4. `fetchMuscleGroupStats()`\n5. `fetchExerciseVariety()`\n6. `fetchRestDayData()`\n\nThis is the main aggregation flow for widget content.\n\n---\n\n## WidgetDataRefresher\n\n`WidgetDataRefresher` is a lightweight `@MainActor` singleton that coordinates widget regeneration after workout changes.\n\n### Singleton access\n\nUse:\n\n```swift\nWidgetDataRefresher.shared\n```\n\n### Refresh flow\n\n`refreshWidgets(modelContext:)`:\n\n1. Creates a `WidgetDataProvider`\n2. Calls `refreshAllWidgetData()`\n3. Calls `WidgetCenter.shared.reloadAllTimelines()`\n\nIf anything fails, it logs the error and stops.\n\n### Timeline-only refresh\n\n`reloadTimelines()` is a convenience wrapper around:\n\n```swift\nWidgetCenter.shared.reloadAllTimelines()\n```\n\n### Where it is used\n\nThis refresher is called from workout UI flows such as:\n\n- finishing a workout\n- deleting a workout\n- saving a workout session\n\nThat makes it the bridge between SwiftData mutations and widget updates.\n\n---\n\n## Data flow summary\n\n### Health data flow\n\n1. `SettingsView` or app startup triggers `HealthKitManager.requestAuthorization()` or `checkAuthorizationStatus()`\n2. The manager reads or writes HealthKit samples\n3. Intraday arrays are refreshed via `fetchSteps`, `fetchWater`, or `fetchCalories`\n4. UI can bind to `stepsData`, `waterData`, `calorieData`, and totals\n\n### Widget data flow\n\n1. Workout data changes in SwiftData\n2. `WidgetDataRefresher.refreshWidgets(modelContext:)` is called\n3. `WidgetDataProvider.refreshAllWidgetData()` rebuilds the widget payload\n4. `SharedContainer.writeData(...)` persists the JSON\n5. `WidgetCenter.shared.reloadAllTimelines()` tells widgets to reload\n\n### Live Activity flow\n\n1. Workout or timer logic calls `LiveActivityManager.startWorkoutActivity(workoutName:)`\n2. Timer updates call `updateWorkoutActivity(...)`\n3. Ending a workout should call `endWorkoutActivity()`\n\n---\n\n## Cross-module connections\n\nThis module is not isolated; it is tightly coupled to the rest of the app’s workout domain.\n\n### Incoming callers\n\n- `BigRoosterWorkoutsApp.swift` creates or accesses `HealthKitManager`\n- `SettingsView.swift` requests HealthKit authorization\n- `WorkoutSessionView.swift` refreshes widgets after workout changes\n- `RestTimerManager.swift` updates the workout Live Activity\n\n### Outgoing dependencies\n\n- `WorkoutHistoryRepository` for streak, volume, and summary calculations\n- `SharedContainer` for widget payload persistence\n- `WidgetCenter` for timeline reloads\n- `WorkoutActivityAttributes` for Live Activity state\n- `WidgetDataModels` types such as:\n - `WidgetData`\n - `WorkoutStats`\n - `WorkoutDayData`\n - `PersonalRecord`\n - `MuscleGroupStats`\n - `ExerciseVarietyData`\n - `RestDayData`\n - `ExerciseFrequency`\n\n---\n\n## Implementation caveats\n\n### HealthKit availability\n\nEvery HealthKit read/write path checks:\n\n```swift\nHKHealthStore.isHealthDataAvailable()\n```\n\nIf HealthKit is unavailable, the methods return early with safe defaults.\n\n### Authorization handling\n\nThe save methods retry once after requesting authorization when the status is `.notDetermined`. If authorization is denied or restricted, they return `false`.\n\n### Force unwraps\n\nThe code force-unwraps quantity types:\n\n```swift\nHKQuantityType.quantityType(forIdentifier: ...)\n```\n\nThis is acceptable for known HealthKit identifiers, but it is still a crash point if Apple changes behavior or if the identifier is invalid.\n\n### Commented-out Live Activity updates\n\n`updateWorkoutActivity(...)` and `endWorkoutActivity()` currently contain commented-out async calls. As written, they do not mutate the live activity after creation.\n\n### Widget metric parameter\n\n`fetchHeatmapData(range:metric:)` accepts `metric`, but the current implementation always maps calories from session summaries and does not branch on the metric value.\n\n---\n\n## Extending the module\n\n### Adding a new HealthKit metric\n\nTo add another tracked metric:\n\n1. Add a new array property to `HealthKitManager`\n2. Extend `checkAuthorizationStatus()` and `requestAuthorization()`\n3. Add a `fetch...` method that calls `fetchQuantity(...)`\n4. Add a `save...` method if writes are supported\n\n### Adding a new widget statistic\n\nTo expose a new widget field:\n\n1. Extend `WidgetData` and related model types\n2. Add a fetch method to `WidgetDataProvider`\n3. Include the new value in `refreshAllWidgetData()`\n4. Ensure the widget reads it from `widgetData.json`\n\n### Re-enabling Live Activity updates\n\nIf you want the workout Live Activity to reflect live changes:\n\n1. Uncomment the `activity.update(...)` call in `updateWorkoutActivity(...)`\n2. Uncomment the `activity.end(...)` call in `endWorkoutActivity()`\n3. Clear `currentActivity` after ending\n4. Verify the `WorkoutActivityAttributes.ContentState` fields match the widget/live activity UI\n\n---\n\n## Quick reference\n\n### `HealthKitManager`\n- `checkAuthorizationStatus()`\n- `requestAuthorization()`\n- `fetchSteps(for:)`\n- `fetchWater(for:)`\n- `fetchCalories(for:)`\n- `saveSteps(count:date:)`\n- `saveWater(amount:date:)`\n- `saveCalories(amount:date:)`\n- `getCombinedSteps(for:)`\n\n### `LiveActivityManager`\n- `shared`\n- `startWorkoutActivity(workoutName:)`\n- `updateWorkoutActivity(duration:exerciseCount:setCount:restTimerEndTime:isTimerActive:)`\n- `endWorkoutActivity()`\n\n### `WidgetDataProvider`\n- `refreshAllWidgetData()`\n- `fetchHeatmapData(range:metric:)`\n- `fetchWorkoutStats()`\n- `fetchPersonalRecords()`\n- `fetchMuscleGroupStats()`\n- `fetchExerciseVariety()`\n- `fetchRestDayData()`\n\n### `WidgetDataRefresher`\n- `shared`\n- `refreshWidgets(modelContext:)`\n- `reloadTimelines()`","home-backgrounds-weather":"# Home Backgrounds & Weather\n\n# Home Backgrounds & Weather\n\nThis module manages the app’s home-screen background selection, persistence, weather-driven preset resolution, and image rendering for export.\n\nIt is centered around three responsibilities:\n\n- storing the user’s background configuration\n- resolving the active `HomeBackgroundPreset` from configuration, time of day, and weather\n- fetching weather conditions from Open-Meteo using the device location\n\nThe module is used by the home UI, the background editor, profile views, and background export flows.\n\n## Architecture overview\n\n```mermaid\nflowchart LR\n UI[Home / Editor / Profile Views] --> M[HomeBackgroundManager]\n M --> S[HomeBackgroundStorage]\n M --> R[HomeBackgroundResolver]\n M --> W[HomeBackgroundWeatherProvider]\n W --> OM[OpenMeteoHomeBackgroundWeatherProvider]\n OM --> L[DeviceWeatherCoordinateProvider]\n OM --> O[OpenMeteoWeatherService]\n O --> MAPPER[OpenMeteoWeatherConditionMapper]\n R --> P[HomeBackgroundPreset]\n BG[Background export] --> BR[BackgroundRenderer]\n BR --> CV[BackgroundCanvasView]\n```\n\n## Core types\n\n### `HomeBackgroundManager`\n\n`HomeBackgroundManager` is the main entry point for the feature.\n\nIt is marked `@MainActor` and `@Observable`, so it is intended to be used directly from SwiftUI views and other UI-facing code.\n\nResponsibilities:\n\n- loads the persisted `HomeBackgroundConfiguration` on initialization\n- saves configuration changes through `HomeBackgroundStorage`\n- tracks the latest weather condition in `currentWeatherCondition`\n- resolves the current preset for the active configuration\n- supports preview resolution for arbitrary configurations\n- refreshes weather asynchronously through an injected provider\n\n#### Initialization\n\n```swift\ninit(\n storage: HomeBackgroundStorage = HomeBackgroundStorage(),\n weatherProvider: any HomeBackgroundWeatherProviding = HomeBackgroundWeatherProvider()\n)\n```\n\nOn init, the manager:\n\n1. creates its storage and weather provider dependencies\n2. loads the saved configuration from `UserDefaults`\n3. initializes `currentWeatherCondition` to `.unknown`\n\n#### Key methods\n\n- `apply(_:)`\n - updates the in-memory configuration\n - persists it via `HomeBackgroundStorage.save(_:)`\n\n- `useWeatherProvider(_:)`\n - swaps the weather provider implementation\n - resets `currentWeatherCondition` to `.unknown`\n - useful for tests or alternate data sources\n\n- `currentPreset(at:calendar:)`\n - resolves the active preset using:\n - the current configuration\n - the current day part from `HomeBackgroundDayPart.current(at:calendar:)`\n - the latest weather condition\n\n- `refreshWeatherCondition(force:) async`\n - fetches weather only when appropriate\n - if `force == false`, it only runs when:\n - `configuration.isEnabled == true`\n - `configuration.mode == .dynamic`\n - on failure, it falls back to `.unknown`\n\n- `previewPreset(for:)`\n - resolves a preset for a supplied configuration\n - uses `configuration.previewDayPart` and `configuration.previewWeatherCondition`\n - intended for editor previews and configuration UI\n\n### `HomeBackgroundResolver`\n\n`HomeBackgroundResolver` contains the preset selection logic.\n\nIt is a pure resolver: given a configuration, a day part, and a weather condition, it returns a `HomeBackgroundPreset?`.\n\n#### `resolve(configuration:dayPart:weatherCondition:)`\n\nBehavior:\n\n- returns `nil` when `configuration.isEnabled` is `false`\n- for `.staticImage` mode:\n - tries `configuration.selectedPresetID`\n - then `configuration.fallbackPresetID`\n - then defaults to `.clearDay`\n- for `.dynamic` mode:\n - delegates to `dynamicPreset(dayPart:weatherCondition:fallbackID:)`\n\n#### Dynamic resolution rules\n\n`dynamicPreset(dayPart:weatherCondition:fallbackID:)` applies the following logic:\n\n- if the day part is night-like:\n - `.storm` or `.rain` → `.stormNight`\n - everything else → `.clearNight`\n- during daytime:\n - `.clear` → `.evening` if `dayPart == .evening`, otherwise `.clearDay`\n - `.cloudy` → `.cloudyDay`\n - `.snow` → `.snowDay`\n - `.fog` → `.fogDay`\n - `.rain` or `.storm` → `.rainDay`\n - `.unknown` → preset from `fallbackID`, or `.clearDay` if missing\n\nThis resolver is the central place to update when adding new weather-to-preset behavior.\n\n### `HomeBackgroundStorage`\n\n`HomeBackgroundStorage` persists `HomeBackgroundConfiguration` in `UserDefaults`.\n\nIt uses `AppPreferenceKey.homeBackgroundConfiguration` as the storage key.\n\n#### `load()`\n\n- reads raw `Data` from `UserDefaults`\n- decodes `HomeBackgroundConfiguration` with `JSONDecoder`\n- returns `.default` if the value is missing or decoding fails\n\n#### `save(_:)`\n\n- encodes the configuration with `JSONEncoder`\n- writes the encoded data back to `UserDefaults`\n- silently ignores encoding failures\n\nThis storage layer is intentionally small and synchronous.\n\n### `HomeBackgroundWeatherProviding`\n\nA protocol used to abstract weather fetching.\n\n```swift\nprotocol HomeBackgroundWeatherProviding {\n func currentCondition() async throws -> HomeWeatherCondition\n}\n```\n\nThis abstraction allows the manager to work with either the real Open-Meteo-backed provider or a test double.\n\n### `HomeBackgroundWeatherProvider`\n\nA thin wrapper that conforms to `HomeBackgroundWeatherProviding`.\n\nIt delegates to another provider, defaulting to `OpenMeteoHomeBackgroundWeatherProvider`.\n\nThis wrapper exists so `HomeBackgroundManager` can depend on a stable type while still allowing dependency injection.\n\n### `OpenMeteoHomeBackgroundWeatherProvider`\n\nThis is the concrete weather pipeline used by the app.\n\nIt combines three dependencies:\n\n- `WeatherCoordinateProviding`\n- `OpenMeteoWeatherServicing`\n- `OpenMeteoWeatherConditionMapper`\n\n#### `currentCondition()`\n\nExecution flow:\n\n1. fetch the current coordinate from `coordinateProvider`\n2. fetch the current weather code from `service`\n3. map the weather code to `HomeWeatherCondition` using `mapper`\n\nThis provider is the bridge between location, network, and app-specific weather semantics.\n\n### `WeatherCoordinateProviding`\n\nProtocol for anything that can supply the current coordinate.\n\n```swift\nprotocol WeatherCoordinateProviding {\n func currentCoordinate() async throws -> WeatherCoordinate\n}\n```\n\n### `OpenMeteoWeatherServicing`\n\nProtocol for fetching weather data from Open-Meteo.\n\n```swift\nprotocol OpenMeteoWeatherServicing {\n func currentWeatherCode(for coordinate: WeatherCoordinate) async throws -> Int\n}\n```\n\n### `WeatherCoordinate`\n\nA simple value type used by the weather service layer.\n\n```swift\nstruct WeatherCoordinate: Equatable {\n let latitude: Double\n let longitude: Double\n}\n```\n\n### `OpenMeteoWeatherService`\n\nConcrete network client for Open-Meteo.\n\n#### `currentWeatherCode(for:)`\n\nBuilds a request to:\n\n`https://api.open-meteo.com/v1/forecast`\n\nwith query items:\n\n- `latitude`\n- `longitude`\n- `current=weather_code`\n- `timezone=auto`\n\nIt then:\n\n1. performs the request with `URLSession`\n2. validates that the response is HTTP 2xx\n3. decodes `OpenMeteoCurrentWeatherResponse`\n4. returns `payload.current.weatherCode`\n\nErrors are surfaced as `OpenMeteoWeatherError.invalidURL` or `OpenMeteoWeatherError.invalidResponse`, plus any decoding/network errors thrown by the session.\n\n### `OpenMeteoWeatherConditionMapper`\n\nMaps Open-Meteo weather codes to `HomeWeatherCondition`.\n\nMapping summary:\n\n- `0, 1` → `.clear`\n- `2, 3` → `.cloudy`\n- `45, 48` → `.fog`\n- `51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82` → `.rain`\n- `71, 73, 75, 77, 85, 86` → `.snow`\n- `95, 96, 99` → `.storm`\n- anything else → `.unknown`\n\nThis mapping is intentionally isolated so weather-code changes can be updated without touching the rest of the pipeline.\n\n### `DeviceWeatherCoordinateProvider`\n\n`DeviceWeatherCoordinateProvider` is the location-backed implementation of `WeatherCoordinateProviding`.\n\nIt is `@MainActor`, subclasses `NSObject`, and conforms to `CLLocationManagerDelegate`.\n\n#### Behavior\n\n- configures `CLLocationManager` with `kCLLocationAccuracyThreeKilometers`\n- returns the current cached location immediately if available\n- otherwise requests authorization or a one-shot location update\n- resumes the awaiting continuation when a location or error arrives\n\n#### Delegate flow\n\n- `locationManagerDidChangeAuthorization(_:)`\n - requests location when authorization becomes available\n - fails when denied/restricted\n\n- `locationManager(_:didUpdateLocations:)`\n - uses the last location in the array\n - converts it to `WeatherCoordinate`\n\n- `locationManager(_:didFailWithError:)`\n - forwards the error to the awaiting caller\n\nThe provider uses a single `CheckedContinuation` and clears it after completion.\n\n### `OpenMeteoWeatherError`\n\nErrors specific to the Open-Meteo weather pipeline:\n\n- `invalidURL`\n- `invalidResponse`\n- `locationUnavailable`\n\n## Rendering\n\n### `BackgroundRenderer`\n\n`BackgroundRenderer` renders a background preview/export image from a `BackgroundConfiguration`.\n\n```swift\n@MainActor\nclass BackgroundRenderer {\n let config: BackgroundConfiguration\n let size: CGSize\n\n func render() -> UIImage?\n}\n```\n\n#### `render()`\n\n- creates a `BackgroundCanvasView(config:)`\n- constrains it to the requested `size`\n- renders it using `ImageRenderer`\n- sets `renderer.scale = 3.0`\n- returns the resulting `UIImage`\n\nThis type is used by the background export flow, not by the weather resolution logic.\n\n## Execution flows\n\n### Current preset resolution\n\nWhen the UI asks for the active home background:\n\n1. `HomeView` or `ProfileView` calls `HomeBackgroundManager.currentPreset(...)`\n2. the manager forwards to `HomeBackgroundResolver.resolve(...)`\n3. the resolver chooses a preset based on:\n - `configuration.isEnabled`\n - `configuration.mode`\n - `currentWeatherCondition`\n - current day part\n\n### Weather refresh\n\nWhen the app needs to update weather:\n\n1. `HomeView` or `HomeBackgroundEditorView` calls `HomeBackgroundManager.refreshWeatherCondition(force:)`\n2. the manager checks whether weather should be fetched\n3. if allowed, it calls `weatherProvider.currentCondition()`\n4. the provider chain resolves:\n - device coordinate\n - Open-Meteo weather code\n - app weather condition\n5. the manager stores the result in `currentWeatherCondition`\n\nIf anything fails, the manager falls back to `.unknown`.\n\n### Preview resolution\n\nThe editor uses preview resolution to show how a configuration will look without applying it:\n\n1. `HomeBackgroundEditorView` calls `HomeBackgroundManager.previewPreset(for:)`\n2. the manager forwards to `HomeBackgroundResolver.resolve(...)`\n3. the resolver uses:\n - `configuration.previewDayPart`\n - `configuration.previewWeatherCondition`\n\nThis keeps preview behavior aligned with runtime behavior.\n\n### Background export\n\nThe export path is separate from weather resolution:\n\n1. `exportBackground(...)`\n2. `exportBackgroundAsImage(...)`\n3. `BackgroundRenderer.render()`\n4. `BackgroundCanvasView`\n\nThis module provides the renderer used by that flow.\n\n## Integration points in the app\n\n### Views\n\nThe manager is consumed by:\n\n- `HomeView`\n- `ProfileView`\n- `HomeBackgroundEditorView`\n\nThe editor also calls `resolve(...)` directly for previewing presets.\n\n### Tests\n\nThe module is covered by tests that validate:\n\n- static preset selection\n- dynamic preset selection for clear, fog, snow, storm, and unknown weather\n- night-time behavior\n- Open-Meteo weather-code mapping\n- dependency injection into `OpenMeteoHomeBackgroundWeatherProvider`\n\nThese tests are especially important because the resolver and mapper encode the feature’s user-visible behavior.\n\n## Notes for contributors\n\n### When changing preset behavior\n\nUpdate `HomeBackgroundResolver.dynamicPreset(...)` and add or adjust tests in `HomeBackgroundResolverTests`.\n\n### When adding weather codes\n\nUpdate `OpenMeteoWeatherConditionMapper.condition(for:)` and verify the resulting preset selection still matches the intended UX.\n\n### When changing persistence\n\nKeep `HomeBackgroundStorage` compatible with existing encoded `HomeBackgroundConfiguration` values, since it reads from `UserDefaults` directly.\n\n### When testing weather flows\n\nPrefer injecting custom implementations of:\n\n- `HomeBackgroundWeatherProviding`\n- `WeatherCoordinateProviding`\n- `OpenMeteoWeatherServicing`\n\nThis avoids network and location dependencies in tests.\n\n### When working with location permissions\n\n`DeviceWeatherCoordinateProvider` assumes the app has the necessary location usage description and permission flow elsewhere in the app. If authorization is denied or unavailable, weather resolution degrades gracefully to `.unknown`.\n\n## Summary\n\nThis module provides the complete home-background pipeline:\n\n- `HomeBackgroundStorage` persists configuration\n- `HomeBackgroundManager` coordinates state and weather refreshes\n- `HomeBackgroundResolver` turns configuration plus context into a preset\n- `OpenMeteoHomeBackgroundWeatherProvider` fetches and maps live weather\n- `BackgroundRenderer` renders background imagery for export\n\nThe design keeps the resolver pure, the weather pipeline injectable, and the UI-facing manager simple to consume from SwiftUI.","home-dashboard":"# Home Dashboard\n\n# Home Dashboard Module\n\nThe Home Dashboard is the app’s landing experience. It combines workout recommendations, recent training summary widgets, active workout controls, achievement celebration, and home wallpaper configuration into a single SwiftUI-driven surface.\n\nThis module is centered around three view files:\n\n- `HomeView`\n- `HomeBackgroundEditorView`\n- `HomeBackgroundViews`\n\nIt also includes a few reusable preview/component views that support the dashboard and related editors.\n\n---\n\n## Responsibilities\n\nThe Home Dashboard is responsible for:\n\n- showing a personalized greeting or achievement/streak header\n- surfacing the current workout recommendation\n- summarizing recent training stats over a selectable date range\n- presenting an active workout control panel when a session is running\n- showing and dismissing achievement celebrations\n- applying wallpaper/background readability treatment based on theme and preset\n- editing the home background configuration\n\n---\n\n## Main entry point: `HomeView`\n\n`HomeView` is the primary dashboard screen. It is a `SwiftUI.View` that pulls data from SwiftData, app storage, and environment services, then renders the dashboard inside a `VerticalSplitView`.\n\n### Data sources\n\n`HomeView` reads from:\n\n- `@Environment(\\.modelContext)` for inserting a default `UserProfile`\n- `@Environment(AppRouter.self)` for navigation\n- `@Environment(HomeBackgroundManager.self)` for wallpaper state and weather refresh\n- `@Environment(ThemeManager.self)` for dark/light appearance\n- multiple `@Query` collections:\n - `UserProfile`\n - `WorkoutTemplate`\n - `WorkoutSession`\n - `WorkoutPlan`\n - `UserWorkoutPlan`\n - `Exercise`\n - `AchievementEvent`\n - `PendingAchievementCelebration`\n- `@AppStorage` preferences:\n - haptics\n - workout reminder time\n - daily calorie goal settings\n - home stats widget order\n\n### High-level layout\n\n`HomeView.body` uses a `VerticalSplitView` with:\n\n- `topView`: dashboard cards and summary content\n- `bottomView`: expanded workout session content or workout start sheet\n- `bottomMiniOverlay`: collapsed workout control bar\n\nIt also overlays achievement celebrations and applies blur while a celebration is visible.\n\n```mermaid\nflowchart TD\n A[HomeView] --> B[VerticalSplitView]\n B --> C[homeCardsContent]\n B --> D[expandedWorkoutContent]\n B --> E[collapsedWorkoutControl]\n A --> F[AchievementCelebrationOverlayView]\n C --> G[headerSection]\n C --> H[primaryWorkoutRecommendation]\n C --> I[lastWeekSummaryCard]\n```\n\n---\n\n## Dashboard state and behavior\n\n`HomeView` maintains several pieces of local UI state:\n\n- `headerMode`: cycles between greeting, streak, and badge\n- `splitDetent`: controls collapsed vs expanded workout panel\n- `showWorkoutStartSheet`: presents the workout start flow\n- `showStatsWidgetConfiguration`: presents widget ordering/configuration\n- `selectedStatsPreset`, `customStartDate`, `customEndDate`: control the stats range\n- `pendingSession`: temporarily holds a session selected from the start sheet\n- `showExpandedWorkoutContent`: delays rendering of the expanded workout view for smoother transitions\n- `lastCelebrationIdentifier`: prevents repeated haptic feedback for the same celebration\n- `primaryWorkoutRecommendation`: cached recommendation shown on the dashboard\n\n### Session-driven transitions\n\nThe dashboard reacts to active workout session changes:\n\n- when no active session exists:\n - clears `pendingSession`\n - collapses the split view if needed\n - refreshes the primary recommendation\n- when a session becomes active:\n - stores it in `pendingSession`\n - clears the recommendation\n\nThis keeps the dashboard aligned with the workout lifecycle.\n\n### Recommendation refresh\n\n`HomeView` computes a `recommendationRefreshSignature` from:\n\n- template IDs\n- recent session IDs and progress signatures\n- workout plan IDs\n- user workout plan IDs and progress signatures\n- exercise count\n- reminder preferences\n- calorie goal preferences\n\nThat signature is used as the `.task(id:)` trigger for `refreshPrimaryWorkoutRecommendation()`, so recommendation recalculation happens when relevant data changes.\n\n### Background weather refresh\n\n`HomeView` also computes `homeBackgroundWeatherRefreshSignature` from the background configuration. When it changes, the view triggers:\n\n- `homeBackgroundManager.refreshWeatherCondition()`\n\nThis keeps dynamic wallpaper state in sync with the current configuration.\n\n---\n\n## Header section\n\n`headerSection` is the top-left dashboard header. It shows:\n\n- the current date\n- a rotating headline:\n - greeting\n - workout streak\n - latest achievement badge\n- the profile avatar button on the right\n\n### Header rotation\n\nThe header cycles every 5 seconds using a `.task` attached to the text container. The available modes depend on whether there is a latest achievement:\n\n- without achievements: greeting and streak only\n- with achievements: greeting, streak, and badge\n\n### Profile button\n\nThe profile button:\n\n- navigates to `.profile`\n- shows the user photo if available\n- falls back to a person icon otherwise\n\nIn debug builds it also prints navigation state to help trace split-view behavior.\n\n---\n\n## Workout recommendation card\n\nIf `primaryWorkoutRecommendation` is available, `HomeView` renders one of two cards:\n\n- `HomeActivePlanCard` for active plan recommendations\n- `WorkoutRecommendationPreviewCard` for other recommendation types\n\n### `HomeActivePlanCard`\n\nThis card highlights:\n\n- the active plan label\n- the recommended day\n- the plan title\n- schedule badge text\n\nTapping it calls `openWorkoutStartSheet()`.\n\nThe card uses `HomeWallpaperReadabilityStyle` to adjust surface opacity and border contrast against the current wallpaper.\n\n### Recommendation generation\n\n`refreshPrimaryWorkoutRecommendation()` calls `UnifiedWorkoutRecommendationService.primaryRecommendation(...)` with:\n\n- workout plans\n- enrollments\n- templates\n- recent sessions\n- exercises\n- reminder preferences\n- goal preferences\n\nIt intentionally suppresses recommendations while an active workout session exists.\n\n---\n\n## Training summary section\n\n`lastWeekSummaryCard` is the dashboard’s stats area. It lets the user:\n\n- choose a preset range from `StatsRangePreset`\n- configure custom start/end dates when the preset is `.custom`\n- open the widget configuration sheet\n\nIf the selected range produces stats, the view renders:\n\n- a configurable grid of stat tiles\n- a trend chart\n\nOtherwise it shows an empty state.\n\n### Range selection\n\n`selectedStatsRange` is derived from:\n\n- the selected preset\n- the current date\n- `customStatsRange` when the preset is custom\n\n`customStatsRange` normalizes the start/end dates so the interval always spans whole days and the end includes the full final day.\n\n### Stats calculation\n\n`selectedStats` calls:\n\n- `calculateTrainingStats(for:from:calendar:)`\n\nThis is the main bridge into the training stats utility layer.\n\n### Empty state\n\nWhen no workouts exist in the selected range, the dashboard shows:\n\n- “No workouts in this range”\n- a short explanation encouraging completed sessions\n\n---\n\n## Widget ordering and configuration\n\nThe dashboard’s stat tiles are driven by `HomeStatWidgetKind`.\n\n### `HomeStatWidgetKind`\n\nThis enum defines the available dashboard widgets:\n\n- `activeTime`\n- `volume`\n- `workouts`\n- `calories`\n- `setsAndReps`\n- `topExercise`\n- `workoutDays`\n- `averageSession`\n- `averageVolume`\n- `volumeRecord`\n- `deloadLow`\n\nEach widget provides:\n\n- `title`\n- `icon`\n- `isAvailable(for:)`\n- `value(for:)`\n- `detail(for:)`\n\nOnly `topExercise` is conditionally hidden when no highlight exists.\n\n### Stored order\n\nWidget order is persisted in:\n\n- `@AppStorage(AppPreferenceKey.homeStatsWidgetOrder)`\n\nThe enum provides:\n\n- `decodeOrder(from:)`\n- `encodeOrder(_:)`\n- `normalizedOrder(_:)`\n\nThe order is sanitized to a maximum of 6 widgets.\n\n### Configuration sheet\n\n`HomeStatsWidgetConfigurationSheet` lets the user:\n\n- reorder selected widgets\n- remove widgets\n- add from the available pool\n- apply or cancel changes\n\nIt uses a `List` with:\n\n- a `Selected` section\n- an `Available` section\n\nThe sheet keeps at least one widget selected and enforces the maximum count.\n\n### Layout logic\n\n`widgetLayoutRows(for:)` groups widgets into rows:\n\n- compact rows contain up to two widgets\n- wide rows are emitted separately\n\nThis is used by `weeklyStatsWidgets(_:)` to build the dashboard grid.\n\n---\n\n## Trend chart\n\n`HomeStatsChartWidget` renders a `Charts.Chart` over `stats.dailyMetrics`.\n\nIt plots:\n\n- volume\n- calories\n- active minutes\n\nIt also marks notable days:\n\n- volume PR\n- deload low\n- calorie PR\n\nThe chart uses:\n\n- a custom foreground style scale\n- weekday labels on the x-axis\n- a leading y-axis\n\nThis widget is designed to be readable against both wallpaper and fallback backgrounds.\n\n---\n\n## Active workout experience\n\nWhen a workout session is active, the bottom split area becomes the workout control surface.\n\n### Collapsed control bar\n\n`collapsedWorkoutControl` is the mini overlay shown when the split is collapsed. It:\n\n- opens the workout session if one is active\n- opens the workout start sheet if no session exists\n- shows an active ring animation when a session is running\n- shows a compact stats panel with elapsed time, sets, volume, and calories\n- includes a spark chart preview for the active session\n\n### Expanded workout content\n\n`expandedWorkoutContent` switches between:\n\n- `WorkoutSessionView` when a session exists\n- `WorkoutStartSheet` when the user is starting a workout\n- `EmptyView` otherwise\n\nThe expanded session view receives:\n\n- `onBack` to collapse the split\n- `isSessionVisible` to coordinate visibility\n- `onExerciseSearchFocusChange` to track search focus state\n\n### `openWorkoutStartSheet()`\n\nThis helper decides whether to:\n\n- expand to the full workout session if a session already exists\n- expand and present `WorkoutStartSheet` if no session exists\n\n### `CollapsedWorkoutStatsPanel`\n\nThis panel uses `TimelineView` and `ActiveWorkoutProgressSnapshot` to update every second.\n\nIt displays:\n\n- elapsed time\n- completed sets\n- total volume\n- estimated calories\n\n### `ActiveWorkoutSparkChart`\n\nThis is a compact, accessibility-hidden chart that visualizes per-exercise normalized bars for:\n\n- volume\n- calories\n- active minutes\n\nIt is intentionally decorative and used as a subtle background accent in the collapsed control.\n\n### `ActiveWorkoutProgressSnapshot`\n\nThis helper aggregates session data into display-ready values.\n\nIt computes:\n\n- elapsed time\n- completed set count\n- total volume\n- estimated calories\n- normalized exercise bars\n\nIt also includes helper logic for:\n\n- compact exercise labels\n- compact weight formatting\n\n`compactWeight(_:)` is used by `HomeView` and returns a short textual representation, including a `t` suffix for values over 1000.\n\n---\n\n## Achievement celebration flow\n\nIf there is a pending celebration, `HomeView` overlays `AchievementCelebrationOverlayView`.\n\n### Dismissal\n\n`dismissCurrentCelebration(_:)` consumes the celebration through:\n\n- `AchievementService.shared.consumeHomeCelebration(_:in:)`\n\nThe overlay dismissal is animated and tied to the current celebration identifier.\n\n### Haptic feedback\n\n`triggerHomeCelebrationFeedbackIfNeeded()`:\n\n- tracks the last celebration identifier\n- avoids duplicate feedback\n- triggers a success notification via `UINotificationFeedbackGenerator` when haptics are enabled\n\nThis method is called:\n\n- on appear\n- when the pending celebration identifier changes\n\n---\n\n## Wallpaper and readability support\n\n`HomeView` uses `HomeWallpaperReadabilityStyle` from `HomeBackgroundViews.swift` to adapt card opacity and borders to the current wallpaper.\n\n### `HomeWallpaperReadabilityStyle`\n\nThis struct derives readability settings from:\n\n- the current `HomeBackgroundPreset`\n- whether the app is in dark appearance\n\nIt exposes:\n\n- `needsLightReadabilityTreatment`\n- `surfaceOpacity`\n- `prominentSurfaceOpacity`\n- `controlSurfaceOpacity`\n- `borderOpacity`\n- `prominentBorderOpacity`\n\nThese values are used throughout the dashboard to keep text and cards legible.\n\n### `HomeBackgroundLayer`\n\nThis view renders the actual wallpaper layer:\n\n- the selected preset image when enabled\n- a fallback gradient when disabled or unavailable\n- a readability overlay on top of the image\n\n### `HomeFallbackBackground`\n\nA simple gradient fallback used when no wallpaper preset is active.\n\n### `HomeBackgroundPreviewCard`\n\nUsed by the background editor to preview the selected preset or fallback background.\n\n---\n\n## Background editor: `HomeBackgroundEditorView`\n\n`HomeBackgroundEditorView` is the configuration screen for home wallpaper settings.\n\nIt edits a local `draft` of `HomeBackgroundConfiguration` and applies changes through `HomeBackgroundManager`.\n\n### Lifecycle\n\nOn first load, the editor copies the current manager configuration into the draft.\n\nIt also refreshes weather when:\n\n- the draft is enabled\n- the draft mode is dynamic\n\n### Toolbar actions\n\n- `Cancel` dismisses without saving\n- `Apply` calls `homeBackgroundManager.apply(draft)` and dismisses\n\n### Sections\n\nThe editor is composed of several focused sections:\n\n- `HomeBackgroundPreviewCard`\n- `HomeBackgroundToggleSection`\n- `HomeBackgroundModeSection`\n- `HomeBackgroundPresetSection`\n- `HomeBackgroundDynamicSection`\n\n### Static image mode\n\nWhen `draft.mode == .staticImage`, the editor shows `HomeBackgroundPresetSection`, which presents horizontally scrollable preset cards.\n\n### Dynamic mode\n\nWhen `draft.mode != .staticImage`, the editor shows `HomeBackgroundDynamicSection`, which lets the user preview:\n\n- day part\n- weather condition\n\nIt also displays live weather information from `HomeBackgroundManager`.\n\n### Weather info helpers\n\n`HomeBackgroundWeatherInfoBar` and `HomeBackgroundWeatherInfoItem` present:\n\n- live weather status\n- preview state\n\nThe `HomeWeatherCondition` extension maps conditions to:\n\n- display titles\n- SF Symbols\n- tint colors\n\n---\n\n## Supporting views\n\n### `HomeBackgroundToggleSection`\n\nA simple toggle section for enabling/disabling the home wallpaper feature.\n\n### `HomeBackgroundModeSection`\n\nLets the user choose between `HomeBackgroundMode` cases using button rows.\n\n### `HomeBackgroundPresetSection`\n\nDisplays preset cards in a horizontal scroll view.\n\n### `HomeBackgroundDynamicSection`\n\nProvides segmented pickers for:\n\n- `HomeBackgroundDayPart`\n- `HomeWeatherCondition`\n\nIt also normalizes `.unknown` preview weather to `.clear` on load.\n\n### `HomeBackgroundPresetCard`\n\nA selectable preset thumbnail card with title and subtitle overlay.\n\n---\n\n## Other reusable components in this module\n\n### `FormattedCompactDatePicker`\n\nA compact date picker wrapper used by the custom stats range UI.\n\nIt overlays a formatted label on top of a nearly transparent `DatePicker`, giving the appearance of a custom control while preserving native date selection behavior.\n\n### `ActiveWorkoutIconRing`\n\nAn animated ring used in the collapsed workout control when a session is active.\n\n### `LastWeekMetricTile`, `LastWeekSmallHighlightTile`, `compactMetaItem`\n\nThese are lightweight tile components defined in `HomeView.swift`. They are not all currently used in the main dashboard layout, but they follow the same visual language as the rest of the module and may support future summary layouts.\n\n### `MiniAppPreview`\n\nA generic theme preview component used elsewhere in the app, not directly by `HomeView`, but part of the same visual ecosystem.\n\n### `MuscleIllustrationCard`\n\nA separate component used by exercise guidance screens. It is not part of the home dashboard flow, but it shares the same design system and model conventions.\n\n---\n\n## Integration points with the rest of the app\n\n### Navigation\n\n`HomeView` uses `AppRouter` to navigate to:\n\n- `.profile`\n- `.recordsHistory`\n\n### Workout flow\n\nThe dashboard connects to:\n\n- `WorkoutSessionView`\n- `WorkoutStartSheet`\n- `CollapsedWorkoutStatsPanel`\n- `ActiveWorkoutProgressSnapshot`\n\n### Recommendation flow\n\nIt depends on:\n\n- `UnifiedWorkoutRecommendationService`\n- `WorkoutReminderPreferences`\n- `WorkoutGoalPreferences`\n\n### Achievement flow\n\nIt depends on:\n\n- `AchievementService`\n- `AchievementCelebrationOverlayView`\n- `PendingAchievementCelebration`\n\n### Background flow\n\nIt depends on:\n\n- `HomeBackgroundManager`\n- `HomeBackgroundResolver`\n- `HomeBackgroundConfiguration`\n- `HomeBackgroundPreset`\n- `HomeWeatherCondition`\n\n### Stats flow\n\nIt depends on:\n\n- `calculateTrainingStats`\n- `TrainingSummaryStats`\n- `StatsRangePreset`\n\n---\n\n## Contribution notes\n\n### Keep recommendation refresh cheap and deterministic\n\n`recommendationRefreshSignature` is the mechanism that prevents unnecessary recomputation. If you add new inputs that affect recommendations, include them in the signature.\n\n### Preserve widget order normalization\n\n`HomeStatWidgetKind.normalizedOrder(_:)` enforces the maximum widget count. Any new widget configuration path should use it before saving.\n\n### Maintain readability treatment consistency\n\nIf you add new dashboard cards or overlays, use `HomeWallpaperReadabilityStyle` rather than hardcoding opacity values.\n\n### Be careful with session state transitions\n\nThe split view and workout sheet logic depend on `activeSession`, `pendingSession`, and `splitDetent` staying in sync. Changes to one should be reviewed against the `.onChange(of: activeSession == nil)` and `.onChange(of: splitDetent)` handlers.\n\n### Avoid duplicate celebration feedback\n\n`lastCelebrationIdentifier` is used to prevent repeated haptic feedback. If celebration behavior changes, preserve that guard or replace it with equivalent deduplication.\n\n---\n\n## Preview setup\n\n`HomeView` and `HomeBackgroundEditorView` both include previews configured with in-memory model containers and shared environment objects.\n\nThe `HomeView` preview seeds the model container with the entities needed for the dashboard to render without crashing:\n\n- `UserProfile`\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `WorkoutTemplate`\n- `Exercise`\n- `AchievementEvent`\n- `PendingAchievementCelebration`\n\nThis is useful when extending the dashboard because missing SwiftData entities can otherwise cause preview failures.","notifications-rest-timer":"# Notifications & Rest Timer\n\n# Notifications & Rest Timer\n\nThis module covers two related notification flows in the app:\n\n- the in-workout **rest timer**, which tracks countdown state, updates the UI, schedules a completion notification, and syncs with Live Activities\n- **workout reminder notifications**, which turn a recommended workout time into a `UNNotificationRequest`\n\nThe code is split across small, focused types:\n\n- `RestTimerManager` owns the active rest timer lifecycle\n- `RestTimerView` renders and controls the timer UI\n- `WorkoutReminderPreferences` and `WorkoutReminderPreferencesStore` persist reminder settings\n- `WorkoutReminderNotificationScheduler` converts a recommendation into a notification request\n- `WorkoutReminderSettingsControls` provides the settings UI for reminder preferences\n\n---\n\n## Rest timer architecture\n\n`RestTimerManager` is the central state holder for rest periods during a workout session. It is marked `@Observable` and `@MainActor`, so SwiftUI views can bind to it directly and all state mutations happen on the main thread.\n\nIt keeps two sources of truth in sync:\n\n1. **Transient UI state**\n - `timeRemaining`\n - `isActive`\n - `isPaused`\n - `endTime`\n - `configuredDuration`\n\n2. **Persistent session state**\n - `WorkoutSession.isRestTimerActive`\n - `WorkoutSession.isRestTimerPaused`\n - `WorkoutSession.restTimerEndTime`\n - `WorkoutSession.restTimerDuration`\n - `WorkoutSession.restTimerPausedRemaining`\n\nThis design lets the timer survive view refreshes and app state changes by reconstructing itself from `WorkoutSession` via `updateFromSession()`.\n\n### Timer lifecycle\n\nThe timer lifecycle is managed through these public methods:\n\n- `startTimer(duration:)`\n- `pauseTimer()`\n- `resumeTimer()`\n- `restartTimer()`\n- `stopTimer()`\n- `adjustTime(by:)`\n- `updateFromSession()`\n\nA typical flow is:\n\n1. `startTimer(duration:)` sanitizes the duration, updates the session, schedules a notification, updates the Live Activity, and starts a 1-second polling timer.\n2. `startPolling()` fires every second and calls `updateTimer()`.\n3. `updateTimer()` decrements the visible countdown by recomputing remaining time from `endTime`.\n4. When time reaches zero, `stopTimer()` is called and `playAlert()` triggers haptic feedback if enabled.\n\n### State synchronization\n\n`updateFromSession()` is the recovery path. It is used when the manager is created or when the session state changes externally.\n\nBehavior depends on the session flags:\n\n- if paused, it restores `timeRemaining` from `restTimerPausedRemaining` and cancels any pending notification\n- if active, it computes remaining time from `restTimerEndTime`\n- if the timer has already expired, it stops the timer and plays the alert\n- otherwise it resumes polling\n\nThis makes the manager resilient to app restarts, navigation changes, and session-driven updates.\n\n---\n\n## Rest timer notification behavior\n\n`RestTimerManager` schedules a local notification when the timer is running and cancels it when the timer is paused or stopped.\n\n### Notification flow\n\n- `startTimer(duration:)` → `scheduleNotification(at:)`\n- `resumeTimer()` → `scheduleNotification(at:)`\n- `adjustTime(by:)` → `scheduleNotification(at:)`\n- `pauseTimer()` → `cancelNotification()`\n- `stopTimer()` → `cancelNotification()`\n- `updateFromSession()` cancels notifications when restoring a paused timer\n\n`notificationIdentifier` is fixed to `\"rest_timer_done\"`, so the manager always replaces or removes the same pending request instead of accumulating duplicates.\n\n### Authorization\n\n`requestNotificationAuthorizationIfNeeded()` requests `.alert`, `.sound`, and `.badge` permissions from `UNUserNotificationCenter`.\n\nThe initializer calls this only when `notificationsEnabled` is true, which is read from `UserDefaults`.\n\n### Notification content\n\n`RestTimerManager` builds a simple completion notification:\n\n- title: `\"Rest Timer Done\"`\n- body: `\"Time to start your next set!\"`\n- sound: controlled by `soundEnabled`\n\nThe trigger is a `UNCalendarNotificationTrigger` built from the target date components.\n\n---\n\n## Live Activity integration\n\nEvery timer state change calls `updateLiveActivity()`, which forwards the current workout summary to `LiveActivityManager.shared.updateWorkoutActivity(...)`.\n\nThe payload includes:\n\n- `session.duration`\n- exercise count from `session.exerciseLogs.count`\n- set count from `session.exerciseLogs.reduce(0) { $0 + $1.sets.count }`\n- `restTimerEndTime`\n- `isTimerActive`\n\nThis keeps the Live Activity aligned with the rest timer state, even when the timer is paused or adjusted.\n\n---\n\n## Haptics and completion alert\n\nWhen the countdown reaches zero, `updateTimer()` stops the timer and calls `playAlert()`.\n\n`playAlert()` uses `UINotificationFeedbackGenerator` and only runs when `hapticEnabled` is true.\n\nThis means timer completion feedback is split across two channels:\n\n- **local notification** for background or locked-screen completion\n- **haptic feedback** for in-app completion\n\n---\n\n## `RestTimerManager`\n\n`RestTimerManager` is the core timer controller.\n\n### Stored state\n\n- `timeRemaining`: countdown value shown in the UI\n- `isActive`: whether the timer is currently running\n- `isPaused`: whether the timer is paused\n- `endTime`: absolute end date for the current run\n- `configuredDuration`: the current configured duration, used for restart and adjustment logic\n\n### Session coupling\n\nThe manager stores a `WorkoutSession` reference and mirrors timer state into it. This is what allows the timer to be reconstructed later.\n\n### Preference keys\n\nThe manager reads timer-related preferences from `UserDefaults` using private keys:\n\n- `defaultRestTime`\n- `notificationsEnabled`\n- `soundEnabled`\n- `hapticEnabled`\n\n### Default duration\n\n`globalDefaultDuration` reads `defaultRestTime` from `UserDefaults` and falls back to `90` seconds when no value is stored.\n\n### Important implementation details\n\n- `startTimer(duration:)` clamps the duration to at least 1 second.\n- `pauseTimer()` stores the remaining time in `session.restTimerPausedRemaining`.\n- `resumeTimer()` restores from `session.restTimerPausedRemaining` when available.\n- `adjustTime(by:)` updates both the configured duration and the remaining time while the timer is active.\n- `restartTimer()` uses `session.restTimerDuration` first, then `configuredDuration`, then a minimum of 1 second.\n- `stopTimer()` clears all timer-related session fields and resets local state.\n\n### Polling model\n\n`startPolling()` creates a repeating `Timer` with a 1-second interval. The timer callback hops back onto the main actor and calls `updateTimer()`.\n\nThis is intentionally simple and UI-friendly, but it means the countdown is derived from wall-clock time rather than from accumulated ticks. That makes it more robust if the app is briefly delayed.\n\n---\n\n## `RestTimerView`\n\n`RestTimerView` is the SwiftUI component that presents and controls the rest timer.\n\nIt binds to `RestTimerManager` using `@Bindable`, so changes in the manager automatically update the view.\n\n### UI behavior\n\nThe view shows:\n\n- a label that switches between:\n - `\"REST TIME\"`\n - `\"REST PAUSED\"`\n- a monospaced countdown display\n- pause/resume and stop buttons\n- `+30` and `-30` adjustment buttons\n\n### Actions\n\nThe controls call directly into `RestTimerManager`:\n\n- pause button:\n - `pauseTimer()` when active\n - `resumeTimer()` when paused\n- stop button:\n - `stopTimer()`\n- `+30` button:\n - `adjustTime(by: 30)`\n- `-30` button:\n - `adjustTime(by: -30)`\n\n### Formatting\n\n`formattedTime(_:)` renders the countdown as `m:ss`, rounding up to avoid showing `0:00` too early.\n\n### Visual state\n\nThe background and text styling change based on `timerManager.isPaused`, which gives the user a clear paused vs active state.\n\n### Helper shape\n\n`RoundedCorner` is a small `Shape` wrapper around `UIBezierPath` used by `splitAdjustButton(...)` to render the split `+30 / -30` control with different rounded corners.\n\n---\n\n## Timer flow diagram\n\n```mermaid\nflowchart TD\n A[startTimer(duration:)] --> B[scheduleNotification(at:)]\n A --> C[updateLiveActivity()]\n A --> D[startPolling()]\n D --> E[updateTimer()]\n E --> F{remaining <= 0?}\n F -- no --> E\n F -- yes --> G[stopTimer()]\n G --> H[cancelNotification()]\n G --> I[updateLiveActivity()]\n G --> J[playAlert()]\n```\n\n---\n\n## Workout reminder preferences\n\n`WorkoutReminderPreferences` stores the user’s reminder settings:\n\n- `isEnabled`\n- `reminderHour`\n- `reminderMinute`\n\nIt is `Equatable`, which makes it easy to compare and test.\n\n### Sanitization\n\nThe initializer clamps values into valid clock ranges:\n\n- hour: `0...23`\n- minute: `0...59`\n\nThis prevents invalid persisted values from leaking into scheduling logic.\n\n### Planned reminder date\n\n`plannedReminderDate(referenceDate:calendar:)` computes the next reminder occurrence at the configured time.\n\nBehavior:\n\n- it builds a date using the current day and the stored hour/minute\n- if that time is still in the future today, it returns that date\n- otherwise it returns the same time on the next day\n\nThis is the core date calculation used by reminder scheduling and recommendation logic elsewhere in the app.\n\n---\n\n## `WorkoutReminderPreferencesStore`\n\n`WorkoutReminderPreferencesStore` persists `WorkoutReminderPreferences` to `UserDefaults`.\n\n### Stored keys\n\nIt uses `AppPreferenceKey` values:\n\n- `workoutReminderEnabled`\n- `workoutReminderHour`\n- `workoutReminderMinute`\n\n### Read behavior\n\nThe getter reconstructs a `WorkoutReminderPreferences` value from defaults and falls back to:\n\n- `false` for enabled state\n- `18:00` for reminder time\n\nThe helper `storedInteger(forKey:defaultValue:)` distinguishes between:\n\n- a missing key, which uses the default\n- a stored integer value, which may be `0`\n\nThat matters because `UserDefaults.integer(forKey:)` returns `0` for missing keys as well as stored zero values.\n\n### Write behavior\n\nThe setter writes all three fields back to `UserDefaults` immediately.\n\n---\n\n## `WorkoutReminderNotificationScheduler`\n\n`WorkoutReminderNotificationScheduler` converts an `UpcomingWorkoutRecommendation` into a `UNNotificationRequest`.\n\nIt is intentionally lightweight and does not schedule the request itself; it only builds the request object.\n\n### Dependencies\n\n- `Calendar`\n- `UpcomingWorkoutRecommendationFormatter`\n\nThe formatter is used to generate the notification title and body from the recommendation content, keeping notification copy consistent with the rest of the app.\n\n### `descriptor(for:preferences:)`\n\nReturns a `WorkoutReminderNotificationDescriptor?`.\n\nIt returns `nil` when reminders are disabled.\n\nOtherwise it produces:\n\n- `identifier`: `\"workout-reminder-\\(String(describing: recommendation.templateID))\"`\n- `title`: formatted recommendation title\n- `body`: formatted recommendation body\n- `triggerDate`: `recommendation.plannedReminderDate`\n\n### `request(for:preferences:soundEnabled:)`\n\nBuilds a `UNNotificationRequest` from the descriptor.\n\nIt:\n\n- reuses `descriptor(for:preferences:)`\n- creates `UNMutableNotificationContent`\n- sets sound based on `soundEnabled`\n- creates a `UNCalendarNotificationTrigger` from the descriptor date\n- returns the request\n\nThis keeps the scheduling logic deterministic and easy to test.\n\n---\n\n## `WorkoutReminderNotificationDescriptor`\n\n`WorkoutReminderNotificationDescriptor` is a simple `Equatable` value type that captures the notification payload before it becomes a `UNNotificationRequest`.\n\nFields:\n\n- `identifier`\n- `title`\n- `body`\n- `triggerDate`\n\nThis is useful for testing and for separating content generation from notification API details.\n\n---\n\n## `WorkoutReminderSettingsControls`\n\n`WorkoutReminderSettingsControls` is the SwiftUI settings component for reminder preferences.\n\nIt exposes two bindings:\n\n- `remindersEnabled`\n- `reminderTime`\n\n### Behavior\n\n- always shows a `Toggle` labeled `\"Workout Reminders\"`\n- shows a `DatePicker` for hour/minute selection only when reminders are enabled\n\nThis keeps the settings UI compact while still allowing direct time selection when the feature is active.\n\n---\n\n## How these pieces connect to the rest of the app\n\n### Workout session screen\n\n`WorkoutSessionView` uses `RestTimerManager` to drive the rest timer UI. The timer is also started from workout actions such as `saveSet(...)`, which means the rest timer is part of the workout flow rather than a standalone feature.\n\n### Settings screen\n\n`SettingsView` uses `WorkoutReminderSettingsControls` to let the user enable reminders and choose a reminder time.\n\n### Recommendation engine\n\n`WorkoutReminderPreferences` is consumed by recommendation and scheduling logic elsewhere in the app, including `UpcomingWorkoutRecommendationEngine` and unified recommendation tests. The planned reminder date is part of how upcoming workouts are presented and scheduled.\n\n### Live Activity startup\n\n`LiveActivityManager` uses `WorkoutReminderNotificationScheduler` when starting workout activity, which keeps reminder content aligned with the workout recommendation system.\n\n---\n\n## Contribution notes\n\n### When changing rest timer behavior\n\nIf you modify `RestTimerManager`, keep these invariants in mind:\n\n- session state and local state must stay synchronized\n- pausing must preserve remaining time\n- resuming must restore from paused remaining time\n- stopping must clear pending notifications and Live Activity state\n- timer completion must stop polling before triggering alert feedback\n\n### When changing reminder scheduling\n\nIf you modify `WorkoutReminderNotificationScheduler` or `WorkoutReminderPreferences`:\n\n- preserve the `plannedReminderDate` semantics for “next occurrence”\n- keep reminder copy generation centralized through `UpcomingWorkoutRecommendationFormatter`\n- ensure disabled preferences continue to suppress notification creation\n- keep `WorkoutReminderPreferencesStore` compatible with existing `UserDefaults` keys\n\n### Testing focus areas\n\nThe module is well-suited to tests around:\n\n- timer start/pause/resume/stop transitions\n- notification scheduling and cancellation\n- session restoration via `updateFromSession()`\n- reminder preference persistence\n- reminder request generation from recommendations\n\n","other-agents-md":"# Other — AGENTS.md\n\n# AGENTS.md\n\n## Purpose\n\n`AGENTS.md` is the repository’s contributor guide for working in this SwiftUI codebase. It does not define runtime behavior, app features, or executable APIs. Instead, it establishes the engineering rules, architectural preferences, and workflow expectations that should be followed when modifying the project.\n\nThis file is especially important because it sets the default assumptions for:\n\n- Swift language version and concurrency style\n- SwiftUI and state management patterns\n- SwiftData modeling constraints\n- UI design direction for the app\n- Xcode MCP usage\n- GitNexus-based impact analysis and change safety\n\n## Scope\n\nThe guidance in this file applies across the repository unless a more specific document or project constraint overrides it. It is intended for anyone contributing code, especially when adding or changing:\n\n- SwiftUI views\n- `@Observable` shared state\n- SwiftData models\n- navigation and layout\n- tests\n- project structure and naming\n- build and verification workflows\n\n## How to read this file\n\nThe document is organized as a set of enforceable conventions rather than implementation details. The most important sections are:\n\n- **Role** — defines the expected engineering standard\n- **Core instructions** — high-level platform and architecture constraints\n- **Swift instructions** — language-level preferences and safety rules\n- **SwiftUI instructions** — view-layer conventions\n- **SwiftData instructions** — persistence constraints when CloudKit is involved\n- **Project structure** — repository organization guidance\n- **UI Design Rules for Workout Tracker** — visual and interaction principles\n- **Xcode MCP** — preferred tooling when available\n- **GitNexus** — required code-intelligence workflow for safe edits\n\n## Key conventions\n\n### Platform and language baseline\n\nThe repository targets:\n\n- **iOS 26.0 or later**\n- **Swift 6.2 or later**\n\nModern Swift concurrency is the default. The guide explicitly prefers `async`/`await` APIs over closure-based alternatives whenever both exist.\n\n### State management\n\nShared app state should be modeled with `@Observable` classes, typically paired with:\n\n- `@State` for ownership\n- `@Bindable` or `@Environment` for passing state through the view hierarchy\n\nThe guide strongly discourages legacy observation patterns such as:\n\n- `ObservableObject`\n- `@Published`\n- `@StateObject`\n- `@ObservedObject`\n- `@EnvironmentObject`\n\nThese are only acceptable when unavoidable or when working in legacy/integration code where architectural changes would be too disruptive.\n\n### Main actor isolation\n\nAny `@Observable` class should be marked `@MainActor` unless the project uses a main-actor default isolation model. This is a safety rule intended to keep shared UI state aligned with Swift concurrency expectations.\n\n### API style preferences\n\nThe guide favors modern Swift and Foundation APIs, including examples such as:\n\n- `URL.documentsDirectory`\n- `appending(path:)`\n- `replacing(_:with:)` on strings\n- `localizedStandardContains()` for user-input filtering\n- `FormatStyle` APIs instead of legacy formatter classes\n\nIt also prohibits several older patterns, including:\n\n- `DispatchQueue.main.async`\n- `Task.sleep(nanoseconds:)`\n- force unwraps and unrecoverable `try`\n- `UIScreen.main.bounds`\n- `cornerRadius()`\n- `foregroundColor()`\n- `tabItem()`\n- `NavigationView`\n- one-parameter `onChange`\n\n### Design system usage\n\nThe guide requires use of project-specific design helpers:\n\n- `DesignSystem.Spacing`\n- `DesignSystem.Typography`\n- `DesignSystem.Colors`\n\nThis keeps spacing, typography, and color usage consistent across the app.\n\n## Workflow expectations\n\n### Before editing code\n\nWhen GitNexus is available, the guide requires impact analysis before modifying any function, class, or method. The intended workflow is:\n\n1. Identify the symbol to change.\n2. Run `gitnexus_impact({target: \"...\", direction: \"upstream\"})`.\n3. Review callers, affected processes, and risk level.\n4. Warn the user if the result is HIGH or CRITICAL risk before proceeding.\n\nThis is a safety gate, not a suggestion.\n\n### Before committing\n\nThe guide requires `gitnexus_detect_changes()` before committing to confirm that only the expected symbols and execution flows were affected.\n\n### When exploring unfamiliar code\n\nThe guide prefers GitNexus query and context tools over ad hoc searching:\n\n- `gitnexus_query(...)` for concept- or flow-oriented exploration\n- `gitnexus_context(...)` for symbol-level context\n- GitNexus process and cluster resources for architecture navigation\n\n## Xcode MCP integration\n\nIf Xcode MCP is configured, the guide prefers its tools over generic alternatives for common development tasks:\n\n- `DocumentationSearch` to verify API availability and usage\n- `BuildProject` to confirm compilation\n- `GetBuildLog` to inspect errors and warnings\n- `RenderPreview` to validate SwiftUI appearance\n- `XcodeListNavigatorIssues` to inspect issue navigator findings\n- `ExecuteSnippet` to test code in file context\n- `XcodeRead`, `XcodeWrite`, `XcodeUpdate` for Xcode project files\n\nThis section establishes a preferred toolchain for working inside the project rather than a code dependency.\n\n## GitNexus integration\n\nThe GitNexus block is the most operationally significant part of the file. It defines the repository as indexed code intelligence for **BigRoosterWorkouts** and provides rules for safe navigation and modification.\n\n### Required behaviors\n\n- Run impact analysis before editing any symbol\n- Use query/context tools to understand architecture and execution flow\n- Use rename tools for symbol renaming instead of search-and-replace\n- Run change detection before committing\n- Respect HIGH and CRITICAL risk warnings\n\n### Why it matters\n\nThese rules are designed to reduce accidental breakage in a large SwiftUI codebase by making edits graph-aware rather than text-based. The guide treats the call graph and execution flow index as the source of truth for understanding impact.\n\n## UI design direction\n\nThe file includes a detailed visual philosophy for the Workout Tracker app. The core direction is:\n\n- clean\n- calm\n- functional\n- restrained\n- data-first\n\nThe design rules discourage decorative UI patterns such as:\n\n- excessive cards\n- heavy shadows\n- gradients\n- oversized rounded corners\n- bulky padding\n- loud visual styling\n\nInstead, the guide prefers:\n\n- compact layouts\n- subtle separators\n- strong hierarchy\n- selective emphasis\n- reusable components\n- restrained color usage\n\nThis section is not just aesthetic advice; it directly influences how SwiftUI screens should be structured and composed.\n\n## Relationship to the rest of the codebase\n\n`AGENTS.md` is not imported by the app and has no runtime dependencies. Its role is procedural: it shapes how the codebase should be edited and reviewed.\n\nIn practice, it connects to the rest of the repository in three ways:\n\n1. **Implementation guidance** \n It defines the preferred patterns for Swift, SwiftUI, and SwiftData code.\n\n2. **Tooling workflow** \n It instructs contributors to use Xcode MCP and GitNexus when available.\n\n3. **Quality control** \n It establishes constraints that help keep the codebase modern, safe, and consistent.\n\n## Practical implications for contributors\n\nWhen working in this repository, treat this file as the default policy layer. Before making changes, check whether the work involves:\n\n- a symbol that requires GitNexus impact analysis\n- a SwiftUI view that should use modern APIs\n- shared state that should be converted to `@Observable`\n- a SwiftData model that must satisfy CloudKit constraints\n- a UI that should follow the app’s restrained design language\n\nIf a proposed change conflicts with this guide, the guide should be treated as the first point of review.\n\n## Summary\n\n`AGENTS.md` is the repository’s engineering contract. It defines the preferred architecture, coding style, UI philosophy, and safe-edit workflow for the SwiftUI app. While it contains no executable code, it has broad influence over how every part of the codebase should be developed and maintained.","other-assets-xcassets":"# Other — Assets.xcassets\n\n# Other — Assets.xcassets\n\n`Assets.xcassets` is the app’s centralized asset catalog. It contains the visual resources used throughout **BigRoosterWorkouts**, including the app icon, accent color placeholder, weather backgrounds, badge artwork, and anatomical muscle illustrations.\n\nThis module does not contain executable code, functions, or classes. Its behavior is defined entirely by Xcode asset catalog metadata (`Contents.json`) and the image/vector files referenced by those manifests.\n\n## Purpose\n\nThe asset catalog provides:\n\n- A single source of truth for UI imagery\n- Named resources that can be loaded from SwiftUI/UIKit by asset name\n- Device- and appearance-aware variants for icons and backgrounds\n- Vector-preserving artwork for scalable anatomical diagrams and badges\n\nBecause these assets are referenced by name elsewhere in the app, changes here can affect multiple screens without touching application logic.\n\n## Catalog structure\n\nThe top-level catalog contains these groups:\n\n- `AccentColor.colorset`\n- `AppIcon.appiconset`\n- `HomeWeatherBackgrounds/`\n- `badges/`\n- `muscles/`\n\nEach group is represented by one or more `Contents.json` files that describe the available images, idioms, scales, and appearance variants.\n\n## Asset groups\n\n### AccentColor\n\n`AccentColor.colorset` is currently a placeholder color set:\n\n- Contains a single universal color entry with no explicit color values\n- Uses Xcode’s generated catalog metadata\n\nThis is typically used by SwiftUI as the app’s accent color resource. Since no color components are defined in the provided manifest, the actual rendered color may depend on Xcode defaults or additional configuration outside this snippet.\n\n### AppIcon\n\n`AppIcon.appiconset` defines the application icon set.\n\nKey characteristics:\n\n- Primary source image: `Kettlebell_02.jpeg`\n- Includes iOS universal 1024×1024 entries\n- Includes appearance variants for:\n - dark luminosity\n - tinted luminosity\n- Includes macOS icon sizes:\n - 16×16\n - 32×32\n - 128×128\n - 256×256\n - 512×512\n\nThe manifest shows one explicit file for the universal iOS icon and one explicit file for the macOS 512×512 slot. The remaining slots are present as catalog entries and may be populated by Xcode’s asset generation workflow or by derived variants.\n\n### HomeWeatherBackgrounds\n\n`HomeWeatherBackgrounds` contains weather-themed background images used on the home screen or other weather-aware UI.\n\nAvailable image sets:\n\n- `home_weather_clear_day`\n- `home_weather_clear_evening`\n- `home_weather_clear_night`\n- `home_weather_cloudy_day`\n- `home_weather_fog_day`\n- `home_weather_rain_day`\n- `home_weather_snow_day`\n- `home_weather_storm_night`\n\nEach image set:\n\n- Is universal\n- Declares 1x, 2x, and 3x scales\n- Provides a single explicit `.jpg` file for the 1x slot\n- Leaves the higher-density slots empty in the manifest\n\nThis pattern indicates the app expects these backgrounds to scale across devices while keeping the asset names stable for runtime lookup.\n\n### badges\n\n`badges` contains achievement artwork for workout milestones.\n\nAvailable badge assets:\n\n- `100 workout completed`\n- `30 day streak`\n- `7 day streak`\n- `first workout completed`\n- `longest time record`\n- `new record calories burned`\n\nThese are stored as PDF vector assets in the manifest, which is a good fit for badges because they need to remain crisp at multiple sizes.\n\n### muscles\n\n`muscles` is the largest asset group and contains anatomical muscle illustrations used for workout targeting, anatomy views, or exercise detail screens.\n\nCommon characteristics across these assets:\n\n- Universal idiom\n- 1x / 2x / 3x scale entries\n- PDF source files\n- `preserves-vector-representation: true`\n\nThat last property is important: it tells Xcode to keep the PDF as vector artwork rather than rasterizing it at import time. This allows the same asset to render cleanly at many sizes and on high-density displays.\n\nThe muscle assets are organized by body region and view orientation. Examples include:\n\n- `muscle-abductors-anterior-outer-inner-muscles`\n- `muscle-back-posterior-outer-inner-muscles`\n- `muscle-biceps-brachii-anterior-outer-inner-muscles`\n- `muscle-deltoid-medial-lateral-posterior-outer-inner-muscles`\n- `muscle-trapezius-upper-posterior-outer-inner-muscles`\n- `muscle-triceps-brachii-posterior-outer-inner-muscles`\n- `muscle-wrist-flexors-anterior-outer-inner-muscles`\n\nThe naming convention is consistent and descriptive:\n\n- `muscle-<region>-<orientation>-<detail>.pdf`\n\nThis makes it easier to infer which illustration should be used in a given UI context.\n\n## Naming and lookup conventions\n\nAssets in this catalog are intended to be referenced by their catalog names, not by file paths.\n\nTypical usage patterns elsewhere in the app would be:\n\n- SwiftUI: `Image(\"asset-name\")`\n- UIKit: `UIImage(named: \"asset-name\")`\n\nExamples of stable lookup names include:\n\n- `home_weather_clear_day`\n- `7 day streak`\n- `muscle-quadriceps-anterior-outer-inner-muscles`\n\nBecause the asset catalog is name-driven, renaming an asset here requires updating every runtime reference.\n\n## How the catalog works\n\nAt build time, Xcode compiles `Assets.xcassets` into an optimized asset bundle. At runtime, the app resolves assets by name and selects the best match based on:\n\n- Device idiom\n- Screen scale\n- Appearance traits such as luminosity\n- Platform-specific slots\n\nFor example:\n\n- The app icon can vary between iOS and macOS\n- Weather backgrounds can scale across Retina and non-Retina displays\n- Muscle illustrations preserve vector quality regardless of display size\n\n## Relationship to the rest of the app\n\nThis module is a shared dependency for UI layers across the codebase.\n\nIt likely supports:\n\n- Home screen presentation\n- Weather-aware visual styling\n- Achievement or progress screens\n- Exercise anatomy views\n- App branding and launch identity\n\nSince there are no internal calls or execution flows, the catalog’s “integration” happens entirely through asset name references in view code and UI configuration.\n\n## Maintenance notes\n\nWhen contributing to this catalog:\n\n- Keep asset names stable once they are referenced in code\n- Prefer vector PDFs for scalable illustrations and badges\n- Ensure app icon slots remain complete for all required platforms\n- Match image naming to the existing convention for weather and muscle assets\n- Verify that appearance variants are populated correctly for dark/tinted modes where applicable\n\n### Common pitfalls\n\n- Removing or renaming an asset without updating code references\n- Adding raster artwork where vector PDF would be more appropriate\n- Leaving required app icon slots incomplete\n- Introducing inconsistent naming that makes runtime lookup harder\n\n## Asset inventory summary\n\n| Group | Asset type | Notes |\n|---|---|---|\n| `AccentColor.colorset` | Color set | Placeholder accent color definition |\n| `AppIcon.appiconset` | App icon | iOS and macOS icon variants |\n| `HomeWeatherBackgrounds` | JPEG image sets | Weather-specific backgrounds |\n| `badges` | PDF image sets | Achievement and milestone badges |\n| `muscles` | PDF image sets | Vector anatomical illustrations |\n\n## Architecture\n\n```mermaid\nflowchart TD\n A[Assets.xcassets] --> B[AppIcon.appiconset]\n A --> C[HomeWeatherBackgrounds]\n A --> D[badges]\n A --> E[muscles]\n A --> F[AccentColor.colorset]\n\n C --> C1[Weather background images]\n D --> D1[Workout achievement badges]\n E --> E1[Vector muscle illustrations]\n B --> B1[Branding / platform icons]\n F --> F1[Theme accent color]\n```\n\n## Summary\n\n`Assets.xcassets` is the visual resource backbone of **BigRoosterWorkouts**. It contains the app’s branding, themed backgrounds, achievement badges, and scalable anatomy artwork, all organized as named assets for straightforward use throughout the UI.","other-bigroosterworkouts-xcodeproj":"# Other — BigRoosterWorkouts.xcodeproj\n\n# BigRoosterWorkouts.xcodeproj\n\n`BigRoosterWorkouts.xcodeproj` is the Xcode project container for the BigRoosterWorkouts app and its unit test bundle. It does not contain executable app logic itself; instead, it defines the build graph, target configuration, package dependencies, resources, signing settings, and test host wiring that make the app buildable and testable.\n\nThe project is configured for:\n\n- a SwiftUI-based iOS app target: `BigRoosterWorkouts`\n- a unit test bundle target: `BigRoosterWorkoutsTests`\n- a remote Swift package dependency: `AppRouter`\n- filesystem-synchronized source groups for the app and tests\n- Xcode-managed resource and entitlement generation\n\n## What this module contains\n\nThis project file defines the following major pieces:\n\n- **Project object**: global settings shared by all targets\n- **App target**: `BigRoosterWorkouts`\n- **Test target**: `BigRoosterWorkoutsTests`\n- **Build phases**: sources, frameworks, resources, and extension embedding\n- **Package dependency**: `AppRouter` from GitHub\n- **Build configurations**: Debug and Release variants for project and targets\n\nBecause this is an `.xcodeproj`, there are no Swift functions or classes here. The “API surface” is the Xcode build configuration itself.\n\n## High-level structure\n\n```mermaid\nflowchart LR\n P[PBXProject: BigRoosterWorkouts] --> A[Target: BigRoosterWorkouts]\n P --> T[Target: BigRoosterWorkoutsTests]\n P --> S[Swift Package: AppRouter]\n\n A --> AF[Frameworks: AppRouter]\n A --> AR[Resources: TestPlan.xctestplan, AGENTS.md]\n A --> AG[Embed Foundation Extensions]\n T --> TD[Depends on BigRoosterWorkouts]\n```\n\n## Targets\n\n### `BigRoosterWorkouts`\n\nThis is the main application target.\n\n#### Product\n- `BigRoosterWorkouts.app`\n\n#### Target type\n- `com.apple.product-type.application`\n\n#### Source organization\n- Uses `PBXFileSystemSynchronizedRootGroup`\n- Root folder: `BigRoosterWorkouts`\n\nThis means Xcode tracks the folder contents directly rather than requiring every file to be manually listed in the project file.\n\n#### Dependencies\n- Swift package product: `AppRouter`\n\nThe package is linked in the target’s Frameworks phase and declared in `packageProductDependencies`.\n\n#### Build phases\n- **Sources**: present, but no explicit file list in the project file\n- **Frameworks**: links `AppRouter`\n- **Resources**: includes `TestPlan.xctestplan` and `AGENTS.md`\n- **Embed Foundation Extensions**: copy-files phase for app extensions, currently empty\n\n#### App-specific build settings\nThe app target is configured with common iOS app settings and several generated Info.plist keys:\n\n- `ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon`\n- `ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor`\n- `CODE_SIGN_ENTITLEMENTS = BigRoosterWorkouts/BigRoosterWorkouts.entitlements`\n- `GENERATE_INFOPLIST_FILE = YES`\n- `PRODUCT_BUNDLE_IDENTIFIER = com.vishwakarma.BigRoosterWorkouts`\n- `MARKETING_VERSION = 1.0`\n- `CURRENT_PROJECT_VERSION = 1`\n- `TARGETED_DEVICE_FAMILY = \"1,2\"`\n- `SWIFT_VERSION = 6.0`\n- `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`\n\nThe generated Info.plist includes usage descriptions for Health, Motion, and Location access:\n\n- `NSHealthShareUsageDescription`\n- `NSHealthUpdateUsageDescription`\n- `NSLocationWhenInUseUsageDescription`\n- `NSMotionUsageDescription`\n\nIt also enables background modes:\n\n- `fitness`\n- `location`\n\nThese settings indicate the app is expected to interact with motion/health data and location-based features.\n\n### `BigRoosterWorkoutsTests`\n\nThis is the unit test bundle target.\n\n#### Product\n- `BigRoosterWorkoutsTests.xctest`\n\n#### Target type\n- `com.apple.product-type.bundle.unit-test`\n\n#### Source organization\n- Uses `PBXFileSystemSynchronizedRootGroup`\n- Root folder: `BigRoosterWorkoutsTests`\n\n#### Dependencies\n- Depends on the `BigRoosterWorkouts` app target via `PBXTargetDependency`\n\nThis is wired through:\n\n- `PBXContainerItemProxy`\n- `PBXTargetDependency`\n- `TEST_HOST`\n\nThe test bundle is configured to run inside the built app executable.\n\n#### Build phases\n- **Sources**: present, but no explicit file list in the project file\n- **Frameworks**: empty\n- **Resources**: empty\n\n#### Test-specific build settings\nThe test target is configured with:\n\n- `BUNDLE_LOADER = \"$(TEST_HOST)\"`\n- `TEST_HOST = \"$(BUILT_PRODUCTS_DIR)/BigRoosterWorkouts.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BigRoosterWorkouts\"`\n- `PRODUCT_BUNDLE_IDENTIFIER = com.vishwakarma.BigRoosterWorkoutsTests`\n- `SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated`\n- `SWIFT_EMIT_LOC_STRINGS = NO`\n\nThis is the standard setup for host-based unit tests in iOS.\n\n## Project-level configuration\n\nThe `PBXProject` object defines settings shared across the workspace graph.\n\n### Shared attributes\n- `BuildIndependentTargetsInParallel = 1`\n- `LastSwiftUpdateCheck = 2630`\n- `LastUpgradeCheck = 2640`\n\n### Localization and regions\n- `developmentRegion = en`\n- `knownRegions = (en, Base)`\n\n### Package references\n- `AppRouter` remote package reference:\n - Repository: `https://github.com/dimillian/AppRouter.git`\n - Requirement: `upToNextMajorVersion`\n - Minimum version: `1.0.4`\n\n### Project root and groups\nThe main group contains:\n\n- `AGENTS.md`\n- `TestPlan.xctestplan`\n- `BigRoosterWorkouts/`\n- `BigRoosterWorkoutsTests/`\n- `Frameworks/`\n- `Products/`\n\n## Build phases and resource handling\n\n### App target resources\nThe app target’s resource phase includes:\n\n- `TestPlan.xctestplan`\n- `AGENTS.md`\n\nThis is unusual for a production app target, but valid. It means these files are bundled into the app product. If that is intentional, they can be accessed at runtime as app resources; if not, they may be present for tooling or project conventions.\n\n### Foundation extensions embedding\nThe `Embed Foundation Extensions` copy-files phase exists but currently has no files. This phase is typically used for app extensions or related embedded bundles.\n\n### Framework linking\nThe app target links:\n\n- `AppRouter`\n\nThe project also declares system framework references in the `Frameworks` group:\n\n- `WidgetKit.framework`\n- `SwiftUI.framework`\n\nThese are referenced as framework file entries in the project, though only `AppRouter` is explicitly listed in the app target’s Frameworks build phase.\n\n## Build configurations\n\nThe project defines Debug and Release configurations for both the project and each target.\n\n### Project-level Debug\nOptimized for development:\n\n- `ENABLE_TESTABILITY = YES`\n- `GCC_OPTIMIZATION_LEVEL = 0`\n- `ONLY_ACTIVE_ARCH = YES`\n- `SWIFT_OPTIMIZATION_LEVEL = -Onone`\n- `DEBUG=1`\n\n### Project-level Release\nOptimized for distribution:\n\n- `ENABLE_NS_ASSERTIONS = NO`\n- `SWIFT_COMPILATION_MODE = wholemodule`\n- `VALIDATE_PRODUCT = YES`\n- `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym`\n\n### App target Debug/Release\nBoth app configurations share the same core settings:\n\n- automatic code signing\n- entitlements file\n- generated Info.plist\n- app icon and accent color asset catalog names\n- Swift 6\n- main actor isolation\n- iPhone and iPad support\n\nDifferences are mostly in optimization and debug info behavior inherited from the project-level configuration.\n\n### Test target Debug/Release\nBoth test configurations share:\n\n- host-based test execution\n- automatic code signing\n- generated Info.plist\n- Swift 6\n- nonisolated default actor isolation\n\n## How the project connects to the rest of the codebase\n\nThis project file is the integration point for the app’s source tree and external dependencies:\n\n- `BigRoosterWorkouts/` contains the app source files, assets, and entitlements\n- `BigRoosterWorkoutsTests/` contains unit tests\n- `AppRouter` is pulled in as a Swift Package dependency\n- `WidgetKit` and `SwiftUI` are available as linked frameworks\n- `TestPlan.xctestplan` defines test execution behavior in Xcode\n- `AGENTS.md` is bundled as a resource, likely for tooling or contributor guidance\n\nThe project itself does not implement runtime behavior; it determines how the runtime behavior is assembled from the app target and its dependencies.\n\n## Important implementation details for contributors\n\n### File system synchronized groups\nBoth source groups use `PBXFileSystemSynchronizedRootGroup`. This means:\n\n- files added to `BigRoosterWorkouts/` or `BigRoosterWorkoutsTests/` are reflected automatically in Xcode\n- the project file may not list individual Swift files explicitly\n- build phases may appear sparse because file membership is inferred from the filesystem\n\nWhen adding new source files, place them in the correct folder and ensure Xcode recognizes them through the synchronized group.\n\n### Test host wiring\nThe test target depends on the app target and runs against the built app executable. If you change the app product name, bundle path, or target structure, update:\n\n- `TEST_HOST`\n- `BUNDLE_LOADER`\n- target dependency wiring if needed\n\n### Signing and entitlements\nThe app target uses:\n\n- `CODE_SIGN_ENTITLEMENTS = BigRoosterWorkouts/BigRoosterWorkouts.entitlements`\n- `CODE_SIGN_STYLE = Automatic`\n- `DEVELOPMENT_TEAM = 4AL2UK5Z4U`\n\nIf you add capabilities or background modes, update the entitlements file and the generated Info.plist keys together.\n\n### Package dependency management\n`AppRouter` is pinned with a minimum version of `1.0.4`. If the app depends on newer routing behavior, update the package requirement in the project file and verify compatibility with the app target.\n\n## Summary of key identifiers\n\n- **Project**: `BigRoosterWorkouts`\n- **App target**: `BigRoosterWorkouts`\n- **Test target**: `BigRoosterWorkoutsTests`\n- **App product**: `BigRoosterWorkouts.app`\n- **Test product**: `BigRoosterWorkoutsTests.xctest`\n- **Swift package**: `AppRouter`\n- **App bundle identifier**: `com.vishwakarma.BigRoosterWorkouts`\n- **Test bundle identifier**: `com.vishwakarma.BigRoosterWorkoutsTests`\n- **Deployment target**: iOS 26.0\n- **Swift version**: 6.0","other-bigroosterworkouts":"# Other — BigRoosterWorkouts\n\n# BigRoosterWorkouts Entitlements\n\nThis module defines the app’s macOS/iOS sandbox entitlements for the `BigRoosterWorkouts` target. It does not contain executable Swift/Objective-C code; instead, it declares the capabilities the app is allowed to use at runtime.\n\n## Purpose\n\n`BigRoosterWorkouts.entitlements` configures two platform permissions:\n\n- **HealthKit access**\n- **App Group container access**\n\nThese entitlements are required by the operating system and are consumed at build/signing time, not at runtime by application code.\n\n## Contents\n\n### `com.apple.developer.healthkit`\n\n```xml\n<key>com.apple.developer.healthkit<\/key>\n<true/>\n```\n\nEnables the app to use HealthKit APIs. Without this entitlement, any HealthKit-related functionality in the app will fail authorization or be unavailable.\n\nUse this when the app needs to:\n\n- read workout or health data from HealthKit\n- write workout or health data to HealthKit\n- request HealthKit authorization from the user\n\n### `com.apple.security.application-groups`\n\n```xml\n<key>com.apple.security.application-groups<\/key>\n<array>\n\t<string>group.com.vishwakarma.BigRoosterWorkouts<\/string>\n<\/array>\n```\n\nDeclares membership in the app group `group.com.vishwakarma.BigRoosterWorkouts`.\n\nThis allows the app to share data with other targets that are signed with the same app group entitlement, such as:\n\n- extensions\n- widgets\n- companion targets\n- shared persistence containers\n\nTypical uses include:\n\n- storing shared `UserDefaults`\n- accessing a shared file container\n- coordinating data between the main app and extensions\n\n## How it fits into the codebase\n\nBecause this file is an entitlement declaration, it connects to the rest of the codebase indirectly:\n\n- Any HealthKit integration elsewhere in the app depends on `com.apple.developer.healthkit`.\n- Any shared storage or cross-target communication depends on the app group identifier `group.com.vishwakarma.BigRoosterWorkouts`.\n- The Xcode target must be configured to sign with this entitlements file for the permissions to take effect.\n\n## Build-time behavior\n\nThis file is processed during code signing and embedded into the app’s signed entitlements. It does not execute and has no internal control flow.\n\nIf the target is built without this file, or if the signing configuration does not reference it, the app will not receive these capabilities.\n\n## Maintenance notes\n\nWhen updating this file:\n\n- Keep the app group identifier consistent across all targets that need shared access.\n- Ensure the HealthKit capability is also enabled in the Apple Developer portal and Xcode target settings.\n- Add additional entitlements only when the app genuinely requires them, since entitlements affect app capabilities and review requirements.\n\n## Relationship to runtime features\n\n```mermaid\nflowchart LR\n A[BigRoosterWorkouts target] --> B[Entitlements file]\n B --> C[HealthKit capability]\n B --> D[App Group container]\n C --> E[HealthKit APIs elsewhere in app]\n D --> F[Shared storage / extensions]\n```\n\n## Summary\n\n`BigRoosterWorkouts.entitlements` is a configuration artifact that grants the app access to HealthKit and the shared app group container `group.com.vishwakarma.BigRoosterWorkouts`. It has no executable logic, but it is essential for any features in the codebase that rely on health data or shared cross-target storage.","other-bigroosterworkoutstests":"# Other — BigRoosterWorkoutsTests\n\n# BigRoosterWorkoutsTests\n\nTest target for the `BigRoosterWorkouts` app. This module verifies the behavior of the app’s core feature layers: workout plan creation and persistence, workout session coordination, history aggregation, reminder generation, theme/appearance handling, home background resolution, and a small set of SwiftUI utility helpers.\n\nThe tests are written with `XCTest`, and many of them use `SwiftData` in-memory containers to exercise model persistence and service logic without touching the real store.\n\n## What this test target covers\n\nThe suite is organized around the main app subsystems:\n\n- **Workout plan creation and editing**\n - `WorkoutContentCreationTests`\n - `WorkoutPlanFeatureTests`\n- **Workout session lifecycle**\n - `WorkoutSessionCoordinatorTests`\n - `WorkoutStructurePersistenceTests`\n- **Workout history and analytics**\n - `WorkoutHistoryRepositoryTests`\n- **Recommendations and reminders**\n - `UnifiedWorkoutRecommendationServiceTests`\n - `WorkoutReminderFeatureTests`\n- **Theme and appearance**\n - `ThemeAppearanceTests`\n- **Home background selection**\n - `HomeBackgroundResolverTests`\n - `OpenMeteoHomeBackgroundWeatherProviderTests`\n- **SwiftUI helper utilities**\n - `SwiftUIConditionalHelpersTests`\n\nThese tests are not just smoke tests; they encode important domain rules such as:\n\n- plan day progression after completion or skipping\n- threshold-based workout completion\n- persistence of metadata across SwiftData fetch/save cycles\n- recommendation ordering and suppression rules\n- migration behavior for appearance preferences\n- weather-to-background mapping and fallback behavior\n\n## Test architecture\n\nMost tests follow the same pattern:\n\n1. Build a small set of model objects or stub services.\n2. Call the production service under test.\n3. Assert on the resulting model state, recommendation, or persisted data.\n\nThe suite relies heavily on helper factories inside each test class to keep scenarios readable and deterministic.\n\n### Common test helpers\n\nSeveral files define private helpers that construct repeatable fixtures:\n\n- `makeContainer()` / `makeContext()` create in-memory `SwiftData` containers.\n- `makePlan(...)`, `makeWorkoutDay(...)`, `makeTemplate(...)`, `makeCompletedSession(...)` build domain objects with realistic defaults.\n- `enabledDynamicConfiguration(...)` builds a `HomeBackgroundConfiguration` for resolver tests.\n- `seedPrograms()`, `seedStringValues(...)`, `seedIntegerValues(...)` read JSON seed data from `BigRoosterWorkouts/Data`.\n\nThese helpers are intentionally local to each test file so the scenarios remain close to the assertions they support.\n\n## SwiftData-backed tests\n\nA large portion of the suite validates persistence behavior using in-memory `ModelContainer` instances. This is especially important for model relationships and derived metadata.\n\nTypical schema setup looks like this:\n\n```swift\nlet schema = Schema([\n WorkoutSession.self,\n ExerciseLog.self,\n SetLog.self,\n WorkoutPlan.self,\n UserWorkoutPlan.self\n])\nlet configuration = ModelConfiguration(isStoredInMemoryOnly: true)\nlet container = try ModelContainer(for: schema, configurations: configuration)\n```\n\nThis pattern appears in:\n\n- `WorkoutSessionCoordinatorTests`\n- `WorkoutPlanFeatureTests`\n- `WorkoutHistoryRepositoryTests`\n- `WorkoutStructurePersistenceTests`\n- `WorkoutContentCreationTests`\n\nUsing an in-memory store keeps the tests fast and isolates them from app state.\n\n## Workout plan creation and lifecycle\n\n### `WorkoutContentCreationTests`\n\nThese tests validate the creation pipeline for plans and exercises:\n\n- seeded plans resolve to seeded ownership/source metadata\n- scratch-created templates and plans are marked as local user content\n- imported catalog exercises preserve source identity\n- invalid drafts fail with the expected creation errors\n- exercise and day bindings continue to resolve after reordering or deletion\n- generated plans preserve day and exercise ordering\n- a user-created plan can be started, exercised, and completed end-to-end\n\nThis file exercises the production types and services that turn drafts into persisted content, including:\n\n- `ExerciseCreationService`\n- `WorkoutPlanCreationService`\n- `WorkoutPlanBuilderViewModel`\n- `WorkoutPlanService`\n\n### `WorkoutPlanFeatureTests`\n\nThis is the broadest feature test file for plan behavior. It covers:\n\n- seed JSON quality and contract validation\n- plan and exercise seed data normalization\n- plan start behavior and enrollment ordering\n- progress snapshots and schedule status computation\n- workout session creation from plans and selected days\n- completion logic, including:\n - advancing to the next day\n - marking final workouts complete\n - handling incomplete workouts below threshold\n - rebasing next scheduled dates after missed or early completion\n- skipping scheduled days\n- adherence evaluation for sessions with missing sets\n- reminder candidate generation\n\nThese tests are especially useful when changing `WorkoutPlanService`, because they encode the expected relationship between:\n\n- `WorkoutPlan`\n- `UserWorkoutPlan`\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n\n### Key plan-related behaviors verified\n\n- **Completion threshold matters**: a session with too few completed sets does not advance the plan.\n- **Day progression is stable**: completing day 1 advances to day 2, even when the schedule has rest days.\n- **Skipping is explicit**: skipped days are recorded separately from completed days.\n- **Enrollment-scoped state is preserved**: multiple enrollments for the same plan are handled independently.\n- **Schedule presentation is derived from actual plan state**: the UI-facing schedule copy reflects whether a workout is due, missed, planned, or completed for today.\n\n## Workout session coordination\n\n### `WorkoutSessionCoordinatorTests`\n\nThese tests focus on the lower-level session editing and persistence coordinator:\n\n- adding exercises preserves order, identity, and rest-time overrides\n- saving sets works across all supported logging modes:\n - `.weightAndReps`\n - `.repsOnly`\n - `.durationOnly`\n - `.durationAndReps`\n- invalid set input is rejected when reps are required\n- deleting sets and exercises renumbers remaining items correctly\n- previous-set application logic restores draft inputs and highlights the right fields\n- rest duration resolution respects superset overrides, exercise overrides, then defaults\n- finishing a workout triggers injected side effects\n- deleting a workout refreshes profile stats only when appropriate\n\nThis file is the best reference for how `WorkoutSessionCoordinator` is expected to behave when editing a live workout session.\n\n### Important patterns\n\nThe coordinator is tested with explicit side-effect closures rather than real app services. That makes the tests precise and keeps them focused on orchestration behavior.\n\nFor example, `finishWorkout(...)` is verified by injecting closures for:\n\n- `refreshProfileStats`\n- `rebuildAchievements`\n- `queueHomeCelebrations`\n- `scheduleAchievementNotifications`\n- `refreshWidgets`\n- `dismiss`\n\nThis confirms that the coordinator is not only mutating models, but also coordinating the rest of the app correctly.\n\n## Workout history and analytics\n\n### `WorkoutHistoryRepositoryTests`\n\nThese tests validate how completed sessions are grouped, filtered, and summarized:\n\n- multiple sessions on the same day are aggregated into one day summary\n- streak calculations count unique workout days only\n- filtering works across routine titles, muscle groups, tags, and search text\n- month sections include padding and align to calendar weeks\n- session-specific personal records are returned correctly\n- session metadata can be populated from exercise catalog data\n\nThis suite exercises `WorkoutHistoryRepository`, which is responsible for turning raw session data into history views and analytics-friendly summaries.\n\n### What these tests protect\n\n- calendar grouping logic\n- search/filter semantics\n- derived totals such as duration and volume\n- metadata normalization for tags and muscle groups\n- achievement association with the correct session\n\n## Recommendations and reminders\n\n### `UnifiedWorkoutRecommendationServiceTests`\n\nThese tests cover the app’s recommendation engine for both the home screen and workout start sheet:\n\n- active plans outrank upcoming template workouts on the home screen\n- incomplete plan workouts explain why a day remains due\n- active sessions suppress recommendations\n- upcoming template workouts are used when no active plan exists\n- workout start sheet ordering prefers:\n 1. active plans\n 2. recommended plans\n 3. upcoming workouts\n- rest-day recommendations outrank upcoming workouts when appropriate\n- completed-for-today state is shown instead of tomorrow’s workout\n- calorie-goal supplemental workouts are added without overriding due plans\n- schedule presentation uses the correct ordinal and relation\n- missed and future plan states produce the right copy\n- enrollment-scoped state is preserved when multiple enrollments exist for the same plan\n\nThis file is the main behavioral contract for `UnifiedWorkoutRecommendationService`.\n\n### `WorkoutReminderFeatureTests`\n\nThese tests focus on upcoming workout reminders and notification content:\n\n- the recommendation engine prefers the least recently completed matching template\n- no recommendation is returned when templates are unavailable\n- no recommendation is returned when a workout session is already active\n- the engine falls back to the newest template when history does not match\n- reminder preferences persist through `WorkoutReminderPreferencesStore`\n- notification descriptors reuse the same title/body/date as the recommendation\n- calorie goal preferences persist through `WorkoutGoalPreferencesStore`\n\nThis suite verifies the interaction between:\n\n- `UpcomingWorkoutRecommendationEngine`\n- `UpcomingWorkoutRecommendationFormatter`\n- `WorkoutReminderNotificationScheduler`\n- `WorkoutReminderPreferencesStore`\n- `WorkoutGoalPreferencesStore`\n\n## Theme and appearance\n\n### `ThemeAppearanceTests`\n\nThese tests validate `ThemeManager` behavior around appearance mode migration and color-scheme resolution:\n\n- missing appearance preference defaults to `.system` and persists that choice\n- legacy `legacyUseSystemTheme` values migrate to the new appearance mode storage\n- manual light/dark modes are restored correctly from legacy state\n- `isDarkMode` and `preferredColorScheme` track the current appearance mode\n- changing appearance mode preserves the selected theme while switching color palettes\n\nThis file is important when changing preference keys or theme initialization logic, because it verifies both migration and runtime behavior.\n\n## Home background resolution\n\n### `HomeBackgroundResolverTests`\n\nThese tests cover the logic that selects the home background preset:\n\n- disabled configuration returns no preset\n- static mode uses the selected preset\n- dynamic mode maps weather and day part to the correct preset\n- readability tone is correct for dark and light wallpapers\n- unknown weather falls back to the configured preset\n- every known weather condition has a corresponding preset\n\nThe helper `enabledDynamicConfiguration(...)` is used to build a consistent dynamic configuration for these scenarios.\n\n### `OpenMeteoHomeBackgroundWeatherProviderTests`\n\nThese tests verify the weather integration used by the home background system:\n\n- `OpenMeteoWeatherConditionMapper` maps Open-Meteo weather codes to app conditions\n- `OpenMeteoHomeBackgroundWeatherProvider` uses injected coordinate and weather services correctly\n\nThe provider test uses stub implementations of:\n\n- `WeatherCoordinateProviding`\n- `OpenMeteoWeatherServicing`\n\nThis keeps the test deterministic while still validating the full async flow.\n\n## SwiftUI helper utilities\n\n### `SwiftUIConditionalHelpersTests`\n\nThese tests cover small SwiftUI extension helpers:\n\n- `isIOS26OrLater()` matches the platform availability check\n- `.if(...)` compiles for both true and false branches\n- `.ifelse(...)` compiles for both branches\n\nThese tests are mostly compile-time and API-shape checks, but they still matter because they protect the conditional view-building helpers used throughout the UI.\n\n## Seed data contract tests\n\nSeveral tests validate the JSON seed files under `BigRoosterWorkouts/Data`:\n\n- `workout_plans.json`\n- `gym_exercise_dataset.json`\n- `stretch_exercise_dataset.json`\n\nThese checks ensure that generated seed content stays aligned with the app’s model expectations.\n\n### What is validated\n\n- program count and validation metadata\n- no duplicate IDs or titles\n- referenced exercise IDs exist in the exercise datasets\n- generated exercises have normalized field names\n- identifiers do not contain whitespace\n- plan metadata options match the seed contract values\n\nThese tests are valuable because they catch data-shape regressions that would otherwise surface much later in the app.\n\n## Mermaid overview\n\n```mermaid\nflowchart TD\n A[SwiftData in-memory container] --> B[WorkoutPlanService]\n A --> C[WorkoutSessionCoordinator]\n A --> D[WorkoutHistoryRepository]\n E[Seed JSON files] --> F[WorkoutPlanImporter]\n G[ThemeManager] --> H[UserDefaults stores]\n I[Recommendation services] --> J[Home / Start Sheet outputs]\n K[HomeBackgroundResolver] --> L[Weather provider + presets]\n```\n\n## Contributing to this test target\n\nWhen adding or changing behavior in the production module, update or extend the corresponding tests here.\n\nA few guidelines that match the existing style:\n\n- Prefer small, explicit fixtures over large shared setup.\n- Use in-memory `SwiftData` containers for persistence tests.\n- Assert on domain state, not implementation details.\n- Keep helper factories private to the test file that uses them.\n- When testing orchestration, inject closures or stub protocols instead of real app services.\n\n## File-by-file reference\n\n- `HomeBackgroundResolverTests.swift`\n - background preset resolution and readability tone\n- `OpenMeteoHomeBackgroundWeatherProviderTests.swift`\n - weather code mapping and provider injection\n- `SwiftUIConditionalHelpersTests.swift`\n - conditional SwiftUI helper behavior\n- `ThemeAppearanceTests.swift`\n - appearance mode migration and theme selection\n- `UnifiedWorkoutRecommendationServiceTests.swift`\n - recommendation ordering and schedule presentation\n- `WorkoutContentCreationTests.swift`\n - plan/exercise creation and builder behavior\n- `WorkoutHistoryRepositoryTests.swift`\n - history grouping, filtering, streaks, and PR lookup\n- `WorkoutPlanFeatureTests.swift`\n - plan lifecycle, completion, skipping, and seed validation\n- `WorkoutReminderFeatureTests.swift`\n - reminder recommendation and persistence\n- `WorkoutSessionCoordinatorTests.swift`\n - session editing, set saving, deletion, and finish flows\n- `WorkoutStructurePersistenceTests.swift`\n - persistence of tags, superset fields, and template/session metadata","other-build-output-txt":"# Other — build_output.txt\n\n# build_output.txt\n\n`build_output.txt` is a captured Xcode build log for the `BigRoosterWorkouts` project. It is not source code and does not define runtime APIs, but it is still useful as a record of how the app is assembled, which targets participate in the build, and which warnings surfaced during compilation.\n\n## What this file contains\n\nThe log records a successful simulator build invoked with:\n\n- `xcodebuild`\n- Project: `BigRoosterWorkouts.xcodeproj`\n- Scheme: `BigRoosterWorkouts`\n- Destination: `platform=iOS Simulator,name=iPhone 17`\n\nIt includes the full build pipeline:\n\n- package resolution\n- target dependency graph computation\n- Swift compilation\n- linking\n- embedding the widget extension\n- app intents metadata extraction\n- code signing\n- validation\n\nThe build ends with:\n\n- `** BUILD SUCCEEDED **`\n\n## Build-time architecture reflected in the log\n\nThe log shows the project is composed of two main app-level targets plus the Swift package dependency:\n\n- `BigRoosterWorkouts` — the main iOS app target\n- `BigRoosterWidgetsExtensionExtension` — an embedded app extension\n- `AppRouter` — a Swift package dependency resolved from GitHub\n\nThe dependency graph indicates:\n\n- `BigRoosterWorkouts` depends on `BigRoosterWidgetsExtensionExtension`\n- `BigRoosterWorkouts` depends on `AppRouter`\n- `AppRouter` is also self-dependent in the package graph, which is normal for package target resolution\n- `BigRoosterWidgetsExtensionExtension` has no additional dependencies in this build graph\n\n## Build pipeline overview\n\n```mermaid\nflowchart TD\n A[xcodebuild invocation] --> B[Resolve Package Graph]\n B --> C[Compute Target Dependency Graph]\n C --> D[Swift compilation]\n D --> E[Emit module and link app]\n E --> F[Embed widget extension]\n F --> G[Extract App Intents metadata]\n G --> H[Code sign and validate]\n H --> I[BUILD SUCCEEDED]\n```\n\n## Key build stages\n\n### Package resolution\n\nThe build resolves one external package:\n\n- `AppRouter` from `https://github.com/dimillian/AppRouter.git` at version `1.0.4`\n\nThis package is added to the build products and linked into the main app target.\n\n### Target dependency graph\n\nThe log confirms the app target is built after its dependencies. This matters because the main app embeds the widget extension and links against the package framework.\n\nFor contributors, this means changes to:\n\n- the widget extension\n- package dependencies\n- app target settings\n\ncan affect the final app bundle and must be validated together.\n\n### Swift compilation\n\nThe compiler processes a large set of files across the app’s modules, including:\n\n- `Models/*`\n- `Utilities/*`\n- `Views/*`\n\nThe log is especially useful for confirming which files are part of the target and in what compilation batches they are grouped.\n\n### Linking and embedding\n\nThe main app is linked as a debug simulator binary and then packaged with:\n\n- `BigRoosterWidgetsExtensionExtension.appex` under `PlugIns`\n\nThis confirms the widget extension is embedded in the app bundle and validated as part of the final product.\n\n### App Intents processing\n\nThe build runs:\n\n- `ExtractAppIntentsMetadata`\n- `AppIntentsSSUTraining`\n\nThe output notes:\n\n- `No AppShortcuts found - Skipping.`\n\nThis indicates the app includes App Intents support, but no App Shortcuts were discovered during this build.\n\n## Warnings captured in the build\n\nThe log contains two compiler warnings worth tracking.\n\n### Unused local constant in `VerticalSplit.swift`\n\nFile:\n\n- `BigRoosterWorkouts/Utilities/VerticalSplit/VerticalSplit.swift`\n\nWarning:\n\n- `immutable value 'isMinimalPill' was never used; consider replacing with '_' or removing it`\n\nThis suggests dead code or a partially implemented branch in the vertical split UI utility. The warning is non-fatal, but it is a good cleanup candidate.\n\n### Main actor isolation warning in `ProfileView.swift`\n\nFile:\n\n- `BigRoosterWorkouts/Views/ProfileView.swift`\n\nWarning:\n\n- `main actor-isolated property 'profileHero' can not be referenced from a Sendable closure`\n\nThis points to a concurrency/isolation mismatch in the profile view. The property `profileHero` is declared as a private computed view and is being captured from a closure that Swift considers `Sendable`. This should be reviewed if the code is being modernized for Swift 6 concurrency rules.\n\n## What this log tells contributors\n\nAlthough this file does not define executable logic, it provides a useful snapshot of the codebase:\n\n- The app is a SwiftUI-based iOS application.\n- It includes a widget extension.\n- It uses a third-party routing package (`AppRouter`).\n- It has App Intents integration.\n- It is being built with Swift 6 and modern concurrency checks enabled.\n- The build currently succeeds, but there are at least two warnings to address.\n\n## How to use this file\n\nUse `build_output.txt` when you need to:\n\n- verify the build environment and destination\n- confirm which targets are included in the app\n- inspect compiler warnings from a known-good build\n- understand how the app bundle is assembled\n- compare future build output against a baseline\n\n## Maintenance notes\n\nBecause this is a generated artifact rather than source code:\n\n- it should not be treated as an API reference\n- it may become stale as the project evolves\n- it is most useful as a diagnostic snapshot or CI artifact\n\nIf you regenerate it, keep the following in mind:\n\n- preserve the exact command line if reproducibility matters\n- capture warnings and errors in full\n- note the Xcode version and simulator destination\n- update any documentation that references build behavior or warnings","other-claude-md":"# Other — CLAUDE.md\n\n# CLAUDE.md\n\n## Purpose\n\n`CLAUDE.md` is a repository-level operating guide for working with this codebase through GitNexus. It does not define runtime application logic; instead, it documents the required workflow for code exploration, impact analysis, refactoring, and change validation.\n\nThis file is effectively a policy and navigation layer for contributors and agents. Its job is to make code changes safer by enforcing a graph-aware process before edits are made.\n\n## Scope\n\nThis module applies to the entire repository indexed as **BigRoosterWorkouts**.\n\nIt is especially relevant when:\n\n- investigating unfamiliar code\n- assessing the blast radius of a change\n- renaming or refactoring symbols\n- validating changes before commit\n- using GitNexus MCP tools or related CLI skills\n\n## How it works\n\n`CLAUDE.md` is a static instruction document. There are no functions, classes, or execution flows in this module, and it does not participate in the application call graph.\n\nInstead, it defines a required workflow:\n\n1. **Inspect the codebase using GitNexus tools**\n - Prefer `gitnexus_query({ query: \"concept\" })` for conceptual exploration.\n - Use `gitnexus_context({ name: \"symbolName\" })` when you need symbol-level context.\n\n2. **Run impact analysis before editing**\n - Before modifying any function, class, or method, run:\n - `gitnexus_impact({ target: \"symbolName\", direction: \"upstream\" })`\n - Report the blast radius to the user, including:\n - direct callers\n - affected processes\n - risk level\n\n3. **Warn on high-risk changes**\n - If impact analysis returns **HIGH** or **CRITICAL** risk, the user must be warned before proceeding.\n\n4. **Validate changes before commit**\n - Run `gitnexus_detect_changes()` before committing to confirm only expected symbols and execution flows were affected.\n\n5. **Use graph-aware refactoring tools**\n - For renames, use `gitnexus_rename` instead of search-and-replace.\n - This avoids breaking relationships in the indexed call graph.\n\n## Key sections\n\n### GitNexus — Code Intelligence\n\nThis section establishes the repository’s indexing status and identifies the project as:\n\n- **BigRoosterWorkouts**\n- **7420 symbols**\n- **29582 relationships**\n- **253 execution flows**\n\nIt also instructs contributors to check index freshness and run `npx gitnexus analyze` if the index is stale.\n\n### Always Do\n\nThese are mandatory behaviors:\n\n- run impact analysis before editing any symbol\n- run change detection before committing\n- warn users about HIGH or CRITICAL risk\n- use `gitnexus_query` for unfamiliar code\n- use `gitnexus_context` for full symbol context\n\n### Never Do\n\nThese are hard constraints:\n\n- do not edit a function, class, or method without impact analysis\n- do not ignore HIGH or CRITICAL risk\n- do not rename symbols with find-and-replace\n- do not commit without change detection\n\n### Resources\n\nThis section maps GitNexus resources to their intended use:\n\n- `gitnexus://repo/BigRoosterWorkouts/context` — repository overview and index freshness\n- `gitnexus://repo/BigRoosterWorkouts/clusters` — functional areas\n- `gitnexus://repo/BigRoosterWorkouts/processes` — execution flows\n- `gitnexus://repo/BigRoosterWorkouts/process/{name}` — step-by-step traces\n\n### CLI\n\nThis section points contributors to the relevant skill files for common tasks:\n\n- architecture exploration\n- impact analysis\n- debugging\n- refactoring\n- tool/schema reference\n- CLI commands for index/status/clean/wiki operations\n\n## Relationship to the rest of the codebase\n\n`CLAUDE.md` is not imported by application code and has no runtime dependencies. Its influence is procedural rather than programmatic:\n\n- it governs how developers interact with the repository\n- it directs them to GitNexus resources that reflect the actual code graph\n- it reduces the risk of unsafe edits by requiring impact-aware workflows\n\nBecause it has no internal or outgoing calls and no execution flows, it should be treated as repository documentation, not as part of the executable system.\n\n## Operational implications for contributors\n\nWhen working in this repository, this file should be treated as the default operating contract:\n\n- before changing code, identify the symbol and analyze upstream impact\n- before refactoring, use graph-aware rename/extraction tools\n- before committing, verify the affected scope\n- when in doubt, use GitNexus context and process views instead of ad hoc searching\n\n## Architecture summary\n\n```mermaid\nflowchart LR\n CLAUDE[CLAUDE.md]\n Query[gitnexus_query]\n Context[gitnexus_context]\n Impact[gitnexus_impact]\n Detect[gitnexus_detect_changes]\n Rename[gitnexus_rename]\n\n CLAUDE --> Query\n CLAUDE --> Context\n CLAUDE --> Impact\n CLAUDE --> Detect\n CLAUDE --> Rename\n```\n\n## Notes for maintainers\n\nIf the repository workflow changes, update this file to keep the instructions aligned with the current GitNexus tooling and safety requirements. Since this document is used as a guardrail for edits, stale guidance can lead to unsafe or incomplete changes even when the application code itself is correct.","other-docs":"# Other — docs\n\n# Other — docs\n\nThis module contains repository-level documentation and review guidance rather than runtime app code. It is used to describe the product direction, audit the current feature set, and define review criteria for future changes.\n\n## What’s in this module\n\nThe `docs/` folder currently includes three Markdown documents:\n\n- `APP_GOAL_PROGRESS_REPORT.md`\n- `CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md`\n- `PR_REVIEW_CHECKLIST.md`\n\nThese files are not part of the app’s Swift runtime, but they are important to the project because they:\n\n- define the intended product scope,\n- document current implementation status,\n- identify missing or extra features,\n- and provide a checklist for reviewing pull requests.\n\n## Purpose\n\nThe documentation in this module serves three related goals:\n\n1. **Track product progress**\n - Compare the current app against the original workout-tracking vision.\n - Separate MVP work from final social/cloud features.\n\n2. **Audit app shape and scope**\n - Identify which screens, models, and flows support the core workout product.\n - Call out features that are out of scope or should be hidden, removed, or deferred.\n\n3. **Standardize code review**\n - Provide a checklist for SwiftUI, SwiftData, concurrency, and architecture expectations.\n - Reduce regressions when refactoring core workout flows.\n\n## Document overview\n\n### `APP_GOAL_PROGRESS_REPORT.md`\n\nThis is the highest-level product status report.\n\nIt compares the original app description against the current implementation and classifies features into:\n\n- **Achieved**\n- **Mostly achieved**\n- **Partial**\n- **Missing**\n- **Extra**\n\nIt also splits the product into two scopes:\n\n- **MVP**: a polished single-user workout tracker\n- **Final**: the full social/cloud product with sync, sharing, friends, challenges, and leaderboards\n\n### `CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md`\n\nThis document is a functional audit of the app’s current shape.\n\nIt focuses on:\n\n- the main navigation structure,\n- the workout session flow,\n- workout plans,\n- exercise search and custom exercise creation,\n- and features that distract from the core workout-tracking product.\n\nIt also recommends refactor boundaries and a sequencing strategy for future cloud/social work.\n\n### `PR_REVIEW_CHECKLIST.md`\n\nThis is a contributor-facing review checklist.\n\nIt is intended to be used when reviewing changes and covers:\n\n- SwiftUI-first patterns,\n- modern concurrency,\n- SwiftData conventions,\n- crash-safety,\n- design system usage,\n- test coverage,\n- and build verification.\n\n## How these docs connect to the codebase\n\nAlthough these files are not executable code, they are tightly coupled to the app architecture.\n\nThey reference and evaluate real app components such as:\n\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `WorkoutSessionView`\n- `WorkoutPlan`\n- `UserWorkoutPlan`\n- `WorkoutPlanService`\n- `WorkoutTemplate`\n- `ExerciseTemplate`\n- `ExerciseCreationService`\n- `AchievementService`\n- `WorkoutHistoryRepository`\n- `RestTimerManager`\n- `HealthKitManager`\n\nThe docs are effectively a map of the codebase’s current product state. When the implementation changes, these documents should be updated to keep the product narrative aligned with the actual code.\n\n## Key themes across the docs\n\n### 1. The app is already strong as a local workout tracker\n\nThe documentation consistently describes the app as having a solid foundation for:\n\n- live workout logging,\n- rest timers,\n- workout history,\n- personal records,\n- achievements,\n- analytics,\n- templates,\n- custom exercises,\n- and workout plans.\n\n### 2. Cloud and social features are not yet implemented\n\nThe docs explicitly call out missing support for:\n\n- cloud backup,\n- multi-device sync,\n- identity,\n- sharing,\n- friends,\n- challenges,\n- and leaderboards.\n\nThese are treated as future work, not current capabilities.\n\n### 3. Data protection is the next MVP-critical gap\n\nThe progress report emphasizes that reliable backup/export is the most important missing trust feature for a daily-use workout app.\n\n### 4. Product sprawl should be reduced\n\nThe audit identifies non-core areas such as:\n\n- food logging,\n- step tracking,\n- theme/background tooling,\n- and design showcase routes.\n\nThese are treated as distractions unless they are intentionally part of the product roadmap.\n\n## Mermaid: documentation relationship\n\n```mermaid\nflowchart TD\n Progress[\"APP_GOAL_PROGRESS_REPORT.md\"]\n Audit[\"CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md\"]\n Review[\"PR_REVIEW_CHECKLIST.md\"]\n Code[\"SwiftUI / SwiftData app code\"]\n\n Progress --> Code\n Audit --> Code\n Review --> Code\n Code --> Progress\n Code --> Audit\n Code --> Review\n```\n\n## How to use these docs as a contributor\n\n### Before starting work\n\nRead the audit and progress report to understand:\n\n- whether the feature is core or extra,\n- whether it belongs to MVP or final scope,\n- and what dependencies or missing foundations exist.\n\n### While implementing\n\nUse the PR checklist to verify:\n\n- architecture stays SwiftUI-first,\n- logic is testable,\n- concurrency is safe,\n- and new code follows project conventions.\n\n### Before merging\n\nUpdate the docs if your change affects:\n\n- product scope,\n- feature status,\n- navigation structure,\n- or the recommended implementation order.\n\n## Maintenance guidance\n\nThese documents should be treated as living project artifacts.\n\nUpdate them when:\n\n- a major feature lands,\n- a feature is removed or hidden,\n- the app’s scope changes,\n- or the architecture shifts in a way that affects product direction.\n\nIf the codebase evolves toward cloud sync or social features, the docs should be revised first or alongside the implementation so the roadmap remains accurate.\n\n## Practical reading order\n\nFor most contributors, the best order is:\n\n1. `CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md`\n2. `APP_GOAL_PROGRESS_REPORT.md`\n3. `PR_REVIEW_CHECKLIST.md`\n\nThat sequence gives you:\n\n- the current app shape,\n- the product status,\n- and the standards for making changes safely.","other-github-issue-checklist-md":"# Other — GITHUB_ISSUE_CHECKLIST.md\n\n# GITHUB_ISSUE_CHECKLIST.md\n\n## Purpose\n\n`GITHUB_ISSUE_CHECKLIST.md` is a project status document that maps open GitHub issues to the current state of the codebase. It is not executable code and does not participate in runtime behavior. Instead, it serves as a lightweight implementation audit for `harshvishu/BigRoosterWorkouts`, helping contributors quickly see which product ideas are already present, partially present, or still missing.\n\nThe checklist uses a simple interpretation rule:\n\n- `[x]` means the codebase already contains a substantial implementation for the issue title.\n- `[ ]` means the feature is missing, incomplete, or only partially present.\n\nThis makes the file useful as a planning and triage artifact when deciding what to build next.\n\n## How to read the checklist\n\nEach entry follows the pattern:\n\n- issue number\n- issue title\n- implementation status\n- short evidence note pointing to relevant code or explaining why the issue is still pending\n\nExample:\n\n- `[x] #21 Capture basic body metrics (weight & height) — implemented in `UserProfile` and `ProfileView``\n\nThis means the issue is considered addressed because the app already has model and UI support for weight and height tracking.\n\nBy contrast:\n\n- `[ ] #18 Add plate and warm-up calculators — no calculator implementation found`\n\nThis means no corresponding implementation was identified in the repository.\n\n## Coverage summary\n\nThe checklist spans a broad set of product areas in the workout app:\n\n- workout logging and session UX\n- timers and rest management\n- templates and saved routines\n- history, analytics, and records\n- profile and body metrics\n- reminders and home-screen recommendations\n- HealthKit integration\n- social, sharing, sync, and export features\n- watch, widget, and shortcut support\n- exercise library enhancements\n\nThe current state shows strong coverage for core training workflows and several retention/analytics features, while many sharing, sync, and companion-platform features remain unimplemented.\n\n## Implemented areas\n\nThe following issues are marked complete because the repository contains clear supporting code:\n\n### Workout logging and session experience\n\n- **#1 Implement rest timer with per-exercise controls**\n - Implemented via `RestTimerManager`\n - UI support exists in `BigRoosterWorkouts/Views/Components/RestTimerView.swift`\n - Rest controls are integrated into the workout session flow\n\n- **#2 Show previous workout data while logging**\n - Implemented through the shadow-set UI in `BigRoosterWorkouts/Views/WorkoutSessionView.swift`\n\n- **#3 Add set tagging and superset support**\n - Implemented via `SetTag`\n - Superset model fields and tagging UI are present in `WorkoutSessionView`\n\n- **#11 Allow notes on workouts and exercises**\n - Implemented via `sessionNotes`\n - Exercise-level notes are stored in `ExerciseLog.notes`\n - Editing/viewing is handled in `BigRoosterWorkouts/Views/WorkoutSessionNotesView.swift`\n\n### Training history, analytics, and achievements\n\n- **#5 Detect and celebrate personal records**\n - Implemented in `AchievementService`\n - Includes record detection, persistence, and badge presentation\n\n- **#7 Implement workout history and calendar**\n - Implemented in `WorkoutHistoryRepository`\n - Displayed in `BigRoosterWorkouts/Views/WorkoutHistoryView.swift`\n\n- **#13 Ship advanced charts and analytics**\n - Implemented in `BigRoosterWorkouts/Views/DetailedStatsView.swift`\n - Also surfaced in `HomeView`\n - Backed by `TrainingStatsCalculator`\n\n- **#17 Gamify workouts with streaks and badges**\n - Implemented through `AchievementService`\n - Includes streak tracking, badge assets, and history presentation in `RecordsHistoryView`\n\n### Templates, profile, and health integration\n\n- **#6 Integrate Apple Health**\n - Implemented with `HealthKitManager`\n - Requires entitlements and settings controls in `BigRoosterWorkouts/Views/SettingsView.swift`\n\n- **#8 Add workout templates and saved routines**\n - Implemented with `WorkoutTemplate`\n - Includes `TemplateListView`, `WorkoutTemplateBuilderView`, and quick templates in the start flow\n\n- **#21 Capture basic body metrics (weight & height)**\n - Implemented in `BigRoosterWorkouts/Models/UserProfile.swift`\n - Editable and viewable in `BigRoosterWorkouts/Views/ProfileView.swift`\n\n### Home experience and reminders\n\n- **#27 Add upcoming workout reminders and home preview**\n - Implemented via `UpcomingWorkoutRecommendationEngine`\n - Uses `WorkoutReminderNotificationScheduler`\n - Displays `UpcomingWorkoutPreviewCard`\n - Integrated into `HomeView` and `WorkoutStartSheet`\n\n## Pending or partially implemented areas\n\nThese issues are marked incomplete because the repository does not show a substantial end-to-end implementation for the title as written:\n\n- **#4 Build exercise library with demos**\n - Exercise/library data exists\n - No demo media workflow was found\n\n- **#9 Expand body measurements tracker**\n - Only basic weight and height are present\n - Broader measurement tracking is not implemented\n\n- **#10 Add progress photo tracking**\n - No progress photo workflow found\n\n- **#12 Add RPE/RIR tracking to sets**\n - No RPE or RIR fields/UI found\n\n- **#14 Implement Apple Watch companion app**\n - No WatchKit or watch target found\n\n- **#15 Add cloud sync and backup**\n - No backup or sync implementation found\n\n- **#16 Support CSV data export**\n - No CSV export flow found\n\n- **#18 Add plate and warm-up calculators**\n - No calculator implementation found\n\n- **#19 Implement social sharing features**\n - No user-facing social sharing feature set found\n\n- **#20 Support Siri Shortcuts and widgets**\n - Widget plumbing exists\n - No Siri Shortcuts/App Shortcut workflow found\n - The issue is therefore not fully implemented\n\n- **#22 Enable cross-device cloud sync for PRO**\n - No cross-device sync layer found\n\n- **#23 Enable routine sharing via links or QR**\n - No link-sharing or QR generation found\n\n- **#24 Add social feed and leaderboards**\n - No feed or leaderboard implementation found\n\n- **#25 Add workout sharing cards**\n - No share-card implementation found\n\n- **#28 Optimize shipped anatomy assets by replacing large vector PDFs with display-sized raster assets**\n - No clear anatomy asset optimization pass found\n\n## Relationship to the codebase\n\nThis file acts as a bridge between product planning and implementation. It references concrete modules and views across the app so contributors can jump from an issue to the relevant code area:\n\n- **Session flow**\n - `WorkoutSessionView`\n - `WorkoutSessionNotesView`\n - `RestTimerView`\n\n- **Home and recommendations**\n - `HomeView`\n - `WorkoutStartSheet`\n - `UpcomingWorkoutRecommendationEngine`\n - `WorkoutReminderNotificationScheduler`\n - `UpcomingWorkoutPreviewCard`\n\n- **History and analytics**\n - `WorkoutHistoryRepository`\n - `WorkoutHistoryView`\n - `DetailedStatsView`\n - `TrainingStatsCalculator`\n - `AchievementService`\n\n- **Profile and health**\n - `UserProfile`\n - `ProfileView`\n - `HealthKitManager`\n - `SettingsView`\n\n- **Templates**\n - `WorkoutTemplate`\n - `TemplateListView`\n - `WorkoutTemplateBuilderView`\n\nBecause the checklist is manually curated from repository inspection, it should be treated as a living document rather than a source of truth guaranteed by tests or build tooling.\n\n## Maintenance notes\n\nWhen updating this file:\n\n- keep the interpretation rule consistent\n- link each issue to the most relevant implementation evidence\n- distinguish between partial plumbing and a complete user-facing feature\n- revise statuses when code changes materially alter feature coverage\n\nIf the repository gains new modules for sharing, sync, export, widgets, or companion platforms, this checklist should be updated to reflect the new implementation state.","other-openspec":"# Other — openspec\n\n# Other — openspec\n\n## Purpose\n\nThe `openspec` module is the repository’s OpenSpec configuration entrypoint. It defines the project context and rule set used when generating or reviewing spec-driven artifacts for **BigRoosterWorkouts**, an iOS workout tracker.\n\nThis module does not contain runtime app code. Instead, it acts as a policy/configuration source that guides AI-assisted proposal and design generation for the rest of the codebase.\n\n## What it contains\n\nThe module currently consists of a single configuration file:\n\n- `openspec/config.yaml`\n\nThat file declares:\n\n- the OpenSpec schema version\n- project context for BigRoosterWorkouts\n- proposal-writing rules\n- design-documentation rules\n- task-planning rules\n\n## How it works\n\n`config.yaml` is consumed by the OpenSpec tooling to shape generated artifacts. The configuration is organized around three main concerns:\n\n1. **Context**\n - Describes the product, platform, language, architecture preferences, routing approach, state management conventions, concurrency expectations, UI system, and testing strategy.\n - This context is intended to keep generated proposals aligned with the app’s actual implementation standards.\n\n2. **Rules**\n - Defines how proposals should be written.\n - Defines how design documents should be structured.\n - Defines how implementation tasks should be broken down and verified.\n\n3. **Project constraints**\n - Reinforces architectural choices such as:\n - SwiftUI-first implementation\n - Observation-based state\n - AppRouter for navigation\n - strict concurrency awareness\n - DesignSystem token usage\n - privacy-sensitive handling of health/workout data\n\n## Configuration structure\n\n### `schema`\n\n```yaml\nschema: spec-driven\n```\n\nDeclares the OpenSpec schema used by this configuration.\n\n### `context`\n\nThe `context` block provides the AI with project-specific implementation guidance. It includes:\n\n- **Product scope**\n - BigRoosterWorkouts features such as workouts, plans, progress, achievements, records, reminders, widgets, and workout history.\n\n- **Platform and language**\n - iOS 26.0+\n - Swift 6 language mode\n - strict concurrency awareness\n - modern Apple frameworks and APIs\n\n- **Architecture**\n - feature-based folder organization\n - shared utilities only when genuinely cross-feature\n - view logic kept out of large SwiftUI bodies\n - separate files for distinct types unless tightly coupled/private\n\n- **Routing**\n - AppRouter as the navigation system\n - value-based routing with `NavigationStack`\n - no ad hoc navigation state\n\n- **State and concurrency**\n - `@Observable` + `@MainActor` for mutable state owners\n - `@State`, `@Bindable`, and `@Environment` for view ownership/passing\n - async/await preferred over callbacks\n - structured concurrency and cancellation awareness\n\n- **SwiftData and health domain**\n - sensitive data handling\n - local-first processing\n - explicit privacy impact for features touching personal data\n - CloudKit compatibility considerations when relevant\n\n- **UI and design system**\n - restrained workout-tracker aesthetic\n - `DesignSystem.Spacing`, `DesignSystem.Typography`, and `DesignSystem.Colors`\n - modern SwiftUI APIs and accessibility-friendly controls\n\n- **Testing**\n - unit tests for core logic and concurrency-sensitive code\n - UI tests only when necessary\n - verification expectations for build/run/test workflows\n\n### `rules`\n\nThe `rules` section is divided into three categories.\n\n#### `proposal`\n\nGuidance for writing feature proposals:\n\n- state the user-visible problem\n- identify the affected feature area\n- include non-goals\n- call out privacy impact for sensitive domains\n- mention whether AppRouter changes are needed\n- avoid third-party dependencies unless explicitly approved\n- keep proposals concise and implementation-oriented\n\n#### `design`\n\nGuidance for design documents:\n\n- describe feature-based file placement\n- identify state ownership clearly\n- include concurrency review\n- route through AppRouter for navigation and sheets\n- use DesignSystem tokens\n- document SwiftData migration and CloudKit concerns when relevant\n- document health permission timing and on-device processing\n- prefer modern SwiftUI APIs\n- avoid deprecated patterns and legacy APIs\n\n#### `tasks`\n\nGuidance for implementation task breakdown:\n\n- split work into small, verifiable chunks\n- inspect existing behavior before adding state or models\n- include focused unit tests\n- include verification steps when applicable\n- finish with checks for AGENTS.md rules, DesignSystem usage, AppRouter routing, and Swift concurrency warnings\n\n## Relationship to the rest of the codebase\n\n`openspec/config.yaml` influences how changes are proposed and documented across the repository, but it does not directly participate in app runtime behavior.\n\nIt is most relevant when working on:\n\n- feature proposals\n- architecture/design docs\n- implementation plans\n- code review guidance\n- AI-assisted spec generation\n\nBecause the configuration encodes project conventions, it helps keep future work consistent with the app’s established patterns, especially around:\n\n- SwiftUI and Observation\n- AppRouter navigation\n- SwiftData and privacy-sensitive data\n- design system usage\n- concurrency-safe implementation\n\n## Key conventions enforced by this module\n\n### State ownership\n\nThe configuration strongly prefers:\n\n- `@Observable` classes for mutable state\n- `@MainActor` isolation for observable state owners\n- `@State` for view-owned instances\n- `@Bindable` or `@Environment` for passing observable state\n\nIt explicitly discourages legacy patterns such as:\n\n- `ObservableObject`\n- `@Published`\n- `@StateObject`\n- `@ObservedObject`\n- `@EnvironmentObject`\n\n### Navigation\n\nThe module standardizes navigation around AppRouter and modern SwiftUI routing:\n\n- `NavigationStack`\n- `navigationDestination(for:)`\n- value-based routes\n- route enums\n\nIt discourages:\n\n- `NavigationView`\n- one-off navigation state\n- nested routers\n- destination-local routing without justification\n\n### Concurrency\n\nThe configuration emphasizes Swift 6 concurrency safety:\n\n- prefer async/await\n- avoid `DispatchQueue.main.async`\n- use `MainActor` and structured concurrency\n- consider task lifetime, cancellation, and Sendable boundaries\n\n### UI and design system\n\nThe module reinforces a consistent visual language:\n\n- `DesignSystem.Spacing`\n- `DesignSystem.Typography`\n- `DesignSystem.Colors`\n- `foregroundStyle()` instead of `foregroundColor()`\n- `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()`\n- modern Tab APIs instead of `tabItem()`\n\n### Privacy and sensitive data\n\nFor workout, health, nutrition, body-measurement, reminder, sync, widget, and export features, the configuration requires:\n\n- explicit privacy impact consideration\n- permission requests only when needed\n- clear user-facing purpose explanations\n- local/on-device processing by default\n\n## Practical impact for contributors\n\nWhen adding or changing features in BigRoosterWorkouts, this module should be treated as the source of truth for:\n\n- how proposals are framed\n- how design docs are structured\n- what implementation constraints must be respected\n- what verification steps should be included\n\nIf a proposed change conflicts with these rules, the proposal should either:\n\n- justify the exception explicitly, or\n- update the configuration if the project’s standards have intentionally changed\n\n## File reference\n\n### `openspec/config.yaml`\n\nPrimary OpenSpec configuration for the repository.\n\n- Defines schema and project context\n- Encodes proposal/design/task rules\n- Guides AI-generated artifacts for consistency with the app’s architecture and standards","other-project-xcworkspace":"# Other — project.xcworkspace\n\n# Other — `project.xcworkspace`\n\nThis module is the Xcode workspace container for the `BigRoosterWorkouts` project. It does not contain executable app logic, Swift types, or runtime behavior. Instead, it defines how Xcode opens and resolves the project and its Swift Package dependencies.\n\n## Purpose\n\n`project.xcworkspace` is the top-level workspace used by Xcode to coordinate:\n\n- the main Xcode project (`BigRoosterWorkouts.xcodeproj`)\n- Swift Package Manager dependency resolution\n- shared workspace metadata\n\nBecause this is a workspace-level module, it exists to support development and build tooling rather than application code.\n\n## Contents\n\n### `contents.xcworkspacedata`\n\nThis file defines the workspace structure:\n\n```xml\n<Workspace version=\"1.0\">\n <FileRef location=\"self:\">\n <\/FileRef>\n<\/Workspace>\n```\n\n#### What it means\n\n- `location=\"self:\"` indicates that the workspace references the project located alongside the workspace itself.\n- There are no additional nested projects or external file references declared here.\n- The workspace is intentionally minimal, which is typical for a single-project Xcode setup.\n\n### `xcshareddata/swiftpm/Package.resolved`\n\nThis file records the exact Swift Package Manager dependency versions used by the workspace.\n\nCurrent pinned dependency:\n\n- `AppRouter`\n - repository: `https://github.com/dimillian/AppRouter.git`\n - version: `1.0.4`\n - revision: `45dc8c4f3320c4a5e08b00a93d832fea036a7545`\n\n#### Why it matters\n\n`Package.resolved` ensures that all developers and CI environments resolve the same package revision, preventing dependency drift. In practice, this makes builds reproducible and keeps package-based behavior consistent across machines.\n\n## How it works\n\nThe workspace itself does not execute code. Its role is to provide Xcode with:\n\n1. a reference to the local project\n2. a shared record of resolved Swift package versions\n\nWhen the workspace is opened in Xcode:\n\n- Xcode reads `contents.xcworkspacedata` to locate the project\n- Xcode reads `Package.resolved` to determine which package revisions to fetch and use\n- the project and its dependencies are then available for building, testing, and editing\n\n## Relationship to the rest of the codebase\n\nThis workspace is the entry point for development tooling around the app:\n\n- it wraps the main `BigRoosterWorkouts.xcodeproj`\n- it ensures the `AppRouter` package is pinned to a known version\n- it does not define app features, UI, models, or business logic itself\n\nIf you are modifying application behavior, you will usually work in the project’s source files rather than in this workspace. If you are changing dependency versions or workspace composition, this is the relevant location.\n\n## Development notes\n\n### Updating dependencies\n\nIf the `AppRouter` package version changes, `Package.resolved` will be updated by Xcode or Swift Package Manager. Commit the updated file when the new version should be shared with the team.\n\n### Workspace stability\n\nBecause the workspace is minimal, it is unlikely to require frequent changes. Most updates here are related to:\n\n- dependency resolution\n- project structure changes\n- Xcode-managed workspace metadata\n\n## Summary\n\n`project.xcworkspace` is the workspace container for `BigRoosterWorkouts`. It provides the Xcode-level configuration needed to open the project and lock Swift package dependencies, but it contains no runtime code or execution flow of its own.","other-skills-lock-json":"# Other — skills-lock.json\n\n# Other — `skills-lock.json`\n\n`skills-lock.json` is a lockfile-style manifest that pins the exact skill definitions used by the project. It records which skills are enabled, where each skill comes from, and the computed content hash used to verify that the skill definition has not changed.\n\nThis file is data-only: it contains no functions, classes, or runtime logic. Its role is to provide reproducibility and integrity for skill resolution.\n\n## Purpose\n\nThe file serves three main purposes:\n\n- **Versioning**: tracks the schema version of the lockfile format.\n- **Source pinning**: records the upstream source for each skill.\n- **Integrity verification**: stores a `computedHash` for each skill so the resolved skill content can be validated against the expected state.\n\nIn practice, this means the project can detect when a skill definition has changed upstream and ensure the local configuration remains consistent.\n\n## Structure\n\nThe file has two top-level keys:\n\n- `version`: lockfile format version\n- `skills`: map of skill identifiers to their locked metadata\n\n### Example shape\n\n```json\n{\n \"version\": 1,\n \"skills\": {\n \"<skill-name>\": {\n \"source\": \"<owner/repo>\",\n \"sourceType\": \"github\",\n \"computedHash\": \"<sha256-like hash>\"\n }\n }\n}\n```\n\n## Fields\n\n### `version`\n\nThe lockfile schema version.\n\n- Current value: `1`\n- Used to interpret the structure of the file and support future format changes.\n\n### `skills`\n\nA dictionary keyed by skill name. Each entry describes one locked skill.\n\nEach skill entry contains:\n\n- `source`: the upstream repository or origin identifier\n- `sourceType`: the type of source, currently `github`\n- `computedHash`: the expected hash of the resolved skill content\n\n## Locked skills\n\nThe current lockfile includes four skills:\n\n| Skill name | Source | Source type | Hash |\n| --- | --- | --- | --- |\n| `find-dead-code` | `tobihagemann/turbo` | `github` | `702b6a147e8db0e9259c39bbff3ceaf752e9620b0b1821832dfb6225a733b3ce` |\n| `gh-issue-fix-flow` | `Dimillian/Skills` | `github` | `2ffe104b8df01be4e83c1783aa9d0dee8bc37c07fce7bc5a208846ccde9a2b8c` |\n| `github` | `Dimillian/Skills` | `github` | `a7580b43bdc65d4f6469ef7e631dbbea649714dc3bbedc060f23525d331a7e53` |\n| `ios-debugger-agent` | `Dimillian/Skills` | `github` | `ffaa86d5b055e161ec32acd535d5f7581825af0941c691a0c65e59380c04f11f` |\n\n## How it works\n\nAlthough this file does not execute code, it participates in the project’s skill resolution workflow:\n\n1. A skill is referenced by name.\n2. The resolver looks up the skill in `skills-lock.json`.\n3. The resolver uses `source` and `sourceType` to locate the upstream definition.\n4. The resolver computes the current content hash.\n5. The computed hash is compared with `computedHash`.\n6. If the hashes match, the skill is considered locked and valid.\n\nIf the hashes differ, the skill definition has likely changed and the lockfile may need to be regenerated or updated.\n\n## Relationship to the rest of the codebase\n\nBecause this module is a lockfile, its integration points are indirect:\n\n- It acts as a source of truth for skill metadata.\n- Other tooling in the repository likely reads this file to resolve or validate skills.\n- Changes here can affect which skill implementations are available and whether they are accepted as current.\n\nThere are no internal or outgoing function calls, and no runtime execution flow in this module.\n\n## Maintenance notes\n\nWhen updating skills:\n\n- Keep `version` aligned with the expected lockfile schema.\n- Update `source` and `sourceType` only when the upstream origin changes.\n- Regenerate `computedHash` whenever the underlying skill content changes.\n- Treat hash mismatches as a signal that the lockfile is stale or the upstream source has been modified.\n\n## Operational characteristics\n\n- **Deterministic**: the same file always resolves to the same set of skill references.\n- **Auditable**: each skill entry records its origin and integrity hash.\n- **Minimal**: no executable logic, only configuration data.\n\n## Mermaid overview\n\n```mermaid\nflowchart LR\n A[skills-lock.json] --> B[version]\n A --> C[skills map]\n C --> D[find-dead-code]\n C --> E[gh-issue-fix-flow]\n C --> F[github]\n C --> G[ios-debugger-agent]\n```\n\n## Notes for contributors\n\n- Preserve JSON validity and formatting.\n- Avoid editing hashes manually unless you are intentionally updating the locked content.\n- If a skill source changes, update the corresponding entry atomically so the source metadata and hash stay in sync.","other-specs":"# Other — specs\n\n# Other — specs\n\nThis module is a collection of OpenSpec specification documents. It does not contain executable code, runtime entry points, or internal call relationships. Instead, it defines product and implementation requirements that guide changes elsewhere in the codebase.\n\nThe specs in this module are used as the source of truth for design, cleanup, and utility behavior across the app. They describe expected outcomes, constraints, and verification scenarios for implementation work in SwiftUI, asset management, and repository maintenance.\n\n## What this module contains\n\n### `achievement-badge-assets`\nDefines requirements for achievement badge artwork and asset catalog coverage.\n\nKey expectations:\n- Every non-nil `AchievementService.catalogItems[].badgeAssetName` must resolve to an asset in `Assets.xcassets/badges`\n- Missing badge artwork must be generated as transparent PNG imagesets named exactly after the badge asset name\n- UI should prefer generated badge artwork over placeholder fallback symbols\n- Asset coverage must be auditable against the achievement catalog\n\nThis spec connects directly to:\n- `AchievementService.catalogItems`\n- `AchievementCatalogItem.badgeAssetName`\n- `ProfileAchievementsPreview`\n- `AchievementBadgeArtworkView`\n\n### `dead-code-cleanup`\nDefines the rules for removing unused UI components safely.\n\nKey expectations:\n- Only UI components with zero production references and zero test references may be removed\n- Active code, framework entry-point conventions, and plausible future features must be preserved\n- Cleanup must be validated by building the app and running existing tests\n\nThis spec is primarily a maintenance guardrail for repository cleanup work.\n\n### `shadcn-swiftui-component-primitives`\nDefines how shared SwiftUI components should adopt shadcn-inspired structure without losing native iOS behavior.\n\nKey expectations:\n- Preserve native SwiftUI controls and interaction behavior\n- Allow Liquid Glass or platform material where appropriate\n- Use selective grouping instead of wrapping everything in cards\n- Keep chips, badges, and filters native-feeling and theme-aware\n- Remove or repurpose over-literal `Shadcn*` primitives that push web-style UI\n\nThis spec informs shared UI component design across the app, especially reusable primitives.\n\n### `shadcn-swiftui-design-tokens`\nDefines the shared design grammar used by the app.\n\nKey expectations:\n- Native iOS spacing, typography, radius, and depth tokens should be derived from shadcn as a reference, not copied literally\n- Shared spacing rhythm should support compact rows, grouped content, and larger media/hero layouts\n- Typography roles must remain Dynamic Type friendly\n- Surface/depth roles should cover plain content, grouped content, raised surfaces, floating glass controls, and modal surfaces\n- Existing theme behavior must remain compatible with `DesignSystem.Colors` and `ThemeColorSet`\n\nThis spec is the foundation for styling decisions in the rest of the UI.\n\n### `shadcn-swiftui-screen-migration`\nDefines the migration strategy for screens moving toward the revised native iOS visual direction.\n\nKey expectations:\n- Screens should use native iOS controls, soft depth, clear typography, and selective grouping\n- Earlier over-literal shadcn-style changes must be reconciled before further migration\n- `ProfileView` is the preferred reference screen\n- High-traffic surfaces should be migrated in small verified batches\n- Verification should include screenshots or visual review in light mode, dark mode, and at least one custom theme\n- Intentional exceptions must be documented\n\nThis spec governs how visual changes are rolled out across the app.\n\n### `swiftui-conditional-helpers`\nDefines small SwiftUI utility helpers used by feature views.\n\nKey expectations:\n- A global availability helper returns whether the runtime satisfies `#available(iOS 26.0, *)`\n- `View.if` applies a transform only when a condition is true\n- `View.ifelse` selects between true and false transforms\n- The helpers must preserve SwiftUI type safety without `AnyView`, UIKit, third-party dependencies, or side effects\n\nThis is the only spec in this module that describes concrete helper APIs rather than product behavior.\n\n## How these specs fit into the codebase\n\nThese documents are not runtime modules themselves. They shape implementation in other parts of the app by defining:\n\n- UI composition rules for SwiftUI screens and shared components\n- Asset catalog expectations for achievement artwork\n- Cleanup criteria for removing dead UI code\n- Small SwiftUI helper APIs for conditional view composition\n\nIn practice, contributors should treat these specs as constraints when editing:\n- `DesignSystem`\n- shared SwiftUI primitives\n- profile and achievement views\n- asset catalog contents\n- repository cleanup changes\n\n## Relationship between the specs\n\nThe specs are complementary:\n\n- `shadcn-swiftui-design-tokens` defines the shared visual language\n- `shadcn-swiftui-component-primitives` defines reusable UI building blocks that consume those tokens\n- `shadcn-swiftui-screen-migration` applies both to real screens\n- `achievement-badge-assets` ensures achievement visuals are backed by real assets\n- `dead-code-cleanup` keeps the codebase lean while protecting active flows\n- `swiftui-conditional-helpers` provides small composition helpers used by feature views\n\nA simplified dependency view:\n\n```mermaid\nflowchart TD\n Tokens[shadcn-swiftui-design-tokens]\n Primitives[shadcn-swiftui-component-primitives]\n Screens[shadcn-swiftui-screen-migration]\n Assets[achievement-badge-assets]\n Cleanup[dead-code-cleanup]\n Helpers[swiftui-conditional-helpers]\n\n Tokens --> Primitives\n Tokens --> Screens\n Primitives --> Screens\n Assets --> Screens\n Helpers --> Screens\n Cleanup --> Screens\n```\n\n## Notes for contributors\n\n- These specs are intentionally implementation-guiding, not code.\n- When changing UI behavior, check whether the change is constrained by one of the shadcn-related specs.\n- When adding or renaming achievement badge assets, verify the catalog names remain stable.\n- When removing UI files, confirm there are no production or test references before deletion.\n- When adding conditional SwiftUI composition, prefer the `View.if` and `View.ifelse` helpers described here rather than introducing type erasure.\n\n## Current status\n\nSeveral specs still contain `TBD` purpose text inherited from archived changes. The requirements are the authoritative content for implementation until those purpose sections are updated.","other-testplan-xctestplan":"# Other — TestPlan.xctestplan\n\n# TestPlan.xctestplan\n\n`TestPlan.xctestplan` is the Xcode test plan configuration for the `BigRoosterWorkoutsTests` test target. It does not contain executable code; instead, it defines how Xcode should discover and run tests for the project.\n\nThis file is part of the test infrastructure for the `BigRoosterWorkouts.xcodeproj` workspace/project and is used by Xcode when running the test suite from the IDE, command line, or CI.\n\n## Purpose\n\nThe test plan centralizes test-run settings so they can be shared consistently across developers and automation. In this module, the plan currently:\n\n- Registers the `BigRoosterWorkoutsTests` target as the test target to execute\n- Enables test timeouts by default\n- Defines a single configuration named `Configuration 1`\n\nBecause this is a `.xctestplan` file, it influences test execution behavior but does not define application logic or test code itself.\n\n## Structure\n\nThe file is a JSON document with four top-level keys:\n\n- `configurations`\n- `defaultOptions`\n- `testTargets`\n- `version`\n\n### `configurations`\n\n```json\n\"configurations\": [\n {\n \"id\": \"9C49A84E-1ED3-4048-96BA-244A872EACC8\",\n \"name\": \"Configuration 1\",\n \"options\": {}\n }\n]\n```\n\nThis array defines named test configurations. Each configuration can override test options for a specific run.\n\nCurrent state:\n\n- One configuration exists: `Configuration 1`\n- Its `options` object is empty, so it does not override any defaults\n\nUse this section when you need to introduce environment-specific or scenario-specific test behavior, such as different launch arguments, environment variables, or code coverage settings.\n\n### `defaultOptions`\n\n```json\n\"defaultOptions\": {\n \"testTimeoutsEnabled\": true\n}\n```\n\nThese options apply to all test runs unless overridden by a configuration.\n\nCurrent behavior:\n\n- `testTimeoutsEnabled` is enabled\n\nThis means Xcode will enforce test timeouts during execution, helping catch hanging or slow tests earlier in local development and CI.\n\n### `testTargets`\n\n```json\n\"testTargets\": [\n {\n \"target\": {\n \"containerPath\": \"container:BigRoosterWorkouts.xcodeproj\",\n \"identifier\": \"8A1B2C3D4E5F60718293A401\",\n \"name\": \"BigRoosterWorkoutsTests\"\n }\n }\n]\n```\n\nThis section declares which test bundles or targets belong to the plan.\n\nCurrent target:\n\n- `BigRoosterWorkoutsTests`\n\nThe target is referenced by:\n\n- `containerPath`: the Xcode project containing the target\n- `identifier`: the target’s internal project identifier\n- `name`: the human-readable target name\n\nIf you add more test targets to the project, they should be added here so they are included in the plan.\n\n### `version`\n\n```json\n\"version\": 1\n```\n\nThe test plan schema version. This should generally be left unchanged unless Xcode requires an update.\n\n## How it connects to the codebase\n\nThis file sits at the boundary between the project configuration and the test code:\n\n- It points Xcode at the `BigRoosterWorkoutsTests` target\n- It controls runtime behavior for that target through shared defaults\n- It does not directly reference source files, classes, or functions\n- It affects how the test suite is executed, not what the tests assert\n\nIn practice, any XCTest cases defined in `BigRoosterWorkoutsTests` are governed by this plan when the plan is selected for a test run.\n\n## Operational impact\n\nBecause `testTimeoutsEnabled` is set to `true`, test runs are more likely to fail fast when a test hangs or exceeds expected runtime. This is useful for:\n\n- Preventing stalled CI jobs\n- Catching deadlocks or infinite waits in tests\n- Keeping feedback loops predictable\n\nSince the configuration options are currently empty, all behavior comes from the default options and the target definition.\n\n## Maintenance notes\n\nWhen updating this file, keep the following in mind:\n\n- Add new test targets to `testTargets` so they are included in the plan\n- Use `configurations` when you need multiple test scenarios\n- Keep `defaultOptions` aligned with team expectations for local and CI test runs\n- Preserve the target identifier unless the target is recreated in Xcode, in which case the identifier may change\n\n## Minimal execution model\n\n```mermaid\nflowchart LR\n Xcode[Xcode / CI] --> Plan[TestPlan.xctestplan]\n Plan --> Target[BigRoosterWorkoutsTests]\n Plan --> Options[Default test options]\n Target --> XCTest[XCTest execution]\n```\n\n## Summary\n\n`TestPlan.xctestplan` is the shared test execution definition for `BigRoosterWorkoutsTests`. It currently enables test timeouts, defines one empty configuration, and serves as the Xcode-managed entry point for running the project’s tests consistently.","other-views":"# Other — Views\n\n# Other — Views\n\nThis module contains reusable SwiftUI views and a theme/background editor used across the app. It includes:\n\n- `BackgroundBuilderView` and its supporting canvas/inspector views for editing theme backgrounds\n- Small reusable UI components such as `FilterChip`, `SearchBar`, and workout recommendation cards\n- Sheet-style views for date selection and filtering\n- A UIKit bridge for sharing exported content\n\nThe code is mostly declarative SwiftUI, but `BackgroundBuilderView` also coordinates state mutation, undo/redo, export, and persistence through `ThemeManager` and `AppRouter`.\n\n---\n\n## Module overview\n\nThe module is split into two broad groups:\n\n1. **Background editing**\n - `BackgroundBuilderView`\n - `BackgroundCanvasView`\n - `InspectorPanel`\n - `LayerRow`\n - `LayerInspector`\n - `ShareSheet`\n - `Array` safe subscript extension\n\n2. **Reusable UI components**\n - `FilterChip`\n - `SearchBar`\n - `UpcomingWorkoutPreviewCard`\n - `WorkoutRecommendationPreviewCard`\n - `WorkoutGoalSettingsControls`\n - `DatePickerSheet`\n - `FilterSheet`\n\nMost of the reusable components are simple presentation views. The background editor is the most stateful part of the module and is the main integration point with the rest of the app.\n\n---\n\n## Background editor\n\n### `BackgroundBuilderView`\n\n`BackgroundBuilderView` is a two-pane editor for building and saving a theme background.\n\nIt owns local editing state:\n\n- `theme`: the `AppTheme` being edited\n- `config`: the mutable `BackgroundConfiguration` currently being edited\n- `undoManager`: a `BackgroundUndoManager` used for undo/redo\n- `selectedLayerIndex`: the currently selected layer in layer-stack mode\n\nIt also reads environment dependencies:\n\n- `ThemeManager` for saving and exporting background data\n- `AppRouter` for presenting the share sheet\n- `dismiss` for closing the editor after save\n\n#### Layout\n\nThe body is an `HStack` with:\n\n- `BackgroundCanvasView(config:)` on the left\n- `InspectorPanel(...)` on the right\n\nThe view sets the navigation title to **Background Builder** and exposes toolbar actions for:\n\n- Undo\n- Redo\n- Export\n- Save\n\n#### Editing flow\n\nThe editor follows a simple pattern:\n\n1. The inspector mutates `config`\n2. The inspector calls `onConfigChange`\n3. `pushUndo()` stores the previous state in `BackgroundUndoManager`\n4. Undo/redo restores a prior `BackgroundConfiguration`\n5. Save writes the final config back through `ThemeManager`\n6. Export renders the config to an image and presents a share sheet\n\n#### Undo/redo behavior\n\n`BackgroundBuilderView` uses `BackgroundUndoManager` as a state history stack.\n\n- `pushUndo()` calls `undoManager.pushState(config)`\n- `performUndo()` replaces `config` with `undoManager.undo()`\n- `performRedo()` replaces `config` with `undoManager.redo()`\n- `.onAppear` resets the undo stack to the initial config\n\nThis means the editor treats `config` as the source of truth and snapshots it whenever the inspector reports a change.\n\n#### Save and export\n\n- `saveBackground()` calls `themeManager.updateThemeBackground(config, for: theme)` and then dismisses the view\n- `exportBackground()` calls `themeManager.exportBackgroundAsImage(config)` asynchronously and then routes to `.share(exportedURL)`\n\nExport errors are currently logged with `print(\"Export failed: \\(error)\")`.\n\n---\n\n### `BackgroundCanvasView`\n\n`BackgroundCanvasView` renders a `BackgroundConfiguration` into a visual preview.\n\nIt switches on `config.type` and renders one of:\n\n- `.solidColor` using `Color(hex:)`\n- `.gradient` using `renderGradient(_:)`\n- `.layerStack` using `renderLayerStack(_:)`\n- fallback gray for unsupported or incomplete configurations\n\nThe canvas is intentionally read-only. It reflects the current configuration state but does not mutate it.\n\n#### Gradient rendering\n\n`renderGradient(_:)` converts the model’s gradient stops into SwiftUI `Gradient.Stop` values and then renders one of:\n\n- `LinearGradient`\n- `RadialGradient`\n- `AngularGradient`\n\nThe gradient type is driven by `gradient.type`, while the start/end/center points come from the model’s unit-point abstractions.\n\n#### Layer stack rendering\n\n`renderLayerStack(_:)` sorts layers by `zIndex` and renders them in a `ZStack`.\n\nEach layer is passed to `renderLayer(_:)`, which applies:\n\n- size\n- position\n- rotation\n- scale\n- opacity\n- blend mode\n\nLayer content is currently limited to:\n\n- shape layers via `renderShape(_:)`\n- gradient layers via `renderGradient(_:)`\n\nUnsupported layer types render as `EmptyView()`.\n\n#### Shape rendering\n\n`renderShape(_:)` supports:\n\n- rectangle\n- circle\n- ellipse\n- capsule\n\nEach shape uses `getFill(_:)` for its fill style and overlays a stroke when `shape.stroke` is present.\n\n`getFill(_:)` supports:\n\n- solid fills via `Color(hex:)`\n- gradient fills via a linear gradient built from the fill’s gradient config\n\nIf a gradient fill is requested but no gradient config exists, the code falls back to white.\n\n---\n\n### `InspectorPanel`\n\n`InspectorPanel` is the editing sidebar for `BackgroundBuilderView`.\n\nIt receives:\n\n- `config` as a binding\n- `selectedLayerIndex` as a binding\n- `undoManager` for undo/redo state\n- `onConfigChange` callback to notify the parent when edits occur\n\nThe panel is organized by background type:\n\n- **Solid Color**\n- **Gradient**\n- **Layer Stack**\n\nWhen the background type changes, `initializeConfigForType()` ensures the corresponding configuration object exists before the user starts editing it.\n\n#### Type-specific controls\n\n- **Solid color**\n - Uses a `ColorPicker`\n - Writes back to `config.solidColor`\n- **Gradient**\n - Shows an initialization button if `config.gradientConfig` is `nil`\n - Otherwise shows a simple configured state message\n- **Layer stack**\n - Shows a layer list\n - Allows adding layers\n - Shows a layer inspector for the selected layer\n\n#### Layer stack editing\n\n`addLayer()` lazily initializes `config.layerStack` if needed, then appends a new default shape layer.\n\nThe new layer is created with:\n\n- a generated name like `Layer 1`\n- a rectangle shape\n- solid red fill\n- default position and size\n\nThe newly added layer becomes selected immediately.\n\nThe layer list uses `LayerRow` for each item and updates `selectedLayerIndex` on tap.\n\nThe selected layer is edited through `LayerInspector`, using `binding(for:)` to create a binding into the nested `layers` array.\n\n---\n\n### `LayerRow`\n\n`LayerRow` is a compact list row for a single `CanvasLayer`.\n\nIt shows:\n\n- a square icon\n- the layer name\n- a checkmark when selected\n\nSelected rows get a light blue background highlight.\n\nThis view is purely presentational and is used only inside `InspectorPanel`.\n\n---\n\n### `LayerInspector`\n\n`LayerInspector` edits the currently selected `CanvasLayer`.\n\nIt supports:\n\n- renaming the layer\n- adjusting opacity\n- editing shape-specific properties when the layer is a shape layer\n\nFor shape layers, it exposes:\n\n- a shape picker\n- a fill color picker\n\nWhen the user changes a value, the view writes back through the `@Binding var layer` and calls `onConfigChange()` so the parent can snapshot the change for undo.\n\nOnly shape-layer editing is implemented here. Other layer types are rendered but not fully editable in this panel.\n\n---\n\n### `ShareSheet`\n\n`ShareSheet` is a `UIViewControllerRepresentable` wrapper around `UIActivityViewController`.\n\nIt is used to present exported background images through the system share sheet.\n\nThis view is not directly invoked inside `BackgroundBuilderView`; instead, the exported URL is routed through `AppRouter` using `.share(exportedURL)`.\n\n---\n\n### `Array` safe subscript extension\n\nThe module adds:\n\n```swift\nextension Array {\n subscript(safe index: Int) -> Element?\n}\n```\n\nThis returns `nil` instead of crashing when the index is out of bounds.\n\nIt is used in the layer inspector and layer list to safely access nested layer arrays while editing mutable state.\n\n---\n\n## Background editor flow\n\n```mermaid\nflowchart LR\n A[BackgroundBuilderView] --> B[BackgroundCanvasView]\n A --> C[InspectorPanel]\n C --> D[LayerRow]\n C --> E[LayerInspector]\n A --> F[BackgroundUndoManager]\n A --> G[ThemeManager]\n A --> H[AppRouter]\n```\n\n### Typical interaction sequence\n\n1. `BackgroundBuilderView` loads with an initial `AppTheme`\n2. `undoManager.reset(with:)` seeds the history stack\n3. The user edits values in `InspectorPanel`\n4. `onConfigChange` pushes the previous config into undo history\n5. `BackgroundCanvasView` re-renders from the updated config\n6. Save persists the config through `ThemeManager`\n7. Export renders the config to an image and opens sharing\n\n---\n\n## Reusable UI components\n\n### `FilterChip`\n\n`FilterChip` is a pill-shaped toggle button used for filter selection.\n\nIt takes:\n\n- `title`\n- `isSelected`\n- `action`\n\nThe visual style changes based on selection state:\n\n- selected chips use accent colors and selected accessibility traits\n- unselected chips use surface colors and a subtle stroke\n\nThis component is designed to be lightweight and reusable anywhere a compact filter toggle is needed.\n\n---\n\n### `SearchBar`\n\n`SearchBar` is a reusable search input with optional focus synchronization.\n\nIt binds to:\n\n- `searchText`\n- `isFocused`\n\nIt also accepts:\n\n- `prompt`\n- `action` to run on submit\n\n#### Behavior\n\n- Shows a magnifying glass icon\n- Uses a `TextField` with `.search` submit behavior\n- Displays a clear button when text is non-empty\n- Synchronizes focus state in both directions using `@FocusState`\n\nThis makes it suitable for screens that need programmatic focus control, such as workout search and history search.\n\n---\n\n### `UpcomingWorkoutPreviewCard`\n\n`UpcomingWorkoutPreviewCard` displays an `UpcomingWorkoutRecommendation`.\n\nIt presents:\n\n- a heading\n- the workout name\n- planned reminder time\n- reminder text\n- rationale badge\n\nThe card uses the app’s design system for typography, spacing, and surface styling.\n\n---\n\n### `WorkoutRecommendationPreviewCard`\n\n`WorkoutRecommendationPreviewCard` is a wrapper that chooses the correct preview card for a `WorkoutRecommendation` enum case.\n\nIt delegates to:\n\n- `UpcomingWorkoutPreviewCard` for `.upcomingWorkout`\n- `PlanRecommendationPreviewCard` for plan-based recommendations\n- a supplemental eyebrow helper for `.supplementalWorkout`\n\nThe private helper `supplementalEyebrow(for:)` maps `SupplementalWorkoutReason` to user-facing labels such as:\n\n- Recovery Workout\n- Goal Booster\n- Extra Workout\n\nThis view centralizes recommendation presentation logic so callers do not need to switch on the enum themselves.\n\n---\n\n### `WorkoutGoalSettingsControls`\n\n`WorkoutGoalSettingsControls` is a small settings fragment for calorie goal configuration.\n\nIt exposes:\n\n- `dailyCalorieGoalEnabled`\n- `dailyCalorieGoal`\n\nWhen enabled, it shows a `Stepper` constrained to `100...2_000` in increments of `50`.\n\nThis component is intended to be embedded in settings screens rather than used as a standalone page.\n\n---\n\n### `DatePickerSheet`\n\n`DatePickerSheet` is a modal sheet for selecting a date.\n\nIt uses:\n\n- `@Environment(\\.dismiss)` to close the sheet\n- `@Binding var selectedDate` to update the caller’s state\n\nThe picker is constrained to the last 365 days through a computed `dateRange`.\n\nThe sheet includes:\n\n- a graphical `DatePicker`\n- Done and Cancel toolbar actions\n\nBoth toolbar actions simply dismiss the sheet; the selected date is already bound to the parent.\n\n---\n\n### `FilterSheet`\n\n`FilterSheet` is a SwiftData-backed filter editor for exercise search criteria.\n\nIt uses:\n\n- `@Bindable var filterState: FilterState`\n- `@Query private var searchIndices: [ExerciseSearchIndex]`\n\nThe sheet derives unique filter values from the query results for:\n\n- muscle\n- equipment\n- mechanics\n- utility\n- force\n- difficulty\n\n#### Behavior\n\n- Shows a **Clear All Filters** destructive action when any filters are active\n- Renders one section per available filter category\n- Uses `Toggle` rows bound directly to the corresponding `Set` properties on `FilterState`\n- Dismisses via a Done button in the toolbar\n\nThe view relies on `themedListRow()` and `themedListBackground()` for consistent list styling.\n\n---\n\n## Data flow and dependencies\n\n### Theme/background integration\n\n`BackgroundBuilderView` is the only view in this module that directly mutates theme data.\n\nIt depends on:\n\n- `ThemeManager.updateThemeBackground(_:for:)`\n- `ThemeManager.exportBackgroundAsImage(_:)`\n- `AppRouter.presentSheet(.share(...))`\n\nThis makes it the bridge between the editor UI and the app’s theme storage/export pipeline.\n\n### Model types used by the editor\n\nThe background editor works with the background model layer, including:\n\n- `BackgroundConfiguration`\n- `GradientConfiguration`\n- `LayerStackConfiguration`\n- `CanvasLayer`\n- `ShapeLayer`\n- `FillType`\n- `Position`\n- `Size`\n\nThe editor assumes these types are value-semantic and can be copied into local state for editing.\n\n### Shared utilities and extensions\n\nSeveral views depend on shared infrastructure outside this module:\n\n- `Color(hex:)` and `toHex()` for color conversion\n- `BackgroundUndoManager` for history\n- `DesignSystem` for styling\n- `themedListRow()` / `themedListBackground()` for list appearance\n- `SwiftData` query support in `FilterSheet`\n\n---\n\n## Contribution notes\n\n### Background editor\n\nWhen extending the background editor, keep these patterns in mind:\n\n- Mutate `config` through bindings or local helper methods\n- Call `onConfigChange()` after any user-driven mutation so undo history stays accurate\n- Initialize nested config objects before exposing controls for them\n- Keep rendering logic in `BackgroundCanvasView` and editing logic in `InspectorPanel`\n\nIf you add new layer types or fill modes, update both:\n\n- the canvas rendering path\n- the inspector editing path\n\n### Undo/redo\n\nUndo history is snapshot-based. If you introduce new mutation paths, make sure they call `pushUndo()` before or during the change flow as appropriate.\n\n### Safe array access\n\nUse the `safe` subscript when reading from mutable layer arrays in UI code. This avoids crashes when selection state and array contents temporarily diverge.\n\n### Reusable components\n\nThe smaller components are intentionally narrow in scope. Prefer composing them rather than expanding them into multi-purpose controls unless there is a clear reuse case.\n\n---\n\n## Summary of key views\n\n- `BackgroundBuilderView`: full-screen background editor with undo, save, and export\n- `BackgroundCanvasView`: read-only renderer for background configurations\n- `InspectorPanel`: editing sidebar for background type and layer stack\n- `LayerInspector`: per-layer property editor\n- `SearchBar`: reusable search input with focus synchronization\n- `FilterSheet`: filter editor backed by SwiftData\n- `WorkoutRecommendationPreviewCard`: recommendation dispatcher for workout cards\n\nThis module is primarily UI-focused, but `BackgroundBuilderView` also serves as an important integration point between the theme model, export pipeline, and app routing.","other-xcshareddata":"# Other — xcshareddata\n\n# Other — xcshareddata\n\nThe `xcshareddata` module contains Xcode project metadata that is shared across all users of the repository. It does not define runtime code, business logic, or callable APIs. Instead, it configures how Xcode builds, runs, tests, profiles, analyzes, and archives the `BigRoosterWorkouts` app.\n\nThis directory is part of the Xcode project bundle:\n\n- `BigRoosterWorkouts.xcodeproj/xcshareddata/xcschemes/BigRoosterWorkouts.xcscheme`\n\n## Purpose\n\nShared Xcode data is used to keep project-level development settings under version control so every contributor opens the same scheme configuration in Xcode.\n\nIn this module, the shared scheme defines:\n\n- which app target is built\n- which test bundle is executed\n- which test plan is used\n- which build configurations are used for each action\n- launch-time environment variables\n- archive and profile behavior\n\nBecause this is shared data, it affects the developer workflow rather than app behavior at runtime.\n\n## Contents\n\n### `BigRoosterWorkouts.xcscheme`\n\nThis is the shared scheme for the `BigRoosterWorkouts` app target.\n\nIt binds the scheme to:\n\n- the app product: `BigRoosterWorkouts.app`\n- the test bundle: `BigRoosterWorkoutsTests.xctest`\n- the test plan: `TestPlan.xctestplan`\n\nThe scheme is stored with `LastUpgradeVersion=\"2640\"` and scheme format `version=\"1.7\"`.\n\n## How the scheme is structured\n\nThe scheme is organized into standard Xcode actions:\n\n- `BuildAction`\n- `TestAction`\n- `LaunchAction`\n- `ProfileAction`\n- `AnalyzeAction`\n- `ArchiveAction`\n\nEach action controls a different Xcode workflow.\n\n### BuildAction\n\nThe build action is configured to build the main app target:\n\n- `buildForTesting=\"YES\"`\n- `buildForRunning=\"YES\"`\n- `buildForProfiling=\"YES\"`\n- `buildForArchiving=\"YES\"`\n- `buildForAnalyzing=\"YES\"`\n\nThis means the `BigRoosterWorkouts` target participates in all major scheme-driven workflows.\n\nThe buildable reference points to:\n\n- `BlueprintIdentifier=\"3AF1C0602F3F25A300408C23\"`\n- `BuildableName=\"BigRoosterWorkouts.app\"`\n- `BlueprintName=\"BigRoosterWorkouts\"`\n\n### TestAction\n\nThe test action is configured for the `Debug` build configuration and uses LLDB for both debugging and launching tests.\n\nIt references:\n\n- `TestPlan.xctestplan` as the default test plan\n- `BigRoosterWorkoutsTests.xctest` as the test bundle\n\nImportant settings:\n\n- `shouldUseLaunchSchemeArgsEnv=\"YES\"`: launch arguments and environment variables from the scheme are applied to tests\n- `parallelizable=\"YES\"`: the test bundle can run in parallel where supported\n\n### LaunchAction\n\nThe launch action controls how the app starts when run from Xcode.\n\nIt uses:\n\n- `Debug` build configuration\n- LLDB debugger and launcher\n- location simulation enabled\n\nIt also sets one environment variable:\n\n- `OS_ACTIVITY_MODE=disable`\n\nThis suppresses some system logging noise during debugging.\n\n### ProfileAction\n\nThe profile action uses:\n\n- `Release` build configuration\n\nThis is the standard setup for Instruments profiling, where optimized release builds are typically preferred.\n\n### AnalyzeAction\n\nThe analyze action uses:\n\n- `Debug` build configuration\n\nThis is used by Xcode static analysis.\n\n### ArchiveAction\n\nThe archive action uses:\n\n- `Release` build configuration\n- `revealArchiveInOrganizer=\"YES\"`\n\nThis ensures archived builds are created from the release configuration and shown in Xcode’s Organizer after archiving.\n\n## Relationship to the rest of the codebase\n\nThis module connects to the rest of the project through Xcode target references rather than source-level calls.\n\nIt references:\n\n- the app target `BigRoosterWorkouts`\n- the test target `BigRoosterWorkoutsTests`\n- the test plan `TestPlan.xctestplan`\n\nThese references make the scheme the coordination point for common developer tasks:\n\n- running the app\n- executing tests\n- profiling performance\n- archiving release builds\n\n## Execution model\n\nThere is no runtime execution flow in this module. Xcode reads the scheme metadata and uses it to determine which targets and configurations to invoke for each action.\n\n```mermaid\nflowchart TD\n Scheme[BigRoosterWorkouts.xcscheme]\n App[BigRoosterWorkouts.app]\n Tests[BigRoosterWorkoutsTests.xctest]\n TestPlan[TestPlan.xctestplan]\n Xcode[Xcode Actions]\n\n Xcode --> Scheme\n Scheme --> App\n Scheme --> Tests\n Scheme --> TestPlan\n```\n\n## Developer notes\n\n### When to update this file\n\nUpdate the shared scheme when you need to change project-wide development behavior, such as:\n\n- adding or removing test bundles\n- switching the default test plan\n- changing launch environment variables\n- adjusting which target is built for a given action\n- changing archive or profile build configurations\n\n### Why this file matters\n\nBecause it is shared, changes here affect every developer and CI workflow that relies on the scheme. Keeping it under version control helps ensure consistent behavior across machines and Xcode installations.\n\n### Common maintenance considerations\n\n- Keep the scheme aligned with the actual target names and identifiers in the Xcode project.\n- Ensure the referenced test plan exists at `container:TestPlan.xctestplan`.\n- Verify that the app and test targets remain valid if targets are renamed or recreated.\n- Be cautious when changing launch environment variables, since they affect debugging behavior for all contributors.\n\n## Summary\n\n`xcshareddata` is a project configuration module, not application code. Its primary responsibility is to define the shared Xcode scheme for `BigRoosterWorkouts`, ensuring consistent build, test, run, profile, analyze, and archive behavior across the team.","overview":"# BigRoosterWorkouts — Wiki\n\n# BigRoosterWorkouts\n\nBigRoosterWorkouts is a SwiftUI workout companion app for planning training, logging sessions, tracking progress, and surfacing the right next action on the home screen. It combines workout plan creation, in-session logging, history and statistics, achievements, reminders, widgets, HealthKit integration, and customizable appearance/backgrounds into one cohesive experience.\n\nIf you’re new to the codebase, start with [App Entry & Root Navigation](app-entry-root-navigation.md) to understand how the app boots, shares state, and routes between major screens. From there, the most important feature areas are [Home Dashboard](home-dashboard.md), [Workout Session](workout-session.md), [Workout Plan Creation](workout-plan-creation.md), [Workout History & Statistics](workout-history-statistics.md), and [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md).\n\n## How the app is organized\n\nThe repository is structured around a small set of core layers:\n\n- **App shell and navigation** — the launch point, shared environment setup, and root routing live in [App Entry & Root Navigation](app-entry-root-navigation.md).\n- **Domain and persistence** — workout plans, sessions, achievements, themes, backgrounds, and related records are defined in [Core Domain Models](core-domain-models.md).\n- **Planning and recommendations** — schedule progression and workout suggestions are handled by [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md).\n- **Workout creation and logging** — users build plans in [Workout Plan Creation](workout-plan-creation.md) and log workouts in [Workout Session](workout-session.md).\n- **Insights and history** — completed workouts feed [Workout History & Statistics](workout-history-statistics.md).\n- **Personalization and surface area** — the home experience, profile/settings, themes, backgrounds, achievements, notifications, widgets, and HealthKit integrations are split across their respective modules.\n\nA useful mental model is:\n\n1. The app launches and seeds/configures shared services.\n2. The home screen shows a recommendation or active workout entry point.\n3. The user either starts a session, edits a plan, or reviews history.\n4. Completed workouts update achievements, widgets, reminders, and statistics.\n5. The home dashboard reflects the latest state.\n\n## High-level architecture\n\n```mermaid\nflowchart TD\n A[App Entry & Root Navigation]\n B[Core Domain Models]\n C[Home Dashboard]\n D[Workout Plan Creation]\n E[Workout Session]\n F[Recommendations & Plan Scheduling]\n G[Workout History & Statistics]\n H[Achievements & Badges]\n I[Health, Activity & Widgets]\n J[Theme & Appearance]\n\n A --> B\n A --> C\n A --> D\n A --> E\n A --> F\n A --> G\n A --> H\n A --> I\n A --> J\n\n C --> F\n D --> B\n D --> F\n E --> B\n E --> F\n E --> G\n E --> H\n E --> I\n G --> H\n I --> G\n J --> C\n```\n\n## Key end-to-end flows\n\n### Launch and app setup\nThe app starts in [App Entry & Root Navigation](app-entry-root-navigation.md), which bootstraps SwiftData, configures TipKit, seeds exercise data, and injects shared managers into the environment. This module also owns the top-level navigation shell, so most navigation and sheet presentation is coordinated from one place.\n\n### Home experience and next-action routing\nThe [Home Dashboard](home-dashboard.md) is the landing surface. It combines the current recommendation, recent training summary, active workout controls, achievement celebration, and background configuration. It depends heavily on [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md) and [Home Backgrounds & Weather](home-backgrounds-weather.md), with layout support from [Vertical Split Layout](vertical-split-layout.md).\n\n### Plan creation and scheduling\nUsers create structured training plans in [Workout Plan Creation](workout-plan-creation.md). Those plans are persisted as domain models and then interpreted by [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md), which advances plan progress, resolves the next scheduled workout, and produces recommendation candidates for the UI.\n\n### Workout logging\nThe workout logging flow lives in [Workout Session](workout-session.md). The UI layer and feature layer work together to manage exercise edits, set input, rest timing, completion, and deletion. Session completion feeds downstream systems such as history, achievements, widgets, and live activity updates.\n\n### History, stats, and achievements\nCompleted sessions are aggregated by [Workout History & Statistics](workout-history-statistics.md) into summaries, streaks, records, and analytics. That data also powers [Achievements & Badges](achievements-badges.md), which detects milestones, persists achievement events, and drives celebration UI.\n\n### Health, widgets, and reminders\n[Health, Activity & Widgets](health-activity-widgets.md) bridges the app to HealthKit, Live Activities, and widget refreshes. [Notifications & Rest Timer](notifications-rest-timer.md) handles in-workout rest countdowns and workout reminder notifications, while [Shared Utilities & UI Helpers](shared-utilities-ui-helpers.md) provides the cross-cutting helpers used throughout the app.\n\n### Personalization\nAppearance and theming are managed by [Theme & Appearance](theme-appearance.md). Home wallpaper selection and weather-aware presets are handled by [Home Backgrounds & Weather](home-backgrounds-weather.md). Profile and settings live in [Profile & Settings](profile-settings.md).\n\n## Important execution flows\n\nA few flows are especially important when reading the code:\n\n- **Delete workout session**: deleting a workout in the session UI triggers widget refreshes and history recalculation, so the app stays consistent across home, widgets, and analytics.\n- **Restart rest timer**: the rest timer manager coordinates countdown state, notification cancellation, polling, and Live Activity updates.\n- **Plan day resolution**: starting a workout from a plan resolves the next scheduled day using plan progress and schedule state, then routes the user into the appropriate session flow.\n\nThese flows are implemented across [Workout Session](workout-session.md), [Workout History & Statistics](workout-history-statistics.md), [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md), [Notifications & Rest Timer](notifications-rest-timer.md), and [Health, Activity & Widgets](health-activity-widgets.md).\n\n## Data and seed content\n\nThe app ships with static seed assets under [Data & Seed Assets](data-seed-assets.md), and the conversion scripts in [Scripts](scripts.md) generate and normalize those files. This is how the initial exercise and workout content is prepared for the app.\n\n## Suggested reading order\n\nIf you want to understand the codebase quickly, read these in order:\n\n1. [App Entry & Root Navigation](app-entry-root-navigation.md)\n2. [Core Domain Models](core-domain-models.md)\n3. [Recommendations & Plan Scheduling](recommendations-plan-scheduling.md)\n4. [Home Dashboard](home-dashboard.md)\n5. [Workout Plan Creation](workout-plan-creation.md)\n6. [Workout Session](workout-session.md)\n7. [Workout History & Statistics](workout-history-statistics.md)\n8. [Achievements & Badges](achievements-badges.md)\n9. [Health, Activity & Widgets](health-activity-widgets.md)\n10. [Theme & Appearance](theme-appearance.md)\n\n## Setup\n\nIf you’re getting the project running locally:\n\n- Open the Xcode project/workspace for **BigRoosterWorkouts**\n- Select a simulator or device target\n- Build and run the app from Xcode\n- If you’re working on data generation or seed updates, review [Scripts](scripts.md) and [Data & Seed Assets](data-seed-assets.md)\n\nFor feature work, it’s usually best to start from the module page for the area you’re changing, then trace into the shared domain models and recommendation/history flows that it depends on.","profile-settings":"# Profile & Settings\n\n# Profile & Settings Module\n\nThis module implements the app’s user-facing profile, settings, and credits screens. It combines SwiftUI views with SwiftData-backed profile state, app preferences stored in `@AppStorage`, and router-driven navigation to the rest of the app.\n\nThe module is split across three files:\n\n- `ProfileView.swift` — profile dashboard, edit profile sheet, and sheet container\n- `ProfileDashboardViews.swift` — reusable profile dashboard subviews and row/item models\n- `SettingsView.swift` — app preferences, health integration, and navigation to credits\n- `CreditsView.swift` — attribution and external resource links\n\n## Responsibilities\n\nThe module provides:\n\n- A profile dashboard with:\n - avatar/photo selection\n - editable username\n - workout stats and body metrics\n - achievement preview\n - shortcuts to workout-related screens\n - theme and wallpaper preview\n- A settings screen with:\n - units and workout preferences\n - notification and feedback toggles\n - Apple Health authorization\n - about/credits navigation\n - placeholder destructive actions\n- A credits screen with:\n - wallpaper attribution\n - library attribution\n - external links to source resources\n\n## Data and state sources\n\nThe module reads and writes state from several app-wide systems:\n\n- `SwiftData`\n - `@Query private var profiles: [UserProfile]`\n - `@Query private var achievements: [AchievementEvent]`\n- `AppStorage`\n - settings preferences such as weight unit, rest time, notifications, reminders, and calorie goals\n- Environment objects\n - `AppRouter` for navigation and sheet presentation\n - `ThemeManager` for theme selection and appearance mode\n - `HomeBackgroundManager` for wallpaper/background configuration\n - `HealthKitManager` for Health authorization state\n- Photos\n - `PhotosPickerItem` for profile photo selection\n\n## Architecture overview\n\n```mermaid\nflowchart TD\n CV[ContentView] --> PV[ProfileView]\n CV --> SV[SettingsView]\n CV --> ED[EditProfileSheetContainer]\n CV --> CR[CreditsView]\n\n PV --> PDV[ProfileDashboardViews.swift]\n PV --> AR[AppRouter]\n PV --> TM[ThemeManager]\n PV --> HBM[HomeBackgroundManager]\n PV --> SD[(SwiftData: UserProfile, AchievementEvent)]\n\n SV --> HK[HealthKitManager]\n SV --> AR\n SV --> TM\n SV --> AP[AppStorage preferences]\n\n ED --> EP[EditProfileSheet]\n EP --> SD\n CR --> UL[External URLs]\n```\n\n## ProfileView\n\n`ProfileView` is the main dashboard for the signed-in/local user profile. It is marked `@MainActor` and uses `SwiftUI` plus `SwiftData` queries to render the current profile and achievement state.\n\n### Core behavior\n\n`ProfileView`:\n\n- resolves the active profile from `@Query`\n- derives achievement preview data from `AchievementService.shared`\n- renders a vertically stacked dashboard inside a `ScrollView`\n- supports:\n - changing the profile photo via `PhotosPicker`\n - inline username editing\n - navigation to workout history, achievements, plans, templates, custom exercises, theme list, home background, and settings\n - opening the edit profile sheet for body metrics\n\n### Profile resolution\n\n```swift\nprivate var profile: UserProfile {\n profiles.first ?? UserProfile()\n}\n```\n\nIf no persisted profile exists, the view falls back to a transient `UserProfile()` instance. This keeps the UI functional even before the first profile record is created.\n\n### Achievement preview logic\n\nThe dashboard shows a compact achievement preview based on the current achievement history:\n\n- `catalogItems` comes from `AchievementService.shared.catalogItems`\n- `unlockedCatalogItems` filters catalog items using `unlockedCatalogItemIDs(from:)`\n- `lockedCatalogItems` is the complement\n- `achievementPreviewItems` builds up to three preview cells:\n - unlocked items show the latest achievement date when available\n - if fewer than three are unlocked, locked placeholders fill the remaining slots\n\nThis logic keeps the preview visually stable while still reflecting progress.\n\n### Metric and stat items\n\n`ProfileView` builds two separate collections:\n\n- `statItems` for the top summary strip:\n - streak\n - workouts\n - body weight\n- `metricItems` for the editable metrics grid:\n - current weight\n - target weight\n - height\n\nUnset values are displayed as `--` in the summary and marked with `isUnset` in the grid.\n\n### Navigation actions\n\nThe view uses `AppRouter` to navigate to other app destinations:\n\n- `showWorkoutHistory()` → `.workoutHistory(selectedDate: nil)`\n- `showAchievements()` → `.recordsHistory`\n- `showCustomExercises()` → `.customExercises`\n- `showWorkoutPlans()` → `.workoutPlans`\n- `showWorkoutTemplates()` → `.templateList`\n- `showThemeList()` → `.themeList`\n- `showHomeBackground()` → `.homeBackground`\n- `showSettings()` → `.settings`\n\n`showEditProfile()` presents `.editProfile` as a sheet.\n\n### Username editing flow\n\nThe username editor is controlled by:\n\n- `isEditingUsername`\n- `usernameDraft`\n\nFlow:\n\n1. `startEditingUsername()` copies the current username into `usernameDraft` and enables edit mode.\n2. `ProfileDashboardHeader` focuses the text field when editing begins.\n3. `saveUsername()` validates the draft:\n - empty draft cancels editing\n - non-empty draft is written back to `profile.username`\n4. `cancelEditingUsername()` restores the draft from the current profile and exits edit mode\n\nThe save path intentionally calls `cancelEditingUsername()` when the draft is empty, so the UI always returns to a consistent non-editing state.\n\n### Photo selection flow\n\n`ProfileView` stores the selected picker item in:\n\n```swift\n@State private var selectedPhoto: PhotosPickerItem?\n```\n\nThe `.task(id: selectedPhoto)` modifier loads the selected image data asynchronously:\n\n```swift\nif let data = try? await selectedPhoto?.loadTransferable(type: Data.self) {\n profile.photoData = data\n}\n```\n\nThis updates the persisted profile photo whenever the user picks a new image.\n\n### Body metrics editing\n\n`ProfileView` opens `EditProfileSheet` through the router. The sheet edits:\n\n- weight\n- target weight\n- height\n\nThe sheet writes values directly back to the passed `UserProfile` and dismisses itself on save.\n\n## EditProfileSheet\n\n`EditProfileSheet` is a simple `NavigationStack` + `Form` editor for body measurements.\n\n### Initialization\n\nThe sheet initializes its text fields from the current profile values:\n\n- `weight`\n- `targetWeight`\n- `height`\n\nEach value is converted to a string if present, otherwise initialized as an empty string.\n\n### Save behavior\n\n```swift\nprivate func save() {\n profile.weight = Double(weight)\n profile.targetWeight = Double(targetWeight)\n profile.height = Double(height)\n dismiss()\n}\n```\n\nThe conversion uses `Double(...)`, so invalid or empty input becomes `nil`. This keeps the model tolerant of partial edits.\n\n## EditProfileSheetContainer\n\n`EditProfileSheetContainer` exists to ensure there is a profile available before presenting the editor.\n\n### Behavior\n\n- queries existing `UserProfile` records\n- if one exists, presents `EditProfileSheet(profile:)`\n- otherwise:\n - shows `ProgressView`\n - inserts a new `UserProfile()` into the model context\n - stores it in `createdProfile`\n\nThis avoids presenting an editor with no backing model object.\n\n## ProfileDashboardViews\n\nThis file contains the reusable building blocks used by `ProfileView`. It also defines the lightweight item models that drive those views.\n\n## Item models\n\n### `ProfileDashboardStatItem`\n\nUsed by `ProfileDashboardStatsStrip`.\n\nFields:\n\n- `title`\n- `value`\n- `systemImage`\n- `tint`\n\n### `ProfileMetricItem`\n\nUsed by `ProfileMetricsGrid`.\n\nFields:\n\n- `title`\n- `value`\n- `systemImage`\n- `tint`\n- `isUnset`\n\n### `ProfileAchievementPreviewItem`\n\nUsed by `ProfileAchievementsPreview`.\n\nFields:\n\n- `title`\n- `assetName`\n- `placeholderSymbolName`\n- `isLocked`\n- `detail`\n\n### `ProfileActionRowItem`\n\nUsed by `ProfileActionSection`.\n\nFields:\n\n- `title`\n- `subtitle`\n- `systemImage`\n- `swatches`\n- `action`\n\nAll of these types conform to `Identifiable` using `title` as the identifier, so titles should remain unique within each collection.\n\n## `ProfileDashboardHeader`\n\nThis is the top section of the profile screen.\n\nIt combines:\n\n- `PhotosPicker` for avatar selection\n- `ProfileAvatar` for image rendering\n- `ProfileUsernameEditor` for inline username editing\n- member-since text\n\nIt also manages focus state for the username field:\n\n- when `isEditingUsername` becomes `true`, the text field is focused automatically\n\n## `ProfileUsernameEditor`\n\nA private subview that switches between display and edit modes.\n\n### Display mode\n\nShows:\n\n- the current username\n- an edit button\n\n### Edit mode\n\nShows:\n\n- a centered `TextField`\n- save button\n- cancel button\n\nThe text field uses:\n\n- `.textInputAutocapitalization(.words)`\n- `.submitLabel(.done)`\n- `.onSubmit(onSave)`\n\nThis keeps the editing experience compact and keyboard-friendly.\n\n## `ProfileAvatar`\n\nRenders the profile photo or a fallback avatar.\n\n### Rendering rules\n\n- If `photoData` can be decoded into a `UIImage`, it is shown as a circular image.\n- Otherwise, a default circle with `person.fill` is displayed.\n- A camera badge overlays the bottom-right corner to indicate editability.\n\nThis view is wrapped by `PhotosPicker`, so tapping the avatar opens the image picker.\n\n## `ProfileDashboardStatsStrip`\n\nDisplays the top-line stats in a horizontal strip with dividers between cells.\n\nIt is designed for exactly the three summary items built by `ProfileView`, but it will render any array of `ProfileDashboardStatItem`.\n\n## `ProfileAchievementsPreview`\n\nShows:\n\n- unlocked/total badge count\n- a “View all” action\n- a horizontal scroll of achievement badges\n\nThe badge cells are rendered by `ProfileAchievementPreviewCell`, which delegates artwork display to `AchievementBadgeArtworkView`.\n\n## `ProfileMetricsGrid`\n\nDisplays the fitness metrics section.\n\n### Layout\n\n- header row with title and edit button\n- 3-column `LazyVGrid`\n- each tile rendered by `ProfileMetricTile`\n\nThe edit button triggers the sheet presentation path in `ProfileView`.\n\n## `ProfileActionSection`\n\nA reusable grouped list section for navigation rows.\n\nIt renders:\n\n- section title\n- a vertical stack of `ProfileActionRow` entries\n- dividers between rows\n\nThis is used for both workout-related actions and app-related actions.\n\n## `ProfileThemePreviewSection`\n\nThis section combines theme and wallpaper controls in one card-like block.\n\n### Inputs\n\n- `themeName`\n- `swatches`\n- `configuration`\n- `preset`\n- `themeManager`\n\n### Behavior\n\nIt presents three related controls:\n\n1. Theme preview row\n - shows the current theme name\n - displays color swatches\n - navigates to the theme list\n\n2. Home background row\n - shows the current wallpaper preset or default state\n - navigates to home background settings\n\n3. Appearance picker\n - binds directly to `themeManager.appearanceMode`\n - uses a segmented picker over `AppAppearanceMode.allCases`\n\n### Derived labels\n\nThe section computes:\n\n- `title`\n - `\"Dynamic Home\"` or the preset title when background is enabled\n - `\"Default Home\"` when disabled\n- `subtitle`\n - `\"Home wallpaper, separate from theme\"`\n - `\"Use theme background\"`\n\nThis keeps the UI aligned with the current background configuration.\n\n## `ProfileActionRow`\n\nA tappable row used inside `ProfileActionSection`.\n\nIt shows:\n\n- leading icon badge\n- title\n- subtitle\n- optional color swatches\n- trailing chevron\n\nIf `swatches` is empty, the swatch cluster is omitted.\n\n## SettingsView\n\n`SettingsView` is the app’s preferences screen. It uses `Form` sections and persists most values through `@AppStorage`.\n\n## Stored preferences\n\nThe view binds directly to these keys:\n\n- `weightUnit`\n- `defaultRestTime`\n- `notificationsEnabled`\n- `soundEnabled`\n- `hapticEnabled`\n- `workoutReminderEnabled`\n- `workoutReminderHour`\n- `workoutReminderMinute`\n- `dailyCalorieGoalEnabled`\n- `dailyCalorieGoal`\n\nThese values are stored outside SwiftData, so they persist independently of the profile model.\n\n## Weight unit\n\n`WeightUnit` is a simple `String`-backed enum:\n\n- `.kg`\n- `.lbs`\n\nThe picker stores the raw string value in `@AppStorage(\"weightUnit\")`.\n\n## Workout reminder binding\n\n`workoutReminderTime` is a computed `Binding<Date>` that bridges the hour/minute storage model to a `DatePicker`-style API used by `WorkoutReminderSettingsControls`.\n\n- `get` builds a `Date` from stored hour/minute components\n- `set` extracts hour/minute from the selected date and writes them back\n\nThis keeps the persisted representation compact while still supporting a date-based control.\n\n## Sections\n\n### Units & Preferences\n\nContains:\n\n- weight unit picker\n- default rest time stepper\n\n### Notifications & Feedback\n\nContains:\n\n- notifications toggle\n- workout reminder controls\n- daily calorie goal controls\n- sound toggle\n- haptic toggle\n\nThe reminder and goal controls are delegated to:\n\n- `WorkoutReminderSettingsControls`\n- `WorkoutGoalSettingsControls`\n\n### Health Integration\n\nUses `HealthKitManager` to manage authorization.\n\n#### Authorization toggle\n\nThe toggle is bound to a custom `Binding<Bool>`:\n\n- reading reflects `healthKitManager.isAuthorized`\n- enabling triggers `requestAuthorization()`\n- disabling is not supported directly from the toggle and the control is disabled once authorized\n\n#### Open Health Settings\n\nWhen authorized, the view shows a button that opens:\n\n- `x-apple-health://`\n\nThis deep link takes the user to the Health app settings.\n\n### About\n\nContains:\n\n- static version display\n- external links to GitHub and Twitter\n- navigation to credits via `router.navigateTo(.credits)`\n\n### Danger Zone\n\nContains placeholder destructive actions:\n\n- Clear Cache\n- Reset All Settings\n\nThese buttons are present in the UI but their actions are not implemented yet.\n\n## CreditsView\n\n`CreditsView` provides attribution for bundled wallpaper resources and third-party libraries.\n\nIt uses a `Form` with themed sections and links out to source URLs.\n\n## Sections\n\n### Intro\n\nShows `CreditIntroRow` and a footer explaining why source links are kept in-app.\n\n### Wallpapers\n\nIterates over `UnsplashCreditItem.homeWeatherWallpapers` and renders each item with `CreditLinkRow`.\n\n### Libraries\n\nCurrently lists:\n\n- `AppRouter`\n - version `1.0.4`\n - linked to the upstream GitHub repository\n\n## Credit row components\n\n### `CreditIntroRow`\n\nA small explanatory row with a sparkles icon and attribution text.\n\n### `CreditLinkRow`\n\nWraps `CreditRowContent` in a `Link` when a URL is available. If `url` is `nil`, it falls back to a non-link row.\n\n### `CreditRowContent`\n\nShared visual content for credit rows:\n\n- leading icon badge\n- title\n- subtitle\n- trailing arrow indicator\n\n## `UnsplashCreditItem`\n\nA private `Identifiable` model used only by `CreditsView`.\n\nIt defines the wallpaper attribution list:\n\n- title\n- photographer\n- system image\n- URL\n\nThe static `homeWeatherWallpapers` array contains the bundled wallpaper credits for weather-themed backgrounds.\n\n## Integration with the rest of the app\n\nThis module is wired into the app’s navigation layer through `AppRouter` and is typically reached from `ContentView` destinations.\n\n### Entry points\n\n- `ProfileView`\n- `SettingsView`\n- `CreditsView`\n- `EditProfileSheetContainer` as the sheet content for profile editing\n\n### Navigation destinations used\n\nThe module expects router destinations such as:\n\n- `.editProfile`\n- `.workoutHistory(selectedDate:)`\n- `.recordsHistory`\n- `.customExercises`\n- `.workoutPlans`\n- `.templateList`\n- `.themeList`\n- `.homeBackground`\n- `.settings`\n- `.credits`\n\n## Design system usage\n\nAll views rely heavily on shared design tokens and modifiers:\n\n- `DesignSystem.Colors`\n- `DesignSystem.Typography`\n- `DesignSystem.Spacing`\n- `DesignSystem.CornerRadius`\n- `DesignSystem.NativeStyle.Depth.softShadow`\n- `.themedListRow()`\n- `.themedListBackground()`\n\nThis keeps the profile/settings area visually consistent with the rest of the app.\n\n## Implementation notes\n\n- `ProfileView` and `SettingsView` are both `@MainActor`-friendly SwiftUI screens that depend on environment state.\n- `ProfileView` uses a transient fallback `UserProfile()` when no record exists, but `EditProfileSheetContainer` is responsible for creating a persisted profile before editing.\n- `ProfileThemePreviewSection` binds directly to `themeManager.appearanceMode`, so appearance changes are immediate.\n- `ProfileActionRowItem`, `ProfileMetricItem`, and related models use `title` as their identifier; avoid duplicate titles in the same collection.\n- `CreditsView` keeps attribution data local and static, making it easy to audit bundled resources.\n\n## Extending the module\n\nWhen adding new profile or settings features:\n\n- prefer adding new reusable row/item types in `ProfileDashboardViews.swift`\n- keep navigation actions in `ProfileView` routed through `AppRouter`\n- store persistent preferences in `@AppStorage` when they are app-wide settings\n- use `SwiftData` only for profile/achievement domain data\n- update `CreditsView` whenever new bundled assets or external libraries are introduced","recommendations-plan-scheduling":"# Recommendations & Plan Scheduling\n\n# Recommendations & Plan Scheduling\n\nThis module contains the core logic for:\n\n- evaluating workout plan progress and schedule state\n- advancing plan enrollments as workouts are completed or skipped\n- generating plan-based recommendations for the UI\n- selecting an upcoming standalone workout recommendation for reminders and fallback suggestions\n- combining plan, reminder, and goal signals into a single ranked recommendation list\n\nIt is centered around three services:\n\n- `WorkoutPlanService`\n- `UpcomingWorkoutRecommendationEngine`\n- `UnifiedWorkoutRecommendationService`\n\nand a set of recommendation models in `WorkoutRecommendation.swift`.\n\n---\n\n## Module overview\n\nThe module separates recommendation concerns into two layers:\n\n1. **Plan scheduling and state management**\n - `WorkoutPlanService` computes progress snapshots, determines what day is due next, advances enrollments, and creates workout sessions.\n - It is the source of truth for plan lifecycle transitions.\n\n2. **Recommendation assembly**\n - `UpcomingWorkoutRecommendationEngine` chooses a single standalone workout template to suggest when no plan workout should be surfaced.\n - `UnifiedWorkoutRecommendationService` merges active plan recommendations, supplemental suggestions, upcoming workouts, and generic plan recommendations into a ranked list for the home screen and workout start sheet.\n\nThe result is a recommendation system that can answer questions like:\n\n- What workout plan day is due right now?\n- Is today a rest day, a future day, or a missed workout?\n- Should the app suggest a standalone workout instead?\n- Which plan should be recommended based on recent training history?\n- Should calorie-goal or adherence-recovery messaging be shown?\n\n---\n\n## Key types\n\n### `WorkoutPlanService`\n\nThe main scheduling and plan lifecycle service.\n\nResponsibilities include:\n\n- resolving the current scheduled day for an enrollment\n- computing `WorkoutPlanProgressSnapshot`\n- determining `WorkoutPlanStartState`\n- recommending plans based on recent activity and equipment\n- starting and ending plans\n- starting workout sessions from plan days\n- skipping scheduled days\n- completing plan days after a workout session\n- evaluating adherence for a session\n\nThis service is used by views such as `WorkoutPlanDetailView`, `WorkoutPlansView`, and `WorkoutStartSheet`.\n\n---\n\n### `UpcomingWorkoutRecommendationEngine`\n\nSelects a single `UpcomingWorkoutRecommendation` from saved templates and recent sessions.\n\nIt prefers:\n\n1. the template least recently matched to completed workout history\n2. otherwise the newest saved template\n\nIt also respects:\n\n- `hasActiveSession`\n- `WorkoutReminderPreferences`\n- the current reference date and calendar\n\n---\n\n### `UpcomingWorkoutRecommendationFormatter`\n\nConverts a template and reminder date into:\n\n- `UpcomingWorkoutRecommendation`\n- `WorkoutReminderContent`\n\nThis keeps reminder text generation consistent across the app, including notification scheduling.\n\n---\n\n### `UnifiedWorkoutRecommendationService`\n\nBuilds the recommendation list used by the home screen and workout start sheet.\n\nIt combines:\n\n- active plan recommendations\n- supplemental workout recommendations\n- upcoming standalone workout recommendations\n- generic recommended plans\n\nIt also suppresses recommendations when the user already has an active workout session in the home context.\n\n---\n\n### Recommendation models\n\nDefined in `WorkoutRecommendation.swift`:\n\n- `WorkoutRecommendationContext`\n- `WorkoutPlanScheduleRelation`\n- `WorkoutPlanSchedulePresentation`\n- `WorkoutRecommendation`\n- `ActivePlanWorkoutRecommendation`\n- `PlanRestDayRecommendation`\n- `FuturePlanWorkoutRecommendation`\n- `RecommendedWorkoutPlanRecommendation`\n- `SupplementalWorkoutRecommendation`\n- `SupplementalWorkoutReason`\n- `PlanRestDayRecommendationKind`\n\nThese types are mostly value objects used by the UI to render recommendation cards and schedule labels.\n\n---\n\n## Architecture and data flow\n\n```mermaid\nflowchart LR\n A[WorkoutPlanService] --> B[WorkoutPlanProgressSnapshot]\n B --> C[WorkoutRecommendation models]\n D[UpcomingWorkoutRecommendationEngine] --> E[UpcomingWorkoutRecommendation]\n F[UnifiedWorkoutRecommendationService] --> C\n F --> E\n F --> G[WorkoutRecommendation list]\n```\n\n`WorkoutPlanService` produces the scheduling state that drives most recommendation decisions. \n`UnifiedWorkoutRecommendationService` then uses that state, plus upcoming workout selection and plan ranking, to produce the final list shown in the UI.\n\n---\n\n## `WorkoutPlanService`\n\n`WorkoutPlanService` is the most important type in this module. It owns the logic for plan progression and schedule interpretation.\n\n### Initialization\n\n```swift\ninit(calendar: Calendar = .current)\n```\n\nThe calendar is injected so scheduling logic can be tested deterministically and can respect locale/calendar behavior.\n\n---\n\n### Active enrollment helpers\n\n#### `activeEnrollment(from:)`\nReturns the first active enrollment after sorting.\n\n#### `activeEnrollments(from:)`\nReturns all active enrollments sorted by:\n\n1. most recent `startedAt`\n2. enrollment ID as a tie-breaker\n\nThis ordering is used when multiple active enrollments exist.\n\n---\n\n### Progress snapshots\n\n#### `progressSnapshots(plans:enrollments:statuses:recentSessions:referenceDate:)`\nBuilds snapshots for all enrollments matching the requested statuses.\n\n#### `progressSnapshots(for:enrollments:statuses:recentSessions:referenceDate:)`\nSame as above, but scoped to a single plan.\n\n#### `progressSnapshot(plan:enrollment:recentSessions:referenceDate:)`\nComputes a `WorkoutPlanProgressSnapshot` for one enrollment.\n\nThis method is the foundation for schedule-aware UI and recommendation logic. It derives:\n\n- completed and skipped day indices\n- current scheduled day and date\n- next workout day and date\n- total workout and scheduled day counts\n- plan status\n- schedule status\n- adherence summary for today\n\n### Snapshot sorting\n\nSnapshots are sorted by a custom priority:\n\n1. active enrollments before inactive/completed ones\n2. schedule status priority\n3. current scheduled date\n4. remaining workout day count\n5. enrollment start date\n6. plan title\n\nThis ensures the most actionable plan appears first.\n\n---\n\n### Schedule resolution\n\nThe service resolves schedule state using a small internal scheduling model:\n\n- `resolvedSchedule(in:enrollment:referenceDate:)`\n- `nextWorkoutResolution(from:in:)`\n- `scheduleStatus(schedule:nextWorkout:enrollment:referenceDate:)`\n\nThe schedule resolution logic handles:\n\n- missing `nextScheduledDayIndex`\n- missing `nextScheduledDate`\n- fallback scheduling based on `lastCompletedDayIndex`\n- rest-day progression\n- future-dated workouts\n- completed plans\n\n#### Important behavior\n\nIf the current scheduled day is a rest day and its date is in the past, `resolvedSchedule` advances until it finds the next scheduled day.\n\nThis is why the module can correctly recover from stale schedule state without relying on elapsed calendar days alone.\n\n---\n\n### Start state derivation\n\n#### `startState(for:recommendations:)`\nConverts a snapshot into a `WorkoutPlanStartState`.\n\nPossible results:\n\n- `.none`\n- `.recommended([WorkoutPlanSuggestion])`\n- `.activeWorkout(...)`\n- `.activeRestDay(...)`\n- `.futureWorkout(...)`\n- `.completed(...)`\n\nThis is the primary bridge between scheduling and the workout start UI.\n\n#### `activePlanState(plans:enrollments:recentSessions:exercises:)`\nConvenience wrapper that:\n\n1. generates plan recommendations\n2. finds the first active snapshot\n3. returns the corresponding `WorkoutPlanStartState`\n\n---\n\n### Plan recommendations\n\n#### `recommendPlans(plans:recentSessions:exercises:limit:)`\nRanks plans using recent workout history and equipment usage.\n\nScoring signals:\n\n- overlap between recent exercise names and plan exercises\n- overlap between recently used equipment and plan equipment tags\n- beginner-friendly training level\n- smaller equipment footprint\n\nThe returned `WorkoutPlanSuggestion` values include:\n\n- `id`\n- `plan`\n- `reason`\n- `score`\n\nThe reason text is intentionally human-readable and used directly by the UI.\n\n---\n\n### Plan lifecycle operations\n\n#### `startPlan(_:startDate:in:)`\nCreates a new `UserWorkoutPlan` enrollment and inserts it into the `ModelContext`.\n\nIt initializes:\n\n- `startedAt`\n- `nextScheduledDayIndex`\n- `nextScheduledDate`\n\n#### `endPlan(_:in:)`\nMarks an enrollment as ended and sets `endedAt`.\n\n#### `startWorkoutSession(plan:day:enrollment:in:)`\nCreates a `WorkoutSession` and copies plan metadata into it.\n\nIt also creates `ExerciseLog` entries for each exercise in the selected day, preserving sort order and planned prescription text.\n\n#### `startSelectedWorkoutSession(plan:day:preferredEnrollment:in:)`\nStarts a workout session for a selected day.\n\n- returns `nil` for rest days\n- starts the plan first if no enrollment is provided\n- then creates the workout session\n\n#### `skipCurrentScheduledDay(plan:enrollment:referenceDate:in:)`\nSkips the current scheduled day if the enrollment is active.\n\nFlow:\n\n1. resolve the current schedule\n2. append the day index to `skippedDayIndices` if needed\n3. call `advanceEnrollment`\n4. save the context\n\n#### `completePlanDayIfNeeded(for:in:)`\nUpdates enrollment state after a workout session is completed.\n\nIt:\n\n1. resolves the enrollment for the session\n2. evaluates adherence\n3. stores adherence metrics on the session\n4. if the threshold is met, marks the day complete and advances the enrollment\n\nIf the session does not meet the threshold, the enrollment is not advanced.\n\n---\n\n### Adherence evaluation\n\n#### `evaluateAdherence(for:)`\nComputes a `WorkoutPlanAdherenceSummary` from the session’s exercise logs.\n\nA session meets the threshold when completion ratio is at least `0.7`.\n\n#### `exerciseSummary(for:)`\nDetermines whether an exercise log is completed or skipped.\n\nIt uses:\n\n- `performedSetCount`\n- `plannedSetsText`\n- `isSkipped`\n\nIf `plannedSetsText` contains a number, that number is used as the minimum planned set count.\n\n#### `minimumPlannedSetCount(from:)`\nExtracts the first numeric token from the planned sets text.\n\n---\n\n### Internal advancement logic\n\n#### `advanceEnrollment(_:from:in:referenceDate:)`\nMoves an enrollment to the next scheduled day after the given day index.\n\nIf no future scheduled day exists:\n\n- `nextScheduledDayIndex` becomes `nil`\n- `nextScheduledDate` becomes `nil`\n- enrollment status becomes `.completed`\n- `completedAt` is set\n\nThis method is used by both skipping and completing plan days.\n\n#### `followingWorkoutDayIndex(in:after:)`\nFinds the next non-rest workout day after a given day index.\n\nThis is used to populate recommendation metadata such as `nextNonRestDayIndex`.\n\n---\n\n## `WorkoutPlanProgressSnapshot`\n\n`WorkoutPlanProgressSnapshot` is the central read model for plan state.\n\nIt contains both raw schedule data and derived convenience properties:\n\n- `completedScheduledDayCount`\n- `skippedScheduledDayCount`\n- `remainingWorkoutDayCount`\n- `nextWorkoutDayIndex`\n- `hasIncompleteWorkoutToday`\n\nThe snapshot is designed to be consumed by UI and recommendation code without requiring them to re-run scheduling logic.\n\n---\n\n## `WorkoutPlanSchedulePresentation`\n\n`WorkoutPlanSchedulePresentation` turns schedule state into display-ready labels and descriptions.\n\nUseful properties:\n\n- `dayLabel`\n- `workoutProgressLabel`\n- `badgeText`\n- `scheduledDateText`\n- `nextWorkoutDateText`\n- `activePlanDescription`\n\nThe `relation` value drives the wording:\n\n- `.dueToday`\n- `.missed`\n- `.future`\n- `.completedToday`\n- `.plannedRestDay`\n\nThis type keeps presentation logic out of the views and avoids duplicating schedule phrasing.\n\n---\n\n## `UpcomingWorkoutRecommendationEngine`\n\nThis engine chooses a single standalone workout template recommendation.\n\n### `upcomingWorkoutRecommendation(...)`\n\nReturns `nil` when:\n\n- there is an active session\n- there are no templates\n\nOtherwise it:\n\n1. computes the planned reminder date from `WorkoutReminderPreferences`\n2. sorts completed sessions by most recent date\n3. tries to find the best matching template\n4. falls back to the newest template if no match is found\n\n### Matching strategy\n\n#### `matchedTemplate(in:from:)`\nFinds the template with the oldest most-recent-match date.\n\nThis means the template that has gone the longest without being trained is preferred.\n\n#### `mostRecentMatchingDate(for:sessions:)`\nReturns the most recent session date that matches a template.\n\n#### `matchingScore(for:session:)`\nScores a session-template match using:\n\n- template/session name similarity\n- shared exercise names\n\nName matches are weighted heavily, while shared exercises contribute additional signal.\n\n#### `sessionCandidateNames(for:)`\nUses:\n\n- `sourceWorkoutPlanDayTitle`\n- `sourceWorkoutPlanTitle`\n\n#### `namesMatch(_:_: )`\nPerforms localized substring matching after trimming whitespace.\n\n#### `normalize(_:)`\nNormalizes strings for exercise comparison by:\n\n- trimming whitespace\n- lowercasing\n- removing diacritics\n- removing punctuation and other separators\n\n### Fallback strategy\n\n#### `fallbackTemplate(in:)`\nChooses the newest template by `createdAt`, with name as a tie-breaker.\n\nThis ensures the engine always has a sensible suggestion when history matching fails.\n\n---\n\n## `UpcomingWorkoutRecommendationFormatter`\n\nThis formatter keeps reminder text consistent across the app.\n\n### `recommendation(for:plannedReminderDate:rationale:)`\nBuilds an `UpcomingWorkoutRecommendation` from a template and reminder date.\n\nIt generates:\n\n- `plannedReminderTimeText`\n- normalized rationale text\n- `reminderText`\n\nThe rationale is normalized to end with a period.\n\n### `content(from:)`\nConverts a recommendation into `WorkoutReminderContent` for notification or UI display.\n\n---\n\n## `UnifiedWorkoutRecommendationService`\n\nThis service assembles the final recommendation list for the app.\n\n### Initialization\n\n```swift\ninit(\n planService: WorkoutPlanService = WorkoutPlanService(),\n upcomingWorkoutEngine: UpcomingWorkoutRecommendationEngine = UpcomingWorkoutRecommendationEngine()\n)\n```\n\nThe dependencies are injectable for testing and composition.\n\n---\n\n### `primaryRecommendation(...)`\n\nReturns the first recommendation from `recommendations(...)` in the `.home` context.\n\nThis is the simplest entry point for the home screen when only one recommendation is needed.\n\n---\n\n### `recommendations(...)`\n\nBuilds a ranked list of `WorkoutRecommendation` values.\n\nInputs include:\n\n- plans\n- enrollments\n- templates\n- recent sessions\n- exercises\n- active session state\n- reminder preferences\n- goal preferences\n- context\n- reference date\n- calendar\n- recommended plan limit\n\n### High-level flow\n\n1. If the context is `.home` and there is an active workout session, return no recommendations.\n2. Build active plan snapshots from `WorkoutPlanService`.\n3. Convert snapshots into active plan recommendations.\n4. Build generic recommended plans, excluding plans already represented by active recommendations.\n5. Build an upcoming workout recommendation unless there is an active session.\n6. Build supplemental recommendations when appropriate.\n7. Merge the results differently depending on context.\n\n### Context behavior\n\n#### `.home`\n- If there is an active plan recommendation, it is placed first.\n- Supplemental recommendations follow.\n- Generic recommended plans are appended last.\n- If there is no active plan recommendation, the upcoming workout may be inserted before generic plan recommendations.\n\n#### `.workoutStartSheet`\n- Active plan recommendations come first.\n- Supplemental recommendations follow.\n- Generic recommended plans follow.\n- If there are no active plan recommendations, the upcoming workout is appended at the end.\n\nThis makes the home screen more selective while the workout start sheet exposes more options.\n\n---\n\n### Active plan recommendations\n\n#### `activePlanRecommendations(from:referenceDate:calendar:)`\nMaps `WorkoutPlanProgressSnapshot` values into recommendation variants:\n\n- `.activePlan(...)` for due workouts\n- `.planRestDay(...)` for planned rest days and completed-for-today states\n- `.futurePlan(...)` for future workouts\n\nCompleted snapshots are ignored.\n\n#### `schedulePresentation(for:day:relation:nextWorkoutDayIndex:nextWorkoutDate:calendar:)`\nBuilds `WorkoutPlanSchedulePresentation` for a specific day.\n\nIt uses:\n\n- `workoutOrdinal(for:in:)`\n- `followingWorkoutDayIndex(in:after:)`\n- `nextWorkoutDateAfter(_:from:in:calendar:)`\n\n#### `incompletePlanDetail(for:)`\nGenerates a detail string when today’s adherence summary exists but does not meet threshold.\n\nThis is used to explain why a plan workout is still due.\n\n#### `dueWorkoutRelation(scheduledDate:referenceDate:calendar:)`\nDistinguishes between:\n\n- `.dueToday`\n- `.missed`\n\nbased on whether the scheduled date is before the reference date.\n\n---\n\n### Generic recommended plans\n\n#### `recommendedPlanRecommendations(plans:recentSessions:exercises:excluding:limit:)`\nUses `WorkoutPlanService.recommendPlans(...)`, then:\n\n- limits recent sessions to the last 12\n- filters out plans already represented by active plan recommendations\n- wraps results in `.recommendedPlan(...)`\n\nThis prevents duplicate plan suggestions from appearing alongside active plan cards.\n\n---\n\n### Supplemental recommendations\n\n#### `supplementalRecommendations(...)`\nAdds extra workout suggestions when there is an upcoming workout available.\n\nPossible reasons:\n\n- `.adherenceRecovery`\n- `.calorieGoal`\n- `.extraWorkout`\n\nThe method prefers more specific reasons first:\n\n1. incomplete workout today\n2. calorie goal not yet met\n3. generic extra workout when no active plan workout is due\n\nIf no upcoming workout exists, no supplemental recommendation is produced.\n\n#### `caloriesBurned(on:from:calendar:)`\nSums calories for non-active sessions on the reference date using `WorkoutHistoryRepository.sessionCalories(_:)`.\n\n---\n\n## Recommendation model behavior\n\n### `WorkoutRecommendation`\n\nAn enum wrapper around all recommendation variants.\n\nIt provides:\n\n- `id`\n- `planID`\n\nThis makes it easy to use in lists and diffable UI.\n\n### `ActivePlanWorkoutRecommendation`\n\nRepresents a due workout from an active plan.\n\nIncludes:\n\n- plan and enrollment\n- current day\n- schedule presentation\n- incomplete-plan detail\n- due date\n- completion counts\n- next non-rest day index\n\n### `PlanRestDayRecommendation`\n\nRepresents either:\n\n- a planned rest day\n- a completed-for-today state\n\nThe `kind` property distinguishes the two.\n\n### `FuturePlanWorkoutRecommendation`\n\nRepresents a workout that is scheduled for a future date.\n\n### `RecommendedWorkoutPlanRecommendation`\n\nRepresents a generic plan suggestion based on recent activity.\n\n### `SupplementalWorkoutRecommendation`\n\nRepresents a standalone workout suggestion that is meant to complement plan state.\n\n---\n\n## How the pieces fit together\n\n### Plan scheduling path\n\n1. `WorkoutPlanService.progressSnapshot(...)`\n2. `WorkoutPlanService.scheduleStatus(...)`\n3. `WorkoutPlanService.startState(for:recommendations:)`\n4. UI renders `WorkoutPlanStartState` or recommendation cards\n\n### Home recommendation path\n\n1. `UnifiedWorkoutRecommendationService.recommendations(...)`\n2. `WorkoutPlanService.progressSnapshots(...)`\n3. `activePlanRecommendations(...)`\n4. `UpcomingWorkoutRecommendationEngine.upcomingWorkoutRecommendation(...)`\n5. `recommendedPlanRecommendations(...)`\n6. final ranked `[WorkoutRecommendation]`\n\n### Reminder path\n\n1. `UpcomingWorkoutRecommendationEngine.upcomingWorkoutRecommendation(...)`\n2. `UpcomingWorkoutRecommendationFormatter.recommendation(...)`\n3. `UpcomingWorkoutRecommendationFormatter.content(from:)`\n4. notification or reminder UI uses `WorkoutReminderContent`\n\n---\n\n## Practical usage notes\n\n### Use `WorkoutPlanService` when you need authoritative plan state\n\nIf you need to know whether a plan day is due, skipped, completed, or in the future, use `progressSnapshot(...)` or `startState(for:)` rather than re-implementing schedule logic in a view.\n\n### Use `UnifiedWorkoutRecommendationService` for UI-facing recommendation lists\n\nThe home screen and workout start sheet should consume `recommendations(...)` or `primaryRecommendation(...)` instead of assembling cards manually.\n\n### Use `UpcomingWorkoutRecommendationEngine` for standalone workout reminders\n\nThis engine is intentionally separate from plan scheduling so reminder logic can evolve independently from plan progression.\n\n### Keep calendar and reference date injectable in tests\n\nMost scheduling methods accept `referenceDate` and `calendar`. Use those parameters in tests to avoid brittle date assumptions.\n\n---\n\n## External dependencies and integration points\n\nThis module depends on model types and utilities from elsewhere in the app, including:\n\n- `WorkoutPlan`\n- `WorkoutPlanDayDefinition`\n- `UserWorkoutPlan`\n- `WorkoutSession`\n- `Exercise`\n- `ExerciseLog`\n- `WorkoutTemplate`\n- `WorkoutReminderPreferences`\n- `WorkoutGoalPreferences`\n- `WorkoutHistoryRepository`\n- `ModelContext`\n\nIt is used by:\n\n- `HomeView`\n- `WorkoutStartSheet`\n- `WorkoutPlanDetailView`\n- `WorkoutPlansView`\n- reminder notification scheduling\n\n---\n\n## Testing considerations\n\nThe module has several behaviors that are especially important to cover in tests:\n\n- active enrollment sorting\n- schedule resolution across rest days\n- snapshot ordering\n- adherence threshold handling\n- plan advancement after completion or skip\n- recommendation ranking and filtering\n- template matching vs fallback selection\n- reminder content formatting\n\nExisting tests already exercise several of these flows, including:\n\n- `progressSnapshot`\n- `startPlan`\n- `startWorkoutSession`\n- `startSelectedWorkoutSession`\n- `reminderCandidates`\n- `upcomingWorkoutRecommendation`\n- `UpcomingWorkoutRecommendationFormatter.recommendation(...)`\n\n---\n\n## Summary\n\nThis module is the scheduling and recommendation backbone of the workout experience.\n\n- `WorkoutPlanService` determines what the user should do next and updates plan state.\n- `UpcomingWorkoutRecommendationEngine` selects a standalone workout when needed.\n- `UnifiedWorkoutRecommendationService` merges all signals into a single ranked recommendation list.\n- The recommendation models provide stable, UI-friendly representations of plan and workout state.\n\nIf you are changing plan progression, reminder selection, or recommendation ordering, this is the module to update first.","scripts":"# Scripts\n\n# Scripts Module\n\nThe `Scripts` module contains the data conversion utilities that build and normalize the workout seed files used by the application. It is centered around two standalone command-line scripts:\n\n- `Scripts/convert_workout_plans.py`\n- `Scripts/convert_mongo_seed_data.py`\n\nTogether, they transform scraped workout content and MongoDB exercise documents into the JSON seed artifacts stored under `BigRoosterWorkouts/Data/`.\n\n## Responsibilities\n\nThese scripts handle three related jobs:\n\n1. **Convert workout program source text into structured JSON**\n - Reads `workout_programs.txt`\n - Produces `workout_plans.json`\n\n2. **Convert MongoDB exercise documents into seed datasets**\n - Reads exercise and workout collections from MongoDB\n - Produces:\n - `gym_exercise_dataset.json`\n - `stretch_exercise_dataset.json`\n - `workout_plans.json`\n - Writes a validation summary to `mongo_seed_validation_summary.json`\n\n3. **Normalize and validate existing seed files**\n - Rewrites IDs into a consistent format\n - Ensures workout plan references resolve to valid exercise IDs\n - Rejects legacy exercise field names in final seed output\n\nBoth scripts are designed to be run directly from the command line and are intentionally self-contained.\n\n---\n\n## Data Flow Overview\n\n```mermaid\nflowchart LR\n A[workout_programs.txt] --> B[convert_workout_plans.py]\n C[MongoDB scrape data] --> D[convert_mongo_seed_data.py]\n B --> E[workout_plans.json]\n D --> F[gym_exercise_dataset.json]\n D --> G[stretch_exercise_dataset.json]\n D --> E\n D --> H[mongo_seed_validation_summary.json]\n```\n\n`convert_workout_plans.py` is the source-to-seed converter for the text-based workout program export.\n\n`convert_mongo_seed_data.py` is the more comprehensive pipeline: it reads MongoDB documents, builds exercise catalogs, matches workout references to catalog exercises, and emits both exercise and program seed files.\n\n---\n\n# `convert_workout_plans.py`\n\nThis script parses the legacy workout program text export and converts it into structured workout plan JSON.\n\n## Input and Output\n\n### Input\n- `BigRoosterWorkouts/Data/workout_programs.txt`\n\n### Output\n- `BigRoosterWorkouts/Data/workout_plans.json`\n\n## Execution Flow\n\n`main()` calls `parse_programs()`, which:\n\n1. Reads and trims the source file\n2. Skips the first 11 lines of header content\n3. Loads the exercise catalog from:\n - `gym_exercise_dataset.json`\n - `stretch_exercise_dataset.json`\n4. Processes the remaining lines in 11-line chunks\n5. Parses each chunk into a program record\n6. Matches each exercise name against the catalog\n7. Builds the final payload and validation summary\n8. Writes the JSON output file\n\nIf any invalid records are encountered, the script exits with a non-zero status.\n\n## Program Parsing Model\n\nEach program is represented by a fixed 11-line block:\n\n1. `source_url`\n2. `title`\n3. `main_goal`\n4. `workout_type`\n5. `training_level`\n6. `duration`\n7. `days_per_week_text`\n8. `time_per_workout_text`\n9. `equipment`\n10. `target_gender`\n11. `raw_days` as a Python literal\n\nThe script uses `ast.literal_eval()` to parse the day structure safely.\n\n## Exercise Matching\n\nExercise names in the source text are matched against the exercise catalog using a layered strategy:\n\n1. **Curated override**\n - `CURATED_MATCH_OVERRIDES` maps known source names to canonical catalog names\n\n2. **Candidate shortlist**\n - `shortlist_candidates()` narrows the search using token overlap\n\n3. **Scoring**\n - `score_candidate()` combines:\n - `difflib.SequenceMatcher` similarity\n - token overlap\n - token coverage\n - exact-name boosts\n - heuristic boosts for common exercise families\n\n4. **Match result**\n - `match_catalog_exercise()` returns:\n - `displayName`\n - `catalogExerciseName`\n - `catalogExerciseID`\n - `matchConfidence`\n - `matchingStrategy`\n\nLow-confidence matches are recorded in validation output for review.\n\n## Key Functions\n\n### `parse_programs()`\nParses the source text file into structured workout plans and validation metadata.\n\n### `load_exercise_catalog()`\nLoads exercise rows from the JSON datasets and builds a searchable catalog.\n\n### `match_catalog_exercise()`\nResolves a scraped exercise name to a catalog exercise using curated and fuzzy matching.\n\n### `split_source_exercise_name()`\nBreaks compound exercise labels into variants for matching.\n\n### `score_candidate()`\nComputes a weighted similarity score for a candidate exercise.\n\n### `summarize_program()`\nBuilds the human-readable summary string for a workout plan.\n\n## Validation Output\n\nThe script tracks:\n\n- `invalidRecords`\n- `duplicateIDs`\n- `duplicateTitles`\n- `nonRestDaysWithoutExercises`\n- `lowConfidenceMatches`\n\nThis validation is embedded in the output payload and also printed as a compact summary.\n\n---\n\n# `convert_mongo_seed_data.py`\n\nThis script is the main seed-generation pipeline for MongoDB-sourced workout data. It converts exercise documents and workout documents into normalized JSON seed files and validates the resulting contract.\n\n## Input and Output\n\n### Input\n- MongoDB database: `workout_scraping` by default\n- Collections:\n - `exercises`\n - `workouts`\n\n### Output\n- `BigRoosterWorkouts/Data/gym_exercise_dataset.json`\n- `BigRoosterWorkouts/Data/stretch_exercise_dataset.json`\n- `BigRoosterWorkouts/Data/workout_plans.json`\n- `BigRoosterWorkouts/Data/mongo_seed_validation_summary.json`\n\n## Command-Line Options\n\n`parse_args()` defines:\n\n- `--mongo-uri`\n- `--database`\n- `--exercise-collection`\n- `--workout-collection`\n- `--dry-run`\n- `--normalize-existing`\n- `--self-test`\n\n## Execution Modes\n\n### Default mode\n`main()` calls `convert(args)`.\n\nThis mode:\n1. Connects to MongoDB\n2. Reads exercise and workout documents\n3. Builds a canonical exercise catalog\n4. Parses workout tables into days and exercises\n5. Matches workout references to catalog exercises\n6. Writes seed files unless `--dry-run` is set\n7. Prints a validation summary\n\n### `--normalize-existing`\n`main()` calls `normalize_existing_seed_files()`.\n\nThis mode:\n1. Reads existing JSON seed files\n2. Normalizes exercise records\n3. Rewrites IDs where needed\n4. Resolves workout exercise references to canonical exercise IDs\n5. Validates the seed contract\n6. Writes normalized files back to disk\n\n### `--self-test`\n`main()` calls `run_self_tests()` and exits after printing `Self-tests passed`.\n\n---\n\n## Exercise Record Normalization\n\nThe script enforces a canonical exercise schema defined by `EXERCISE_FIELD_ORDER`:\n\n- `id`\n- `name`\n- `equipment`\n- `variation`\n- `utility`\n- `mechanics`\n- `force`\n- `preparation`\n- `execution`\n- `targetMuscles`\n- `synergistMuscles`\n- `stabilizerMuscles`\n- `antagonistMuscles`\n- `dynamicStabilizerMuscles`\n- `mainMuscle`\n- `difficulty`\n- `secondaryMuscles`\n- `parentId`\n\nLegacy Mongo field names are mapped through `LEGACY_EXERCISE_KEYS`, but legacy keys are rejected in final seed output by `validate_seed_contract()`.\n\n### `exercise_row_from_mongo()`\nConverts a MongoDB exercise document into the canonical row format.\n\nIt extracts:\n- title\n- profile metadata\n- overview text\n- instruction text\n- difficulty from experience level\n\n### `ordered_exercise_row()`\nEnsures every exercise row follows the canonical field order and fills defaults.\n\n### `normalize_exercise_records()`\nNormalizes a list of exercise rows and rewrites duplicate IDs using `unique_identifier()`.\n\n### `fallback_exercise_row()`\nCreates a synthetic exercise row when a workout references an exercise not present in the catalog.\n\nThis is used only when the source workout row points to an `/exercises/...` URL and no matching catalog entry exists.\n\n---\n\n## Workout Parsing\n\nWorkout documents are parsed by `parse_workout_days()`.\n\nEach workout document may contain multiple `workoutTables`. For each table:\n\n1. The script tracks the active column layout\n2. Header rows are detected with `is_header_row()`\n3. Exercise rows are parsed with `extract_prescription()`\n4. Non-exercise rows are filtered with `is_non_exercise_row()`\n5. Remaining rows are matched to catalog exercises with `match_exercise()`\n\n### `extract_prescription()`\nExtracts:\n- exercise name\n- sets\n- reps\n- notes\n\nIt supports both explicit row fields and column-based extraction from the active table layout.\n\n### `is_non_exercise_row()`\nFilters out labels and non-workout content such as:\n- week/day/set labels\n- nutrition labels\n- loading instructions\n- section headers\n- generic placeholders like `rest`, `notes`, `tempo`\n\n### `match_exercise()`\nResolves a workout exercise reference to a catalog exercise using this order:\n\n1. Exact URL path match\n2. Slug match\n3. Normalized title match\n4. Curated alias match via `CURATED_ALIAS_TARGETS`\n5. Generated fallback from `/exercises/...` URLs\n6. Fuzzy token-based matching\n\nIf a fallback exercise is generated, it is added to the catalog and indexed immediately so later rows can resolve against it.\n\n---\n\n## Catalog Construction and Matching\n\nThe matching pipeline is built around `CatalogExercise`, a dataclass that stores:\n\n- `name`\n- `identifier`\n- `source_url`\n- `canonical_url`\n- `slug`\n- `seed_row`\n- `aliases`\n- `token_bag`\n\n### `catalog_from_rows()`\nBuilds catalog entries from normalized exercise rows and source documents.\n\n### `build_indexes()`\nCreates lookup structures for:\n- URL path\n- slug\n- normalized name\n- token-based fuzzy matching\n\n### `find_fuzzy_match()`\nScores catalog candidates using token overlap and sequence similarity.\n\n### `score_candidate()`\nCombines:\n- `SequenceMatcher` ratio\n- token overlap\n- token coverage\n- exact-name boosts\n- subset boosts\n\nThis is the core of the fuzzy matching behavior.\n\n---\n\n## Validation and Contract Enforcement\n\n### `validate_seed_contract()`\nEnsures the generated payload is structurally safe before writing files.\n\nIt checks that:\n- exercise records do not contain legacy keys\n- IDs do not contain whitespace\n- workout exercise references resolve to known exercise IDs\n\nIf any of these checks fail, the script raises `ValueError`.\n\n### `summarize_validation()`\nProduces a compact summary from the full validation payload.\n\nThis is what gets printed to stdout and written to `mongo_seed_validation_summary.json`.\n\n---\n\n## Existing Seed Normalization\n\n### `normalize_existing_seed_files()`\nThis function is used when the repository already contains seed JSON files and they need to be cleaned up without re-reading MongoDB.\n\nIt:\n1. Loads existing gym and stretch datasets\n2. Loads the current workout plan payload\n3. Normalizes exercise rows with `normalize_exercise_records()`\n4. Rewrites program, day, and exercise IDs into canonical form\n5. Rebinds `catalogExerciseID` values to normalized exercise IDs\n6. Validates and writes the updated files\n\nThis mode is useful when the seed files have drifted from the expected contract.\n\n---\n\n## Self-Test Coverage\n\n### `run_self_tests()`\nPerforms a small set of assertions covering the most important parsing and validation paths:\n\n- header detection\n- prescription extraction\n- non-exercise row filtering\n- catalog indexing\n- legacy-key normalization\n- seed contract validation\n\nThis is not a full test suite, but it provides a fast sanity check for the conversion logic.\n\n---\n\n# Shared Utility Patterns\n\nBoth scripts rely on a similar set of normalization helpers.\n\n## Text normalization\n\n### `clean_text()`\nRemoves zero-width characters, collapses whitespace, and converts lists into comma-separated strings.\n\n### `normalize_name()`\nProduces a lowercase, punctuation-stripped comparison key for matching.\n\n### `display_exercise_name()`\nCleans display labels while preserving human-readable capitalization and punctuation better than `normalize_name()`.\n\n## Identifier generation\n\n### `slugify()`\nConverts text into a URL-like slug.\n\n### `normalized_identifier()`\nConverts text into a safe identifier using underscores and alphanumeric characters.\n\n### `unique_identifier()`\nEnsures IDs are unique within a scope by appending numeric suffixes when needed.\n\n## Parsing helpers\n\n### `parse_duration_weeks()`\nExtracts a week count from text like `12 weeks`.\n\n### `parse_days_per_week()`\nExtracts the first integer from a days-per-week field.\n\n### `parse_time_minutes()`\nExtracts minimum and maximum workout duration values from free-form text.\n\n### `equipment_list()`\nNormalizes equipment text into a list of tags.\n\n---\n\n# How the Two Scripts Relate\n\nThe scripts are separate entry points, but they are designed to work together.\n\n1. `convert_workout_plans.py` can generate `workout_plans.json` from the text export.\n2. `convert_mongo_seed_data.py` can generate the exercise datasets and also produce `workout_plans.json` from MongoDB workout documents.\n3. Both scripts use similar matching and normalization concepts:\n - canonical names\n - token bags\n - fuzzy matching\n - curated overrides\n - validation summaries\n\nIn practice, `convert_mongo_seed_data.py` is the more authoritative pipeline because it also produces the exercise catalog that `convert_workout_plans.py` depends on for matching.\n\n---\n\n# Important Constants and Mappings\n\n## `CURATED_MATCH_OVERRIDES`\nUsed by `convert_workout_plans.py` to map known source exercise names to canonical catalog names.\n\n## `CURATED_ALIAS_TARGETS`\nUsed by `convert_mongo_seed_data.py` to map common scraped exercise names to canonical exercise names.\n\n## `LEGACY_EXERCISE_KEYS`\nMaps canonical exercise fields to legacy Mongo field names.\n\n## `NON_EXERCISE_NAMES`\nDefines labels that should not be treated as workout exercises.\n\n## `ID_FIELDS`\nDefines which fields are considered identifiers for each record type.\n\n---\n\n# Contribution Notes\n\nWhen modifying these scripts, keep the following in mind:\n\n- Preserve the canonical exercise schema enforced by `EXERCISE_FIELD_ORDER`\n- Avoid introducing whitespace into IDs\n- Update curated mappings when new common aliases appear\n- Keep validation summaries stable enough for downstream tooling\n- If you add new seed fields, update both normalization and validation paths\n- If you change matching behavior, review both curated and fuzzy matching thresholds\n\nThe scripts are intentionally conservative: they prefer explicit mappings and validation failures over silently producing ambiguous seed data.","shared-utilities-ui-helpers":"# Shared Utilities & UI Helpers\n\n# Shared Utilities & UI Helpers\n\nThis module collects cross-cutting helpers used by the app’s SwiftUI views, seed-data import pipeline, persistence layer, and navigation setup. It is a mix of small UI conveniences, app preference keys, shared storage helpers, and import/search utilities that support the rest of the codebase without introducing feature-specific dependencies.\n\n## What lives here\n\nThe module includes:\n\n- **UI helpers**\n - `customToolBar(...)` and `CustomToolBarModifier`\n - `if(_:)` and `ifelse(_:)` view helpers\n - `AnyTransition.iris`\n - `UIScreen.displayCornerRadius`\n- **Persistence and app state helpers**\n - `SharedContainer`\n - `AppPreferenceKey`\n - `WorkoutGoalPreferences` and `WorkoutGoalPreferencesStore`\n - `BackgroundUndoManager`\n- **Data import and search utilities**\n - `SeedDataImporter`\n - `WorkoutPlanImporter`\n - `ExerciseSearchService`\n- **Navigation routing**\n - `Destination`, `Sheet`, and `AppRouter`\n\nThese types are intentionally lightweight and mostly stateless, with stateful behavior isolated to the undo manager and preference store.\n\n---\n\n## UI helpers\n\n### `customToolBar(...)`\n\n`customToolBar(...)` is a `View` extension that applies a reusable toolbar layout via `CustomToolBarModifier`.\n\nIt is designed for screens that need:\n\n- a leading toolbar item\n- a principal title/subtitle block\n- a trailing item\n- an optional primary action button\n\nThe API is generic over all toolbar content, so callers can provide arbitrary SwiftUI views for each slot.\n\n#### Key parameters\n\n- `isToolbarVisible`: gates the entire toolbar\n- `isPrimaryActionVisible`: controls whether the primary action item is inserted\n- `isTitleVisible` / `isSubTitleVisible`: independently animate title and subtitle visibility\n- `titleAlignment`: controls whether the principal content is aligned leading or centered\n\n#### Behavior\n\nThe modifier:\n\n- installs toolbar items in `.topBarLeading` and `.topBarTrailing`\n- renders title/subtitle inside `.principal`\n- hides the default back button\n- forces `.inline` navigation bar title display mode\n- animates primary action visibility with a bouncy transition\n- animates title/subtitle changes with `.blurReplace` and offset transitions\n\nThe title block uses a large invisible `Text` placeholder to reserve layout space while the actual content is overlaid.\n\n### `CustomToolBarTitleAlignment`\n\nA small enum used by `customToolBar(...)` to choose between:\n\n- `.leading`\n- `.centered`\n\nThis affects the alignment of the title stack, overlay placement, and frame alignment.\n\n### `AnyTransition.iris`\n\nDefined in `TextTransition.swift`, `AnyTransition.iris` is a custom transition built from:\n\n- `ClipShapeModifier`\n- `ScaledCircle`\n\nIt clips content to a circle that scales from invisible to full-screen coverage, producing an iris-style reveal/conceal effect.\n\n### `ScaledCircle`\n\n`ScaledCircle` is a `Shape` whose `animatableData` controls the diameter of the circle relative to the drawing rectangle’s diagonal.\n\nIt is used only by `AnyTransition.iris`, but it is implemented as a standalone shape so it can be reused in other transitions or clipping effects.\n\n### `ClipShapeModifier`\n\nA generic `ViewModifier` that applies `clipShape(_:)` to any view.\n\nIt exists to support `AnyTransition.iris`, but it is also a useful pattern for building other shape-based transitions.\n\n### `if(_:)` and `ifelse(_:)`\n\nDefined in `SwiftUIConditionalHelpers.swift`, these `View` extensions provide ergonomic conditional view composition.\n\n#### `if(_:_:)`\n\nApplies a transform only when the condition is true; otherwise returns the original view.\n\n#### `ifelse(_:_:)`\n\nChooses between two transforms based on a boolean condition.\n\nThese helpers are especially useful when a view modifier or wrapper should only be applied in one branch without duplicating the base view.\n\n### `isIOS26OrLater()`\n\nA small availability helper that mirrors `#available(iOS 26.0, *)`.\n\nIt is primarily used in tests and conditional UI logic where a runtime boolean is easier to work with than a compile-time availability check.\n\n### `UIScreen.displayCornerRadius`\n\nAn extension on `UIScreen` that reads the display corner radius using a private key-value lookup.\n\n#### Notes\n\n- This is a best-effort helper and may return `0` if the private API changes.\n- It calls `assertionFailure(...)` when the lookup fails.\n- Because it relies on a private property name, it should be treated as fragile and platform-sensitive.\n\n---\n\n## Shared persistence and app preferences\n\n### `AppPreferenceKey`\n\n`AppPreferenceKey` centralizes the string keys used with `UserDefaults`.\n\nCurrent keys include:\n\n- `defaultRestTime`\n- `notificationsEnabled`\n- `soundEnabled`\n- `hapticEnabled`\n- `workoutReminderEnabled`\n- `workoutReminderHour`\n- `workoutReminderMinute`\n- `dailyCalorieGoalEnabled`\n- `dailyCalorieGoal`\n- `homeStatsWidgetOrder`\n- `homeBackgroundConfiguration`\n- `appAppearanceMode`\n- `legacyUseSystemTheme`\n\nUsing a single namespace for keys reduces typo risk and makes migrations easier to audit.\n\n### `WorkoutGoalPreferences`\n\nA small value type that models the calorie-goal portion of workout preferences.\n\n#### Properties\n\n- `dailyCalorieGoalEnabled: Bool`\n- `dailyCalorieGoal: Int`\n\n#### Initialization and sanitization\n\nThe initializer clamps `dailyCalorieGoal` into the range `100...2_000` via `sanitizedDailyCalorieGoal(_:)`.\n\nThis means callers can pass arbitrary values, but the stored model always stays within the supported range.\n\n### `WorkoutGoalPreferencesStore`\n\nA `UserDefaults`-backed store for `WorkoutGoalPreferences`.\n\n#### Reading\n\nThe `preferences` getter:\n\n- reads `dailyCalorieGoalEnabled` as a `Bool`\n- reads `dailyCalorieGoal` via `storedInteger(forKey:defaultValue:)`\n- falls back to `WorkoutGoalPreferences.defaultDailyCalorieGoal` when the key is absent\n\n#### Writing\n\nAssigning to `preferences` persists both fields back to `UserDefaults`.\n\n#### Why `storedInteger(forKey:defaultValue:)` exists\n\n`UserDefaults.integer(forKey:)` returns `0` when a key is missing, which makes it impossible to distinguish “unset” from “explicitly set to zero.” \nThis helper checks for key existence first and only then reads the integer value.\n\n### `SharedContainer`\n\n`SharedContainer` wraps access to the app group container identified by:\n\n```swift\n\"group.com.vishwakarma.BigRoosterWorkouts\"\n```\n\nIt is used for data that must be shared across app targets or extensions.\n\n#### API\n\n- `containerURL`: resolves the app group container directory\n- `fileURL(for:)`: appends a filename to the container URL\n- `writeData(_:to:)`: encodes an `Encodable` value as JSON and writes it atomically\n- `readData(from:as:)`: reads JSON from disk and decodes it into a `Decodable` type\n\n#### Error handling\n\n`writeData` and `readData` throw `SharedContainerError.invalidURL` if the app group container cannot be resolved.\n\n`SharedContainerError.fileNotFound` exists as a general error case, but this module currently does not throw it directly.\n\n#### Usage pattern\n\nThis helper is used by code such as widget refresh logic to persist shared state like background configuration.\n\n---\n\n## Undo support for background editing\n\n### `BackgroundUndoManager`\n\n`BackgroundUndoManager` is an observable history stack for `BackgroundConfiguration` values.\n\nIt is used by the background builder flow to support undo/redo while editing a background design.\n\n#### Internal state\n\n- `history: [BackgroundConfiguration]`\n- `currentIndex: Int`\n- `maxHistory = 20`\n\n#### Public API\n\n- `canUndo`\n- `canRedo`\n- `pushState(_:)`\n- `undo()`\n- `redo()`\n- `reset(with:)`\n\n#### Behavior\n\n`pushState(_:)`:\n\n- truncates any redo states if the user makes a new change after undoing\n- appends the new configuration\n- enforces the maximum history size\n\n`undo()` and `redo()`:\n\n- move `currentIndex` backward or forward\n- return the configuration at the new position\n- return `nil` when the operation is not possible\n\n`reset(with:)`:\n\n- replaces the entire history with a single configuration\n- resets the index to `0`\n\n#### Important implementation detail\n\nWhen history exceeds `maxHistory`, the oldest entry is removed. The current implementation does not increment `currentIndex` in that branch, so contributors should be careful when modifying this logic. The intended behavior is a bounded history stack that preserves the current position after trimming.\n\n---\n\n## Seed data import pipeline\n\nThe import utilities are responsible for populating SwiftData with bundled exercise and workout-plan seed data. They are designed to be idempotent and versioned so the app can reseed when the bundled data format changes.\n\n### `SeedDataImporter`\n\n`SeedDataImporter` imports exercise data from bundled JSON files and seeds both `Exercise` and `ExerciseSearchIndex` records.\n\n#### Entry points\n\n- `importData(into:)`\n- `importExercises(into:)`\n\n`importData(into:)` performs the full seed pass:\n\n1. imports exercises\n2. imports workout plans via `WorkoutPlanImporter.importIfNeeded(into:)`\n\n#### Versioning strategy\n\nExercise import is guarded by:\n\n- `exerciseImportVersion`\n- `exerciseImportVersionKey` in `UserDefaults`\n- current `Exercise` row count\n\nA reseed occurs when:\n\n- the stored version is older than the bundled version, or\n- there are no existing exercises\n\nThis allows the app to refresh seed data when the bundled dataset changes.\n\n#### Exercise import flow\n\n`importExercises(into:)`:\n\n1. checks whether reseeding is needed\n2. loads gym and stretch exercise records\n3. validates that at least one record was parsed\n4. replaces all existing `Exercise` and `ExerciseSearchIndex` rows\n5. saves the context\n6. stores the current import version in `UserDefaults`\n\n#### Record loading helpers\n\n- `loadGymExerciseRecords()`\n- `loadStretchExerciseRecords()`\n\nBoth helpers:\n\n- read bundled JSON arrays\n- sanitize fields\n- construct `Exercise` and `ExerciseSearchIndex` pairs\n- return arrays of internal `ExerciseSeedRecord`\n\n##### Gym records\n\nGym exercises are built from a richer schema and include:\n\n- equipment\n- variation\n- utility\n- mechanics\n- force\n- preparation\n- execution\n- target and supporting muscle groups\n- difficulty\n- parent ID\n- inferred logging mode\n\n##### Stretch records\n\nStretch exercises use a smaller schema and are always seeded with:\n\n- `isStretch: true`\n- `loggingMode: .durationAndReps`\n\n#### Supporting helpers\n\n- `loadJSONRows(resource:)`: loads and parses a bundled JSON file into row dictionaries\n- `jsonField(_:in:)`: extracts and sanitizes a string field\n- `sanitizedField(_:)`: trims whitespace, removes BOM/zero-width characters, and normalizes null-like values to `\"\"`\n- `parsedDifficulty(_:)`: converts a raw value into a bounded difficulty score from `1...5`\n- `inferredLoggingMode(...)`: infers the appropriate `ExerciseLoggingMode` from exercise metadata\n\n#### Logging mode inference\n\n`inferredLoggingMode(...)` uses keyword matching to classify exercises into:\n\n- `.durationOnly`\n- `.durationAndReps`\n- `.repsOnly`\n- `.weightAndReps`\n\nThis is a heuristic, not a strict taxonomy, so changes to the bundled dataset may require updates to the keyword lists.\n\n#### Error handling\n\nThe importer throws `CSVError` for:\n\n- missing bundled files\n- invalid JSON structure\n- empty parsed datasets\n\nDespite the name, the importer currently reads JSON rather than CSV.\n\n### `WorkoutPlanImporter`\n\n`WorkoutPlanImporter` seeds `WorkoutPlan` records from the bundled `workout_plans.json` payload.\n\n#### Entry point\n\n- `importIfNeeded(into:)`\n\n#### Versioning strategy\n\nLike the exercise importer, this importer uses:\n\n- a version key in `UserDefaults`\n- the current `WorkoutPlan` count\n\nA reseed occurs when the bundled version is newer or the database is empty.\n\n#### Import flow\n\n1. load the payload with `loadPayload()`\n2. validate that there are no invalid records\n3. delete existing `WorkoutPlan` rows\n4. insert each `WorkoutPlan` from the payload\n5. save the context\n6. persist the import version\n\n#### Payload structure\n\n`SeedPayload` contains:\n\n- `generatedAt`\n- `programCount`\n- `programs`\n- `validation`\n\nThe nested validation and metadata types (`SeedValidation`, `SeedProgram`, and related record types) are `Codable` models that mirror the JSON schema and preserve diagnostic information about the seed source.\n\n#### `loadPayload()`\n\nReads `workout_plans.json` from the main bundle and decodes it into `SeedPayload`.\n\nIf the file is missing, it throws `CSVError.fileNotFound(\"workout_plans.json\")`.\n\n### Import orchestration\n\nThe app’s startup path calls `importExercisesIfNeeded`, which flows into `SeedDataImporter.importData(into:)`. From there, the import pipeline fans out into exercise seeding and workout-plan seeding.\n\n```mermaid\nflowchart TD\n A[App startup] --> B[importExercisesIfNeeded]\n B --> C[SeedDataImporter.importData(into:)]\n C --> D[importExercises(into:)]\n C --> E[WorkoutPlanImporter.importIfNeeded(into:)]\n D --> F[loadGymExerciseRecords / loadStretchExerciseRecords]\n E --> G[loadPayload()]\n```\n\n---\n\n## Exercise search\n\n### `ExerciseSearchService`\n\n`ExerciseSearchService.search(query:filters:context:)` performs in-memory filtering and ranking over `ExerciseSearchIndex` rows, then resolves the matching `Exercise` objects.\n\n#### Why it uses search indices\n\nThe service queries `ExerciseSearchIndex` first because it contains denormalized searchable fields and filter dimensions. This avoids scanning full `Exercise` objects for every search interaction.\n\n#### Search flow\n\n1. fetch all `ExerciseSearchIndex` rows\n2. filter by query text if present\n3. apply each active filter set\n4. limit to 100 results\n5. rank results when a query exists\n6. fetch all `Exercise` rows\n7. map ranked names back to actual `Exercise` objects\n\n#### Ranking rules\n\nWhen `query` is non-empty, results are ordered by:\n\n1. exact name match\n2. name contains query\n3. alphabetical fallback\n\n#### Filter dimensions\n\nThe service respects the following `FilterState` collections:\n\n- `selectedMuscles`\n- `selectedEquipment`\n- `selectedMechanics`\n- `selectedUtility`\n- `selectedForce`\n- `selectedDifficulty`\n\nEach filter is applied only when its corresponding selection set is non-empty.\n\n#### Notes for contributors\n\n- The search is marked `@MainActor`, so it is intended to run on the main thread.\n- The final mapping uses exercise names to recover `Exercise` instances, so name uniqueness matters for stable results.\n- The service currently fetches all indices and all exercises into memory; this is simple and predictable, but not optimized for very large datasets.\n\n---\n\n## Navigation routing\n\n### `Destination`\n\n`Destination` is the app’s route enum for `AppRouter`.\n\nIt conforms to:\n\n- `DestinationType`\n- `Hashable`\n\n#### Cases\n\nThe enum covers the app’s major navigation targets, including:\n\n- profile and settings\n- workout sessions and history\n- workout plans and plan details\n- exercise browsing and exercise details\n- theme and background editors\n- credits and design-system showcase\n\n#### `from(path:fullPath:parameters:)`\n\nThis static factory maps URL path segments to destinations for deep linking or route parsing.\n\nExamples:\n\n- `\"profile\"` → `.profile`\n- `\"settings\"` → `.settings`\n- `\"history\"` → `.workoutHistory(selectedDate: nil)`\n- `\"workout-plans\"` → `.workoutPlans`\n\nUnrecognized paths return `nil`.\n\n#### Equality and hashing\n\n`Destination` implements custom `==` and `hash(into:)` so that associated values are compared by stable identifiers rather than full object identity.\n\nExamples:\n\n- `WorkoutSession` routes compare by `session.id`\n- `WorkoutPlan` routes compare by `plan.planID`\n- `Exercise` routes compare by `exercise.id`\n- `workoutHistory` compares by selected date\n\nThis keeps routing behavior stable when the underlying model instances are recreated.\n\n### `Sheet`\n\n`Sheet` is the app’s modal route enum for `AppRouter`.\n\nIt conforms to `SheetType` and includes cases such as:\n\n- `editProfile`\n- `workoutStart`\n- `exercisePicker`\n- `templateEditor`\n- `exerciseBuilder`\n- `workoutPlanBuilder`\n- `themePicker`\n- `share(URL)`\n\nIts `id` is derived from `hashValue`, which is sufficient for sheet presentation identity in this app’s routing model.\n\n### `AppRouter`\n\n`AppRouter` is a type alias:\n\n```swift\ntypealias AppRouter = SimpleRouter<Destination, Sheet>\n```\n\nThis binds the app’s route and sheet enums to the shared router implementation from `AppRouter`.\n\n---\n\n## How these pieces connect to the rest of the app\n\nThis module is used broadly across the app:\n\n- **Startup/import path**\n - `BigRoosterWorkoutsApp` calls `importExercisesIfNeeded`, which triggers `SeedDataImporter.importData(into:)`\n - `SeedDataImporter` then seeds exercises and workout plans into SwiftData\n- **Workout recommendation and preferences**\n - `WorkoutGoalPreferences` is read by recommendation logic and workout-start flows\n - `WorkoutGoalPreferencesStore` persists those values in `UserDefaults`\n- **Background editing**\n - `BackgroundUndoManager` is used by `BackgroundBuilderView` for undo/redo\n - `SharedContainer` persists shared background configuration for widgets or other targets\n- **Search and browsing**\n - `ExerciseSearchService` powers exercise search and filtering screens\n- **Navigation**\n - `Destination`, `Sheet`, and `AppRouter` define the app’s route model\n- **UI composition**\n - `customToolBar(...)`, `if(_:)`, `ifelse(_:)`, and `AnyTransition.iris` simplify SwiftUI view code throughout the app\n\n---\n\n## Implementation notes and contributor guidance\n\n### Prefer small, composable helpers\n\nMost types here are intentionally narrow in scope. When adding new functionality, prefer extending an existing helper only if the behavior is truly shared.\n\n### Keep seed importers idempotent\n\nBoth importers rely on version checks and destructive replacement of seed data. If you change the bundled datasets or model schema, update the version constants and validate the reseed path.\n\n### Be careful with `UserDefaults` keys\n\nUse `AppPreferenceKey` rather than hard-coded strings. This is especially important for migration-sensitive values like `legacyUseSystemTheme`.\n\n### Treat private UIKit APIs as fragile\n\n`UIScreen.displayCornerRadius` depends on a private key path and should be used defensively.\n\n### Preserve route identity semantics\n\nIf you add new `Destination` cases with associated values, update both `==` and `hash(into:)` so routing remains stable.\n\n### Watch for name-based exercise lookup\n\n`ExerciseSearchService` maps search results back to `Exercise` by name. If duplicate names become possible, this lookup strategy will need to change.\n\n---\n\n## Error types\n\n### `CSVError`\n\nA `LocalizedError` used by the importers.\n\nCases:\n\n- `fileNotFound(String)`\n- `invalidData(String)`\n\nDespite the name, it is used for JSON-backed seed files as well.\n\n### `SharedContainerError`\n\nUsed by `SharedContainer`.\n\nCases:\n\n- `invalidURL`\n- `fileNotFound`\n\nOnly `invalidURL` is currently thrown by the helper methods in this module.","theme-appearance":"# Theme & Appearance\n\n# Theme & Appearance\n\nThis module provides the app’s theming system: appearance mode selection, theme persistence, theme editing, and the shared design tokens used throughout the UI.\n\nIt is centered around four responsibilities:\n\n- resolving whether the app should render in light or dark mode\n- storing and loading the selected theme and custom themes\n- exposing theme-aware colors and reusable view styles\n- providing UI for browsing, creating, editing, and applying themes\n\n## Main building blocks\n\n- `AppAppearanceMode` — user-facing appearance preference: system, light, or dark\n- `ThemeManager` — observable source of truth for the active theme and appearance mode\n- `ThemeStorage` — file-backed persistence and import/export\n- `ThemeEnvironment` — SwiftUI environment integration for theme access and system color-scheme sync\n- `DesignSystem` — shared spacing, typography, colors, corner radii, and view modifiers\n- `ThemeListView` — theme browser and selector\n- `ThemeEditorView` — custom theme editor\n- `DesignSystemShowcaseView` — visual reference for the design system\n\n---\n\n## Appearance mode\n\n### `AppAppearanceMode`\n\n`AppAppearanceMode` is the persisted user preference for how the app should resolve appearance:\n\n- `.system`\n- `.light`\n- `.dark`\n\nIt conforms to `String`, `CaseIterable`, `Codable`, `Identifiable`, and `Hashable`, which makes it suitable for:\n\n- persistence in `UserDefaults`\n- use in pickers and menus\n- serialization if needed elsewhere\n\n#### Key properties\n\n- `title`: display label used in UI\n- `systemImage`: SF Symbol name for the mode\n- `preferredColorScheme`: maps `.light` and `.dark` to SwiftUI `ColorScheme`, returns `nil` for `.system`\n- `resolvesDarkMode(systemIsDarkMode:)`: converts the appearance mode plus the current system scheme into a boolean dark-mode decision\n\n#### Resolution behavior\n\n- `.system` follows the current system color scheme\n- `.light` always resolves to light mode\n- `.dark` always resolves to dark mode\n\nThis logic is used by `ThemeManager.resolveDarkMode()`.\n\n---\n\n## Theme manager\n\n### `ThemeManager`\n\n`ThemeManager` is the central runtime object for theme state. It is marked `@Observable` and `@MainActor`, so SwiftUI views can observe it directly and all UI-facing mutations happen on the main thread.\n\nIt owns:\n\n- `currentTheme: AppTheme`\n- `customThemes: [AppTheme]`\n- `appearanceMode: AppAppearanceMode`\n- `isDarkMode: Bool`\n- `predefinedThemes: [AppTheme]`\n\nIt also coordinates persistence through `ThemeStorage.shared`.\n\n### Initialization flow\n\n`ThemeManager.init(...)` performs the following work:\n\n1. loads the saved appearance mode from `UserDefaults`\n2. restores the selected theme ID from either:\n - `selected_theme_id`\n - legacy `selectedThemeId`\n3. resolves `currentTheme` from predefined themes, falling back to `AppTheme.defaultLight`\n4. optionally loads custom themes asynchronously\n5. resolves dark mode from the appearance mode and system state\n6. persists the appearance mode if none was stored yet\n\nThe initializer calls `persistAppearanceMode()` when no appearance preference exists yet, which ensures the app writes a canonical preference state on first launch.\n\n### Appearance mode updates\n\n`appearanceMode` has a `didSet` observer:\n\n- no-op if the value did not change\n- persists the new mode\n- recalculates `isDarkMode`\n\nThis means changing the appearance mode immediately updates both storage and runtime rendering state.\n\n### Dark mode resolution\n\n`resolveDarkMode()` is the internal bridge between appearance preference and actual rendering state:\n\n```swift\nisDarkMode = appearanceMode.resolvesDarkMode(systemIsDarkMode: systemIsDarkMode)\n```\n\nThis is the core decision point used by `currentColors`.\n\n### System color-scheme synchronization\n\n`updateSystemColorScheme(isDarkMode:)` updates the cached system scheme and re-resolves dark mode only when appearance mode is `.system`.\n\nThis is important because the app can follow the system scheme without forcing a manual appearance choice.\n\n### Theme switching\n\n`switchTheme(_:)` updates `currentTheme` with animation and persists the selected theme ID through `ThemeStorage`.\n\nUse this when the user applies a theme from the theme browser.\n\n### Theme creation and editing\n\n`ThemeManager` supports several theme lifecycle operations:\n\n- `createNewTheme(inheritFrom:)`\n- `updateTheme(_:)`\n- `deleteCustomTheme(_:)`\n- `duplicateTheme(_:)`\n- `updateThemeBackground(_:for:)`\n\nThese methods all update in-memory state and then persist custom themes asynchronously.\n\n#### Creation patterns\n\n`createNewTheme(inheritFrom:)` supports two modes:\n\n- inherit from an existing theme, copying its colors and background configuration\n- create a new custom theme from the current theme\n\n#### Deletion behavior\n\n`deleteCustomTheme(_:)` also handles inheritance cleanup:\n\n- if the deleted theme is currently active, it switches back to `AppTheme.defaultLight`\n- child themes that referenced the deleted theme have their `parentThemeId` cleared\n\n### Import/export\n\n`ThemeManager` delegates serialization to `ThemeStorage`:\n\n- `exportTheme(_:)`\n- `importTheme(from:)`\n- `exportBackgroundAsImage(_:)`\n\nThese are async APIs and should be awaited from UI actions or tasks.\n\n### Accessibility checks\n\n`checkAccessibility(for:)` evaluates contrast for:\n\n- light mode primary text\n- light mode secondary text\n- dark mode primary text\n- dark mode secondary text\n\nIt returns `[AccessibilityIssue]`, where each issue includes:\n\n- affected element\n- color mode\n- measured contrast ratio\n- required ratio\n- severity level\n- human-readable message\n\nThe contrast pipeline is:\n\n```mermaid\nflowchart TD\n A[checkAccessibility(for:)] --> B[checkColorContrast]\n B --> C[calculateContrastRatio]\n C --> D[hexToLuminance]\n```\n\n`checkColorContrast(...)` classifies results as:\n\n- `.fail` if contrast is below 3.0\n- `.warning` if contrast is between 3.0 and 4.5\n- no issue if contrast is 4.5 or higher\n\n### Contrast calculation\n\n`calculateContrastRatio(text:background:)` converts hex strings to luminance using `hexToLuminance(_:)`, then applies the WCAG contrast formula.\n\nThis implementation expects hex color strings in the theme model.\n\n### Inheritance chain\n\n`inheritanceChain(for:)` walks `parentThemeId` links upward through:\n\n1. custom themes\n2. predefined themes\n\nThis is useful for displaying theme lineage or debugging inherited variants.\n\n### Current colors\n\n`currentColors` is the main convenience accessor used by the UI:\n\n- returns `currentTheme.darkColors` when `isDarkMode` is true\n- otherwise returns `currentTheme.lightColors`\n\nThis property is what `DesignSystem.Colors` reads to stay theme-aware.\n\n---\n\n## Persistence and storage\n\n### `ThemeStorage`\n\n`ThemeStorage` is an `actor`, so file and persistence operations are serialized safely.\n\nIt stores data in the app’s Documents directory:\n\n- `Themes/custom_themes.json`\n- `Backgrounds/`\n\n### Stored data\n\n- custom themes are encoded as `[AppTheme]`\n- selected theme ID is stored in `UserDefaults`\n- theme exports are written as JSON files\n- background exports are written as PNG files\n\n### Directory setup\n\n`ensureDirectoriesExist()` creates both storage directories before writes.\n\n### Theme persistence\n\n- `saveThemes(_:)` encodes custom themes with `JSONEncoder`\n- `loadThemes()` decodes them from disk if the file exists\n\n### Selected theme persistence\n\n- `saveSelectedTheme(id:)` writes both current and legacy keys\n- `loadSelectedThemeId()` reads either key and converts it back to `UUID`\n\n### Import/export\n\n- `exportTheme(_:)` writes a `ThemeExport` JSON file into the themes directory\n- `importTheme(from:)` decodes a `ThemeExport` and returns the embedded `AppTheme`\n\n### Background export\n\n`exportBackgroundAsImage(_:size:)` renders a `BackgroundConfiguration` using `BackgroundRenderer`, then writes the resulting PNG to disk.\n\nIf rendering or encoding fails, it throws `ThemeStorageError`.\n\n### Errors\n\n`ThemeStorageError` includes:\n\n- `invalidImage`\n- `encodingFailed`\n- `decodingFailed`\n- `fileNotFound`\n- `permissionDenied`\n\nOnly some of these are currently thrown in this module; the enum leaves room for broader storage handling.\n\n---\n\n## SwiftUI environment integration\n\n### `ThemeEnvironment`\n\nThis file connects `ThemeManager` to SwiftUI’s environment system.\n\n#### `EnvironmentValues.themeManager`\n\nA custom environment key exposes `ThemeManager` to views.\n\n#### `withThemeManager()`\n\nInjects `ThemeManager.shared` into the environment.\n\nUse this at the app root or in previews when a view hierarchy needs theme access.\n\n#### `syncThemeWithColorScheme()`\n\nAttaches `ThemeColorSchemeModifier`, which keeps `ThemeManager` informed of the current system color scheme.\n\nThis is especially important when appearance mode is `.system`.\n\nThe modifier:\n\n- updates the manager on `onAppear`\n- updates again whenever `colorScheme` changes\n\nThe flow is:\n\n```mermaid\nflowchart LR\n A[View body] --> B[syncThemeWithColorScheme()]\n B --> C[ThemeColorSchemeModifier]\n C --> D[updateSystemColorScheme]\n D --> E[resolveDarkMode]\n E --> F[AppAppearanceMode.resolvesDarkMode]\n```\n\n#### `themeColors(for:)`\n\nConvenience accessor that returns `themeManager.currentColors`.\n\nThis is a small helper for views that already have a `ThemeManager` reference.\n\n---\n\n## Design system\n\n### `DesignSystem`\n\n`DesignSystem` is a namespace-style struct that groups reusable visual tokens and view modifiers.\n\nIt is `@MainActor` because many of its properties depend on `ThemeManager.shared.currentColors`, which is UI state.\n\n### Color tokens\n\n`DesignSystem.Colors` is theme-aware and reads from `ThemeManager.shared.currentColors`.\n\nIt exposes semantic colors such as:\n\n- backgrounds: `backgroundPrimary`, `backgroundSecondary`, `backgroundTertiary`\n- surfaces: `surfacePrimary`, `surfaceSecondary`, `surfaceTertiary`\n- text: `textPrimary`, `textSecondary`, `textMuted`, `textOnAccent`\n- accents: `accent`, `accentSecondary`\n- semantic states: `success`, `warning`, `error`, `info`\n- borders and depth: `border`, `divider`, `shadow`\n\nIt also includes utility colors:\n\n- `clear`, `white`, `black`\n- `scrim`\n- inverse text colors\n- glass overlay colors\n\n#### Legacy fallbacks\n\n`accentFallback` and `yellowFallback` exist for gradients or special cases where a theme-aware token is not available.\n\n### Typography tokens\n\n`DesignSystem.Typography` defines rounded system fonts for common hierarchy levels:\n\n- `hero`\n- `heroTitle`\n- `display`\n- `title`\n- `largeTitle`\n- `headline`\n- `headline2`\n- `subheadline`\n- body and caption variants\n- numeric styles\n\n### Spacing tokens\n\n`DesignSystem.Spacing` provides a consistent spacing scale from `xxs` through `hero`.\n\n### Corner radius tokens\n\n`DesignSystem.CornerRadius` defines reusable radii for:\n\n- small controls\n- cards\n- pills\n- display surfaces\n\n`display` is computed dynamically:\n\n- on iOS 26+, it returns a fallback value\n- otherwise it uses `UIScreen.main.displayCornerRadius`\n\n### Native style grammar\n\n`DesignSystem.NativeStyle` groups tokens intended to make native SwiftUI controls feel cohesive without replacing them with custom web-like components.\n\nIt includes:\n\n- spacing\n- sizing\n- radius\n- typography\n- depth\n\nThis is used in `DesignSystemShowcaseView` and can be reused in feature views.\n\n---\n\n## View modifiers\n\nThe module defines a set of reusable `ViewModifier`s and matching `View` extension helpers.\n\n### Button styles\n\n- `primaryButtonStyle()`\n- `secondaryButtonStyle()`\n- `tertiaryButtonStyle()`\n- `destructiveButtonStyle()`\n\nThese are implemented by:\n\n- `PrimaryButtonStyle`\n- `SecondaryButtonStyle`\n- `TertiaryButtonStyle`\n- `DestructiveButtonStyle`\n\n#### `PrimaryButtonStyle`\n\nUsed for primary actions. It applies:\n\n- subheadline font\n- inverse text color\n- full-width layout\n- fixed height\n- horizontal padding\n- accent gradient background\n- rounded clipping\n- shadow\n\n#### `SecondaryButtonStyle`\n\nOutlined accent button with transparent background.\n\n#### `TertiaryButtonStyle`\n\nFilled surface button with neutral text.\n\n#### `DestructiveButtonStyle`\n\nSolid error-colored button with inverse text.\n\n### Card styles\n\n- `cardStyle()`\n- `flatCardStyle()`\n\nBacked by:\n\n- `CardStyle`\n- `FlatCardStyle`\n\n`CardStyle` adds a surface background, rounded clipping, and a subtle border overlay. \n`FlatCardStyle` keeps the same surface and border treatment without the extra overlay style differences used by `CardStyle`.\n\n### List styles\n\n- `themedListBackground()`\n- `themedListRow()`\n\nThese are used across list-heavy screens such as settings, workout history, and template builders.\n\n- `ThemedListBackgroundStyle` hides the default scroll background and applies the themed background color\n- `ThemedListRowStyle` sets row background and separator tint\n\n### Other styles\n\n- `keypadButtonStyle()` → `KeypadButtonStyle`\n- `timelinePillStyle(isActive:)` → `TimelinePillStyle`\n- `toggleButtonStyle()` → `ToggleButtonStyle`\n\n`TimelinePillStyle` switches between black and white backgrounds based on `isActive`.\n\n`ToggleButtonStyle` creates a circular 56×56 control with a dark background and shadow.\n\n### Animation helpers\n\n`Animation.smooth` and `Animation.snappy` provide reusable spring animations for UI transitions.\n\n---\n\n## Theme editor\n\n### `ThemeEditorView`\n\n`ThemeEditorView` is the custom theme editing screen.\n\nIt receives an `AppTheme` in its initializer and creates local editable state for:\n\n- the theme itself\n- light color set\n- dark color set\n- theme name\n\nThis lets the user edit without mutating the manager until Save is tapped.\n\n### Save flow\n\n`saveTheme()`:\n\n1. copies the edited name and color sets back into `theme`\n2. calls `themeManager.updateTheme(theme)`\n3. dismisses the view\n\nThis is the only point where edits are committed.\n\n### Layout\n\nThe editor is split into three vertical regions:\n\n- header with name field and Save button\n- side-by-side color editors for light and dark modes\n- preview area with `MiniAppPreview`\n\n### `ColorEditorPanel`\n\nA reusable panel for editing one `ThemeColorSet`.\n\nIt groups colors into sections:\n\n- Backgrounds\n- Surfaces\n- Text\n- Accents\n- Semantic\n- Borders\n\nEach row uses `ColorRow`, which binds a hex string to a `ColorPicker`.\n\n### `ColorSection`\n\nA simple section wrapper used by `ColorEditorPanel`.\n\n### `ColorRow`\n\nConverts between:\n\n- stored hex string\n- SwiftUI `Color`\n\nIt uses:\n\n- `Color(hex:)` for reading\n- `toHex()` for writing\n\n### Hex conversion\n\n`Color.toHex()` is defined in this file and is used by the editor and background builder code paths.\n\nBe aware that it relies on `UIColor(self).cgColor.components`, so color-space assumptions can affect the output for some colors.\n\n---\n\n## Theme browser and application flow\n\n### `ThemeListView`\n\n`ThemeListView` is the main theme selection screen.\n\nIt combines:\n\n- a live preview of the selected theme\n- a horizontal theme picker carousel\n- a bottom apply bar\n- a toolbar menu for creation, duplication, and editing\n\n### Theme selection model\n\nThe view tracks:\n\n- `selectedTheme: AppTheme?`\n- `showingCreateOptions: Bool`\n\nIt derives:\n\n- `allThemes` = predefined themes + custom themes\n- `previewTheme` = selected theme or current theme\n- `previewColors` = `previewTheme.lightColors`\n\nThe preview intentionally uses `lightColors` for the card carousel and preview surfaces, which keeps the theme browser visually stable regardless of current dark mode.\n\n### Apply flow\n\n- selecting a card updates `selectedTheme`\n- tapping Apply calls `themeManager.switchTheme(previewTheme)`\n- tapping Cancel restores `selectedTheme` to `themeManager.currentTheme` and dismisses the view\n\n### Toolbar actions\n\nThe menu supports:\n\n- creating a new theme\n- duplicating the selected theme\n- editing colors for custom themes\n- editing background for custom themes\n\n### Theme creation dialog\n\n`confirmationDialog(\"Create New Theme\", ...)` offers:\n\n- start from scratch\n- inherit from any predefined theme\n- inherit from any custom theme\n\nWhen a new theme is created, the view immediately navigates to `ThemeEditorView` for editing.\n\n### Preview components\n\n`ThemeListView` contains several private preview views:\n\n- `ThemeWorkoutSessionPreview`\n- `ThemePreviewWorkoutHero`\n- `ThemePreviewMetric`\n- `ThemePreviewExerciseRow`\n- `ThemePickerCarousel`\n- `ThemePickerCard`\n- `ThemeCardSwatch`\n- `ThemeApplyBar`\n\nThese are presentation-only helpers that demonstrate how the theme affects a workout session UI.\n\n### `ThemeApplyBar`\n\nA bottom safe-area bar with Cancel and Apply actions.\n\nIt also exposes an accessibility label that includes the selected theme name.\n\n---\n\n## Design system showcase\n\n### `DesignSystemShowcaseView`\n\nThis screen is a visual reference for the design system tokens and modifiers.\n\nIt demonstrates:\n\n- theme-aware colors\n- native grammar spacing and depth\n- typography scale\n- spacing scale\n- corner radii\n- button styles\n- card styles\n- keypad button styling\n\nIt is useful for validating visual consistency when changing tokens in `DesignSystem`.\n\n### Usage patterns shown here\n\n- `primaryButtonStyle()`\n- `secondaryButtonStyle()`\n- `tertiaryButtonStyle()`\n- `destructiveButtonStyle()`\n- `cardStyle()`\n- `flatCardStyle()`\n- `keypadButtonStyle()`\n\n---\n\n## Integration points with the rest of the app\n\nThis module is not isolated; it is used throughout the app’s UI.\n\nCommon consumers include:\n\n- `ContentView` via `syncThemeWithColorScheme()`\n- workout and settings screens via `themedListBackground()` and `themedListRow()`\n- workout session controls via `keypadButtonStyle()`\n- plan/detail screens via `primaryButtonStyle()`\n- theme-related navigation via `AppRouter`\n\nThe theme system also interacts with background rendering:\n\n- `ThemeStorage.exportBackgroundAsImage(_:)`\n- `BackgroundRenderer`\n- `BackgroundBuilderView` export flows\n\n---\n\n## Practical contribution notes\n\n### When adding a new themed UI element\n\nPrefer using `DesignSystem.Colors`, `DesignSystem.Typography`, and `DesignSystem.Spacing` instead of hard-coded values.\n\n### When adding a new theme-aware screen\n\nInject `ThemeManager` through the environment and use `ThemeManager.currentColors` or `DesignSystem.Colors`.\n\n### When changing persistence\n\nKeep legacy keys in mind:\n\n- appearance mode uses `AppPreferenceKey.appAppearanceMode` and `AppPreferenceKey.legacyUseSystemTheme`\n- selected theme uses both `selected_theme_id` and `selectedThemeId`\n\n### When editing theme data\n\nRemember that `ThemeManager` persists custom themes asynchronously after mutations. If you add new mutation paths, make sure they also save.\n\n### When working with accessibility\n\nUse `checkAccessibility(for:)` to validate contrast for new theme color combinations, especially if you introduce new text or background roles.\n\n---\n\n## Summary of key flows\n\n### Appearance resolution\n\n1. `ThemeEnvironment.syncThemeWithColorScheme()` observes system changes\n2. `ThemeManager.updateSystemColorScheme(isDarkMode:)` caches the system state\n3. `ThemeManager.resolveDarkMode()` computes `isDarkMode`\n4. `AppAppearanceMode.resolvesDarkMode(systemIsDarkMode:)` determines the final mode\n\n### Theme application\n\n1. user selects a theme in `ThemeListView`\n2. `ThemeManager.switchTheme(_:)` updates `currentTheme`\n3. `ThemeStorage.saveSelectedTheme(id:)` persists the selection\n\n### Theme editing\n\n1. `ThemeEditorView` edits local copies of the theme data\n2. Save triggers `ThemeManager.updateTheme(_:)`\n3. `ThemeManager` persists the updated custom theme list\n\n### Design token usage\n\n1. views reference `DesignSystem`\n2. `DesignSystem.Colors` reads `ThemeManager.shared.currentColors`\n3. UI updates automatically when theme or appearance changes","vertical-split-layout":"# Vertical Split Layout\n\n# Vertical Split Layout\n\nA SwiftUI container for presenting two vertically stacked panels with a configurable split state, mini overlays, and an optional notification lane in the divider area.\n\nThis module is used by `HomeView` to build the app’s split-screen layout and expose a small set of styling and content configuration hooks through fluent modifiers.\n\n## Overview\n\n`VerticalSplitView` manages two primary content regions:\n\n- a **top panel**\n- a **bottom panel**\n\nIt supports several detent states via `SplitDetent`, including:\n\n- `topFull`\n- `bottomFull`\n- `topMini`\n- `bottomMini`\n- `fraction(_:)`\n\nIn practice, the current implementation is optimized around the two-state behavior used by the view itself:\n\n- `bottomFull`\n- `bottomMini`\n\nOther detents are logged and remapped internally to `bottomMini` unless the split is explicitly `bottomFull`.\n\nThe layout also supports:\n\n- custom mini overlays for each panel\n- a configurable background behind the split\n- a configurable background color for the panel containers\n- optional notification chips displayed in the divider lane\n\n---\n\n## Main Types\n\n### `VerticalSplitView`\n\n```swift\npublic struct VerticalSplitView<TopView, BottomView, TopViewOverlay, BottomViewOverlay>: View\n```\n\nThe primary container view.\n\n#### Responsibilities\n\n- hosts the top and bottom content closures\n- applies split-state transitions\n- manages panel visibility and sizing\n- renders mini overlays when a panel is not full-screen\n- displays divider notifications when enabled\n- reacts to geometry changes and detent updates\n\n#### Generic parameters\n\n- `TopView`: content shown in the top panel\n- `BottomView`: content shown in the bottom panel\n- `TopViewOverlay`: overlay shown when the top panel is minimized\n- `BottomViewOverlay`: overlay shown when the bottom panel is minimized\n\n---\n\n### `SplitDetent`\n\n```swift\npublic enum SplitDetent: Equatable\n```\n\nRepresents the split state.\n\n#### Cases\n\n- `topFull`\n- `bottomFull`\n- `topMini`\n- `bottomMini`\n- `fraction(Double)`\n\n#### Notes\n\nThe `description` property formats `.fraction` values to three fractional digits for logging.\n\nAlthough the enum includes fractional and top-oriented states, `VerticalSplitView` currently treats anything other than `.bottomFull` as a request for the bottom-mini configuration.\n\n---\n\n### `SplitNotification`\n\n```swift\npublic struct SplitNotification: Identifiable, Equatable\n```\n\nA small data model for divider-lane chips.\n\n#### Properties\n\n- `id: UUID`\n- `systemName: String`\n- `text: String`\n\n#### Usage\n\nEach notification is rendered as a capsule chip with:\n\n- a SF Symbol icon from `systemName`\n- a short text label\n\n---\n\n## Layout Architecture\n\n`VerticalSplitView` is built from three visual layers:\n\n1. **Top panel** via `TopWrapper`\n2. **Bottom panel** via `BottomWrapper`\n3. **Optional notification lane** positioned over the divider\n\nThe view uses a `ZStack` to layer the panels and the divider lane. Each panel is wrapped in a geometry-aware container that handles:\n\n- safe-area padding\n- rounded masking\n- background fill\n- mini overlay placement\n- overscroll scaling\n\n### Small architecture diagram\n\n```mermaid\nflowchart TB\n V[VerticalSplitView] --> T[TopWrapper]\n V --> B[BottomWrapper]\n V --> N[notificationsLane]\n V --> D[SplitDetent binding]\n T --> O1[topViewOverlay]\n B --> O2[bottomViewOverlay]\n```\n\n---\n\n## Split State and Sizing\n\n### Internal state\n\n`VerticalSplitView` maintains several pieces of state to drive layout:\n\n- `detent`: external binding controlling the split\n- `partition`: current split offset\n- `topHeight`: computed height for the top panel\n- `currentSpacing`: divider spacing between panels\n- `screenHeight`: current container height\n- `topFrame` / `bottomFrame`: measured frames in the named coordinate space\n- `hideTop` / `hideBottom`: panel visibility flags\n- `overscroll`: overscroll amount used for elastic scaling\n- `didSetInitialSplit`: delays animation behavior until the initial layout settles\n\n### Core sizing constants\n\n- `dividerSpacing = 36`\n- `miniOverlayHeight = 58`\n- `overlayTransitionHeight = miniOverlayHeight * 2`\n\nThese values define the visual rhythm of the split and the transition zone between full and mini states.\n\n### Derived layout values\n\n#### `cardHeight`\n\n```swift\nscreenHeight / 2 - currentSpacing / 2\n```\n\nRepresents the nominal height of each panel when the split is balanced.\n\n#### `range`\n\n```swift\ncardHeight - miniOverlayHeight\n```\n\nUsed to compute the partition for the mini state.\n\n#### `dividerOffsetY`\n\nComputes the vertical position of the notification lane / divider chrome.\n\nIt prefers measured panel frames when both are available, and falls back to partition-based positioning otherwise.\n\n---\n\n## Split Update Flow\n\nThe split is updated through two internal methods:\n\n### `didUpdateSplit(split:)`\n\nThis is the main entry point for applying a new detent.\n\nBehavior:\n\n1. logs the detent using `OSLog`\n2. maps the detent to the supported two-state layout\n3. calls `applyTwoState(split:)`\n4. refreshes divider spacing\n\nImportant behavior:\n\n- `.bottomFull` is applied directly\n- all other detents are treated as `.bottomMini`\n- if the incoming detent is not `.bottomMini`, the binding is rewritten to `.bottomMini`\n\nThis means the view normalizes unsupported states rather than preserving them.\n\n### `applyTwoState(split:)`\n\nApplies the actual layout state.\n\nFor `.bottomFull`:\n\n- `hideTop = true`\n- `hideBottom = false`\n- `partition = -cardHeight + miniOverlayHeight`\n\nFor the mini state:\n\n- `hideTop = false`\n- `hideBottom = false`\n- `partition = range`\n\nThen:\n\n```swift\ntopHeight = cardHeight + partition\n```\n\n### `updateDividerSpacing(animated:)`\n\nAdjusts divider spacing and reapplies the current two-state layout.\n\n- if `detent == .bottomFull`, it targets `.bottomFull`\n- otherwise it targets `.bottomMini`\n\nWhen `animated` is `true`, the update runs inside a `.smooth(duration: 0.3)` animation.\n\n---\n\n## Rendering Behavior\n\n### Top panel\n\nRendered through `TopWrapper` when `hideTop == false`.\n\nKey inputs:\n\n- `minimise`\n- `overscroll`\n- `isFull`\n- `bgColor`\n- `content`\n- `overlay`\n\nThe top panel:\n\n- expands to `topHeight + overscroll / 5`\n- transitions vertically when inserted/removed\n- reports its frame through `TopSplitFramePreferenceKey`\n\n### Bottom panel\n\nRendered through `BottomWrapper` when `hideBottom == false`.\n\nKey inputs:\n\n- `minimise`\n- `overscroll`\n- `isFull`\n- `bgColor`\n- `content`\n- `overlay`\n\nThe bottom panel:\n\n- uses the complementary minimize calculation\n- transitions vertically based on `partition`\n- reports its frame through `BottomSplitFramePreferenceKey`\n\n### Mini overlays\n\nEach wrapper places its overlay at the edge nearest the divider:\n\n- `TopWrapper` overlays at the bottom\n- `BottomWrapper` overlays at the top\n\nThe overlay:\n\n- fades out as the panel becomes full\n- shifts slightly during minimization\n- scales up subtly during transition\n- disables hit testing unless fully visible\n\nThis makes the overlay behave like a compact header for the minimized panel.\n\n---\n\n## Notification Lane\n\n`notificationsLane` renders a horizontally scrolling row of chips.\n\nIt is shown only when:\n\n- `notifications` is not empty\n- neither panel is hidden\n\nThis is controlled by:\n\n```swift\nvar shouldShowNotificationsLane: Bool\n```\n\nThe lane:\n\n- is non-interactive (`allowsHitTesting(false)`)\n- hides scroll indicators\n- uses capsule styling with translucent white backgrounds\n- is offset vertically to sit in the divider region\n\nThe lane’s spacing is tied to `currentSpacing`, which is updated by `updateDividerSpacing(animated:)`.\n\n---\n\n## Geometry and Safe Area Handling\n\nThe module uses a few helper extensions to make layout calculations consistent across devices.\n\n### `UIApplication.screenSize`\n\nReturns the current screen size from the first connected window scene, with a fallback size of `393 x 852`.\n\n### `UIApplication.safeAreaInsets`\n\nReturns the current window’s safe area insets, or zero if unavailable.\n\n### `SafeAreaInsetsKey`\n\nAn `EnvironmentKey` that exposes safe area insets through SwiftUI environment values.\n\n### `UIEdgeInsets.insets`\n\nConverts UIKit insets to SwiftUI `EdgeInsets`.\n\nThese helpers are used by the wrappers and the split container to:\n\n- pad content away from the system safe areas\n- determine whether bottom extra offset should be applied\n- size the wrappers to the current screen width\n\n---\n\n## Wrapper Components\n\n### `TopWrapper`\n\nA private SwiftUI view that wraps the top panel content.\n\n#### Responsibilities\n\n- applies safe-area padding\n- clips content to a rounded rectangle\n- renders the top mini overlay\n- applies overscroll scaling\n- fills the panel background\n\n#### Notable behavior\n\n- when `isFull` is `true`, the corner radius increases with overscroll\n- when minimized, the corner radius is fixed\n- the overlay is anchored to the bottom edge\n- the wrapper ignores safe areas to allow full-bleed presentation\n\n### `BottomWrapper`\n\nA private SwiftUI view that wraps the bottom panel content.\n\n#### Responsibilities\n\n- mirrors `TopWrapper` for the bottom panel\n- applies safe-area padding\n- renders the bottom mini overlay\n- applies overscroll scaling\n- fills the panel background\n\n#### Notable behavior\n\n- when `isFull` is `true`, the corner radius decreases with overscroll\n- the overlay is anchored to the top edge\n- the wrapper ignores safe areas to allow full-bleed presentation\n\n---\n\n## Public Initializers\n\n`VerticalSplitView` provides four initializer variants to support different overlay combinations.\n\n### Top and bottom overlays\n\n```swift\ninit(\n detent: Binding<SplitDetent> = .constant(.bottomMini),\n topView: @escaping () -> TopView,\n bottomView: @escaping () -> BottomView,\n topMiniOverlay: @escaping () -> TopViewOverlay,\n bottomMiniOverlay: @escaping () -> BottomViewOverlay\n)\n```\n\nUse when both panels need custom minimized overlays.\n\n### Top overlay only\n\n```swift\ninit(\n detent: Binding<SplitDetent> = .constant(.bottomMini),\n topView: @escaping () -> TopView,\n bottomView: @escaping () -> BottomView,\n topMiniOverlay: @escaping () -> TopViewOverlay\n) where BottomViewOverlay == EmptyView\n```\n\nUse when only the top panel needs a custom minimized overlay.\n\n### Bottom overlay only\n\n```swift\ninit(\n detent: Binding<SplitDetent> = .constant(.bottomMini),\n topView: @escaping () -> TopView,\n bottomView: @escaping () -> BottomView,\n bottomMiniOverlay: @escaping () -> BottomViewOverlay\n) where TopViewOverlay == EmptyView\n```\n\nUse when only the bottom panel needs a custom minimized overlay.\n\n### No overlays\n\n```swift\ninit(\n detent: Binding<SplitDetent> = .constant(.bottomMini),\n topView: @escaping () -> TopView,\n bottomView: @escaping () -> BottomView\n) where TopViewOverlay == EmptyView, BottomViewOverlay == EmptyView\n```\n\nUse when the panels do not need custom minimized chrome.\n\n---\n\n## Fluent Modifiers\n\nThese modifiers are defined as a `public extension VerticalSplitView` and return a modified copy of the view.\n\n### `notifications(_:)`\n\nSets the divider-lane notifications.\n\n```swift\nfunc notifications(_ notifications: [SplitNotification]) -> Self\n```\n\n### `splitBackgroundColor(_:)`\n\nSets a solid background behind both panels and transparent accessory chrome.\n\n```swift\nfunc splitBackgroundColor(_ color: Color) -> Self\n```\n\n### `splitBackground(_:)`\n\nSets a custom background view behind both panels and transparent accessory chrome.\n\n```swift\nfunc splitBackground<Background: View>(\n @ViewBuilder _ background: () -> Background\n) -> Self\n```\n\n### `backgroundColor(_:)`\n\nSets the background color used by the top and bottom panel containers, as well as the menu buttons.\n\n```swift\nfunc backgroundColor(_ color: Color) -> Self\n```\n\n---\n\n## Integration with the Rest of the App\n\n`HomeView` is the primary consumer of this module.\n\nFrom the call graph, `HomeView` configures `VerticalSplitView` using:\n\n- `notifications(_:)`\n- `backgroundColor(_:)`\n- `splitBackground(_:)`\n\nThis suggests the split layout is a central shell for the home screen, with the module responsible for:\n\n- panel composition\n- split-state presentation\n- accessory chrome\n- background styling\n\nThe module also references `Color` from the app’s theme-related code path, indicating that split styling is expected to align with the app’s broader design system.\n\n---\n\n## Implementation Notes\n\n### Supported detents\n\nEven though `SplitDetent` includes multiple cases, the current implementation is effectively two-state:\n\n- `bottomFull`\n- `bottomMini`\n\nIf you bind another detent value, `VerticalSplitView` will log the remapping and normalize it to `bottomMini`.\n\n### Initial layout timing\n\n`didSetInitialSplit` is set after a short delay on appear. This is used to suppress early animations during the initial geometry pass.\n\n### Frame measurement\n\nThe view uses preference keys to capture the rendered frames of both panels in the named coordinate space `\"VerticalSplitSpace\"`. These measurements are used to refine divider positioning.\n\n### Hit testing\n\nMini overlays are only interactive when fully visible:\n\n```swift\n.allowsHitTesting(minimise == 0)\n```\n\nThis prevents accidental interaction while the overlay is transitioning.\n\n---\n\n## Practical Usage\n\nA typical configuration looks like this:\n\n```swift\nVerticalSplitView(\n detent: $detent,\n topView: {\n TopContentView()\n },\n bottomView: {\n BottomContentView()\n },\n topMiniOverlay: {\n TopHeaderView()\n },\n bottomMiniOverlay: {\n BottomHeaderView()\n }\n)\n.notifications([\n SplitNotification(systemName: \"bell.fill\", text: \"New update\")\n])\n.backgroundColor(.secondarySystemBackground)\n.splitBackgroundColor(.black)\n```\n\n---\n\n## Summary\n\nThis module provides a reusable vertical split container with:\n\n- two stacked SwiftUI panels\n- mini/full split behavior\n- configurable overlays\n- divider notifications\n- safe-area-aware layout\n- background customization\n\nIts current design is intentionally opinionated around the app’s home screen use case, with internal normalization toward a bottom-focused two-state split.","workout-history-statistics":"# Workout History & Statistics\n\n# Workout History & Statistics\n\nThis module powers the app’s workout history, calendar/list browsing, and detailed training analytics. It is split into three layers:\n\n- **Data shaping and aggregation** in `WorkoutHistoryRepository`\n- **Statistics calculation** in `TrainingStatsCalculator`\n- **SwiftUI presentation** in `WorkoutHistoryView`, `DetailedStatsView`, and `RecordsHistoryView`\n\nThe module is built around completed `WorkoutSession` and `AchievementEvent` records fetched via SwiftData `@Query`. It derives day-level summaries, filter options, streaks, range-based stats, and chart-ready metric points without requiring additional persisted history tables.\n\n## Responsibilities\n\n### Workout history browsing\n`WorkoutHistoryView` presents completed workouts in two modes:\n\n- **List mode**: grouped by training day, with each session rendered as a `WorkoutHistoryCard`\n- **Calendar mode**: month sections with day cells showing training activity, duration, and volume\n\n### Detailed statistics\n`DetailedStatsView` computes aggregate metrics for a selected date range and renders either:\n\n- a summary dashboard of totals, averages, and top exercises\n- a chart view with daily volume, calories, and active time\n\n### Achievement history\n`RecordsHistoryView` shows unlocked and locked achievement badges, plus a filterable list of `AchievementEvent` records.\n\n---\n\n## Architecture overview\n\n```mermaid\nflowchart LR\n Q[SwiftData @Query<br/>WorkoutSession / AchievementEvent] --> R[WorkoutHistoryRepository]\n Q --> T[TrainingStatsCalculator]\n\n R --> H[WorkoutHistoryView]\n R --> D[DetailedStatsView]\n R --> A[RecordsHistoryView]\n\n T --> D\n\n R --> S[WorkoutDaySummary]\n R --> M[WorkoutMonthSection]\n T --> P[DailyMetricPoint]\n T --> U[TrainingSummaryStats]\n```\n\n`WorkoutHistoryRepository` is the main domain layer for history browsing. `TrainingStatsCalculator` builds on the same session data to produce range-based analytics for charts and summary cards.\n\n---\n\n## Core data models\n\n### `WorkoutHistoryFilter`\nRepresents the active history filters:\n\n- `searchText`\n- `routineTitles`\n- `muscleGroups`\n- `workoutTags`\n- `gymLocations`\n\nUse `hasActiveFilters` to determine whether the UI should show removable filter chips, and `clear()` to reset all filters.\n\n### `WorkoutHistoryFilterOptions`\nA derived set of unique filter values extracted from completed sessions:\n\n- `routineTitles`\n- `muscleGroups`\n- `workoutTags`\n- `gymLocations`\n\nThis is used to populate the filter menus in `WorkoutHistoryView`.\n\n### `WorkoutDaySummary`\nRepresents one training day:\n\n- `date`\n- `sessions`\n- `totalDuration`\n- `totalVolume`\n- `prCount`\n- `isTrained`\n\nThis is the primary unit for list grouping and calendar day selection.\n\n### `WorkoutMonthSection`\nRepresents a month in calendar mode:\n\n- `monthStart`\n- `title`\n- `days`\n\nEach section contains a 7-column grid of `WorkoutMonthDay` values, including leading and trailing padding days.\n\n### `WorkoutProfileSummary`\nA compact profile-level summary:\n\n- `totalWorkouts`\n- `currentStreak`\n\nThis is used by profile sync flows elsewhere in the app.\n\n### `TrainingSummaryStats`\nReturned by `calculateTrainingStats(for:from:calendar:)`, this contains the full analytics payload for `DetailedStatsView`:\n\n- range metadata\n- totals and averages\n- workout day count\n- highlight insight\n- daily chart points\n- top exercises\n\n### `DailyMetricPoint`\nChart-ready daily data:\n\n- `date`\n- `volume`\n- `calories`\n- `activeTime`\n- record/low flags for annotations\n\n### `TrainingHighlight`\nA small insight card model with:\n\n- `title`\n- `detail`\n- `value`\n\n---\n\n## `WorkoutHistoryRepository`\n\n`WorkoutHistoryRepository` is the central utility for transforming raw sessions into history-friendly structures.\n\n### Initialization\n\n```swift\nlet repository = WorkoutHistoryRepository(calendar: .current)\n```\n\nThe repository stores a `Calendar` instance so all grouping, streak, and date math stays consistent and testable.\n\n---\n\n### Session normalization and filtering\n\n#### `completedSessions(from:)`\nReturns only non-active sessions, sorted newest-first.\n\nThis is the base input for nearly every other repository method.\n\n#### `routineTitle(for:)`\nReturns the trimmed source workout plan title, or `\"Custom\"` when the session has no plan title.\n\nThis keeps UI labels consistent across history views.\n\n#### `filterOptions(from:)`\nBuilds unique filter values from completed sessions.\n\nIt derives values from:\n\n- routine titles\n- muscle groups\n- workout tags\n- gym locations\n\nValues are trimmed, deduplicated, and sorted case-insensitively.\n\n#### `filteredSessions(from:using:)`\nApplies the active `WorkoutHistoryFilter` to completed sessions.\n\nFiltering behavior:\n\n- routine title must match if selected\n- muscle groups use set intersection\n- workout tags use set intersection\n- gym locations must match exactly\n- search text is matched against:\n - session notes\n - gym location\n - routine title\n - workout tags\n - muscle groups\n - exercise names\n\nSearch normalization is diacritic-insensitive and case-insensitive.\n\n---\n\n### Day and month aggregation\n\n#### `daySummaries(from:achievements:)`\nGroups completed sessions by day and returns `WorkoutDaySummary` values sorted newest-first.\n\nFor each day it computes:\n\n- total duration\n- total volume\n- PR count from matching `AchievementEvent` records\n- whether the day contains training\n\nThis is the main data source for `WorkoutHistoryView` list mode and calendar mode.\n\n#### `monthSections(from:)`\nConverts day summaries into month sections for calendar browsing.\n\nEach section contains a full grid of `WorkoutMonthDay` values, including padding days before and after the displayed month so the grid stays aligned to weeks.\n\n#### `sessions(on:from:)`\nReturns completed sessions that occurred on a specific date.\n\n#### `adjacentSession(to:offset:within:)`\nFinds a session from the previous or next training day relative to the given session.\n\nThis is used by detail navigation flows to move between workout days.\n\n---\n\n### Profile and streak helpers\n\n#### `profileSummary(from:)`\nReturns total completed workouts and the current streak.\n\n#### `currentStreakDays(from:)`\nComputes the current streak using unique workout days only.\n\nImportant behavior:\n\n- only completed sessions count\n- multiple sessions on the same day count as one workout day\n- the streak must start on today or yesterday\n- the streak breaks at the first missing day\n\nThis logic is used by profile sync and widget data flows.\n\n---\n\n### Session metadata enrichment\n\n#### `populateSessionMetadata(_:exercises:)`\nMutates a `WorkoutSession` after completion to normalize and enrich metadata.\n\nIt:\n\n- resolves muscle groups from the exercise catalog using `exerciseId` or normalized exercise name\n- deduplicates and sorts muscle groups and workout tags\n- trims gym location and session notes\n\nThis is called from workout completion flows so history filters and summaries have clean data.\n\n---\n\n### Session metrics\n\nThese static helpers are shared across history, detail, stats, and widget code:\n\n#### `sessionVolume(_:)`\nCalculates total lifted volume across all sets:\n\n- `weight * reps` for each set\n- nil weights/reps are treated as zero\n\n#### `sessionSetCount(_:)`\nCounts all sets across all exercise logs.\n\n#### `sessionRepCount(_:)`\nCounts all reps across all exercise logs.\n\n#### `sessionCalories(_:)`\nEstimates calories burned from duration:\n\n- `duration / 60 * 5.0`\n\nThis is a simple duration-based estimate used consistently across the app.\n\n---\n\n## `TrainingStatsCalculator`\n\nThis file turns workout sessions into range-based analytics for the detailed stats screen.\n\n### Range selection\n\n#### `StatsRangePreset`\nBuilt-in presets for the stats screen:\n\n- `thisWeek`\n- `lastWeek`\n- `lastFourWeeks`\n- `thisMonth`\n- `custom`\n\nEach preset has a user-facing `title`.\n\n#### `StatsViewMode`\nControls the detailed stats screen layout:\n\n- `summary`\n- `charts`\n\n#### `dateInterval(for:referenceDate:calendar:customRange:)`\nMaps a preset to a `DateInterval`.\n\nBehavior by preset:\n\n- `thisWeek`: current calendar week\n- `lastWeek`: previous full week via `lastWeekDateInterval`\n- `lastFourWeeks`: 28 days ending just before the current week\n- `thisMonth`: current calendar month\n- `custom`: returns the provided custom range\n\n#### `lastWeekDateInterval(referenceDate:calendar:)`\nBuilds the previous week interval relative to the current week.\n\n---\n\n### Aggregate stats\n\n#### `calculateTrainingStats(for:from:calendar:)`\nProduces a `TrainingSummaryStats` value for a date range.\n\nIt filters to completed sessions inside the range, then computes:\n\n- total duration\n- total volume\n- total sets\n- total reps\n- total calories\n- session count\n- average duration\n- average volume\n- unique workout day count\n- top exercises\n- highlight insight\n- daily chart points\n\nIt also builds per-day aggregates for volume, calories, and active time.\n\nIf no completed sessions fall in the range, it returns `nil`.\n\n#### How the calculation works\n\n1. Filter sessions to completed workouts inside the selected `DateInterval`\n2. Accumulate totals across all sessions\n3. Aggregate daily volume, calories, and duration by start-of-day\n4. Build chart points for every day in the range\n5. Mark record/high and low points for chart annotations\n6. Extract top exercises by total volume\n7. Create a highlight card for the top exercise\n\nThe function delegates metric calculations to `WorkoutHistoryRepository.sessionVolume`, `sessionSetCount`, `sessionRepCount`, and `sessionCalories` so the app uses one consistent definition of workout metrics.\n\n---\n\n### Daily chart generation\n\n#### `generateDailyPoints(range:calendar:aggregates:globalRecord:)`\nCreates one `DailyMetricPoint` per day in the selected range.\n\nEach point includes:\n\n- volume\n- calories\n- active time\n- record flags\n\nThis ensures the chart has a continuous x-axis even on rest days.\n\n#### `aggregateDailyVolumes(from:calendar:)`\nBuilds a `[Date: Double]` map of daily volume.\n\n#### `aggregateDailyCalories(from:calendar:)`\nBuilds a `[Date: Double]` map of daily calories.\n\n---\n\n### Formatting helpers\n\n#### `formatDuration(_:)`\nFormats a duration as:\n\n- `Xh Ym` when hours are present\n- `Xm` otherwise\n\n#### `formatWeight(_:)`\nFormats weight as:\n\n- `N kg` below 1000\n- `X.Y t` at 1000 and above\n\nThis is used in stats cards and history summaries.\n\n#### `DateInterval.formattedRangeDescription(calendar:)`\nFormats a date interval as `d MMM – d MMM`.\n\n#### `DateInterval.contains(_:)`\nInclusive date containment check used by stats filtering.\n\n---\n\n## `WorkoutHistoryView`\n\nThis is the main workout history screen.\n\n### Data sources\n\nThe view reads from SwiftData:\n\n- `@Query(sort: \\WorkoutSession.date, order: .reverse) private var sessions`\n- `@Query(sort: \\AchievementEvent.achievedAt, order: .reverse) private var achievements`\n\nIt also owns a `WorkoutHistoryViewModel` state object for mode and filter state.\n\n### Main behaviors\n\n- shows an empty state when there are no visible sessions\n- supports search and multi-select filters\n- toggles between list and calendar modes\n- scrolls to a selected date when `selectedDate` is provided\n- opens `WorkoutDaySummarySheet` when a calendar day contains multiple sessions\n- navigates to workout detail screens via `AppRouter`\n\n### View model usage\n\n`WorkoutHistoryViewModel` wraps `WorkoutHistoryRepository` and exposes:\n\n- `visibleSessions(from:)`\n- `filterOptions(from:)`\n- `daySummaries(from:achievements:)`\n- `monthSections(from:achievements:)`\n\nThis keeps the view focused on presentation.\n\n### Key subviews\n\n#### `HistoryHeroMetric`\nDisplays the top summary metrics in the header card.\n\n#### `HistoryFilterMenu`\nA menu-based filter control for routine, muscle, tag, and location filters.\n\n#### `WorkoutCalendarDayCell`\nRenders a single day in calendar mode, including:\n\n- day number\n- duration\n- volume\n- training highlight styling\n- PR border accent\n\n#### `WorkoutDaySummarySheet`\nShows all sessions for a selected training day and lets the user jump to a session detail view.\n\n#### `WorkoutHistoryCard`\nRenders a single session card in list mode, including:\n\n- routine title\n- time\n- PR count\n- exercise count\n- set count\n- volume\n- duration\n- gym location\n- tags\n- notes\n\n---\n\n## `DetailedStatsView`\n\nThis screen presents analytics for a selected date range.\n\n### State\n\n- `selectedPreset`: range preset\n- `customStartDate` / `customEndDate`: used when `selectedPreset == .custom`\n- `mode`: summary or charts\n\n### Range handling\n\n`selectedRange` is derived from `dateInterval(for:referenceDate:calendar:customRange:)`.\n\nWhen custom dates change, `clampCustomDates()` ensures the end date is never earlier than the start date.\n\n### Summary mode\n\n`summaryContent(_:)` renders:\n\n- active time\n- volume\n- workouts\n- calories\n- sets\n- reps\n- workout days\n- average session duration\n- average volume\n- highlight insight\n- top exercises\n\n### Charts mode\n\n`chartsContent(_:)` renders a `Charts.Chart` over `stats.dailyMetrics` with:\n\n- volume line\n- calories line\n- active time line\n- record markers\n- deload low markers\n- calorie record markers\n\nThe chart uses daily points from `calculateTrainingStats`, so it always reflects the selected range.\n\n---\n\n## `RecordsHistoryView`\n\nThis module section displays achievement history and badge progress.\n\n### Data sources\n\n- `@Query(sort: \\AchievementEvent.achievedAt, order: .reverse) private var achievements`\n\n### Filtering\n\nThe view supports filtering by:\n\n- achievement category\n- achievement kind\n- exercise\n- timeframe\n\nThe timeframe filter is implemented locally with `AchievementTimeframe`.\n\n### Badge sections\n\nThe screen splits catalog items into:\n\n- unlocked badges\n- remaining badges\n\nIt uses `AchievementService.shared` to:\n\n- fetch catalog items\n- determine unlocked IDs\n- find the latest matching achievement for a badge\n\n### Achievement cards\n\n`achievementCard(_:)` renders each `AchievementEvent` with:\n\n- badge artwork\n- title\n- subtitle\n- detail\n- timestamp\n- current badge indicator\n\n---\n\n## Data flow summary\n\n```mermaid\nflowchart TD\n A[WorkoutSession / AchievementEvent] --> B[WorkoutHistoryRepository]\n A --> C[TrainingStatsCalculator]\n\n B --> D[WorkoutHistoryView]\n B --> E[WorkoutDaySummary]\n B --> F[WorkoutMonthSection]\n B --> G[WorkoutProfileSummary]\n\n C --> H[TrainingSummaryStats]\n C --> I[DailyMetricPoint]\n\n H --> J[DetailedStatsView]\n E --> D\n F --> D\n```\n\n---\n\n## Integration points with the rest of the app\n\n### Workout completion\n`populateSessionMetadata(_:exercises:)` is called from workout completion flows so newly saved sessions have normalized metadata for history filters and summaries.\n\n### Workout detail\n`WorkoutDetailView` uses repository helpers such as:\n\n- `routineTitle(for:)`\n- `sessionVolume(_:)`\n- `sessionPRs(for:achievements:)`\n- `adjacentSession(to:offset:within:)`\n\n### Profile and widgets\n`profileSummary(from:)` and `currentStreakDays(from:)` feed profile sync and widget data providers.\n\n### Home screen\n`calculateTrainingStats(for:from:calendar:)` is used by the home dashboard to surface recent training metrics.\n\n### Navigation\n`WorkoutHistoryView` and `RecordsHistoryView` both navigate through `AppRouter`, keeping history screens integrated with the app’s routing model.\n\n---\n\n## Implementation notes\n\n### Completed sessions only\nMost repository methods intentionally ignore active sessions. This keeps history, streaks, and stats focused on finished workouts.\n\n### Calendar-aware grouping\nAll day and month grouping uses the injected `Calendar`, which makes the code locale- and test-friendly.\n\n### Search normalization\nSearch uses diacritic-insensitive and case-insensitive folding, so users can find workouts even when accents or capitalization differ.\n\n### Volume and calorie definitions\nThe module uses a single volume formula and a simple calorie estimate everywhere. If either definition changes, update the repository helpers first so all screens stay consistent.\n\n### Chart continuity\n`generateDailyPoints` emits a point for every day in the selected range, including zero-activity days. This is important for readable charts and accurate record annotations.\n\n---\n\n## Extending the module\n\n### Adding a new history filter\n1. Add a field to `WorkoutHistoryFilter`\n2. Include it in `hasActiveFilters` and `clear()`\n3. Derive options in `filterOptions(from:)`\n4. Apply it in `filteredSessions(from:using:)`\n5. Add UI in `WorkoutHistoryView`\n\n### Adding a new stats metric\n1. Compute it in `calculateTrainingStats(for:from:calendar:)`\n2. Add it to `TrainingSummaryStats`\n3. Render it in `DetailedStatsView`\n4. If it is chart-related, extend `DailyMetricPoint`\n\n### Changing calendar behavior\nUpdate `WorkoutHistoryRepository` and `TrainingStatsCalculator` together so list mode, calendar mode, and detailed stats all use the same date semantics.\n\n---\n\n## Related types and helpers\n\nThis module depends on several app-wide types and utilities:\n\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `AchievementEvent`\n- `AchievementService`\n- `AppRouter`\n- `SearchBar`\n- `DesignSystem`\n- `AchievementBadgeArtworkView`\n\nIt also relies on SwiftData queries and Swift Charts for data access and visualization.","workout-plan-creation":"# Workout Plan Creation\n\n# Workout Plan Creation\n\nThe Workout Plan Creation module provides the UI and data flow for building a new `WorkoutPlan` from scratch. It lets a user define plan metadata, add workout and rest days, attach exercises to each workout day, and persist the finished plan into SwiftData.\n\nThis module is centered around three layers:\n\n- **Draft models** that hold editable state in memory\n- **A view model** that manages draft mutation, validation, and conversion\n- **SwiftUI views** that render the builder experience and exercise picker\n\nIt also includes the small set of enums used to populate plan metadata pickers.\n\n## Responsibilities\n\nThe module handles:\n\n- Creating a new plan draft with sensible defaults\n- Editing plan metadata such as goal, level, duration, equipment, and audience\n- Adding, deleting, and reordering days\n- Adding, deleting, and reordering exercises within a day\n- Selecting exercises from the catalog or custom templates\n- Validating the draft before save\n- Converting the draft into a persisted `WorkoutPlan`\n\nIt does **not** handle plan execution, progress tracking, or session logging. Those concerns live in `WorkoutPlanService`, `WorkoutPlanDetailView`, and the workout session/history features.\n\n---\n\n## Main flow\n\n```mermaid\nflowchart TD\n A[WorkoutPlanBuilderView] --> B[WorkoutPlanBuilderViewModel]\n B --> C[WorkoutPlanDraft]\n A --> D[WorkoutPlanExercisePickerView]\n D --> E[ExerciseSelection]\n B --> F[WorkoutPlanCreationService]\n F --> G[WorkoutPlan]\n G --> H[SwiftData modelContext.insert]\n```\n\n### Save flow\n\n1. `WorkoutPlanBuilderView` collects user input through bindings into `WorkoutPlanBuilderViewModel.draft`.\n2. The user taps **Save**.\n3. `WorkoutPlanBuilderView.save()` calls `viewModel.makePlan()`.\n4. `WorkoutPlanBuilderViewModel.makePlan()` delegates to `WorkoutPlanCreationService.makePlan(from:)`.\n5. The service validates and normalizes the draft into a `WorkoutPlan`.\n6. The view inserts the plan into the SwiftData `modelContext` and dismisses the sheet.\n\nIf validation fails, the view model sets `validationMessage` and the view displays it inline.\n\n---\n\n## Key types\n\n### `WorkoutPlanDraft`\n\n`WorkoutPlanDraft` is the editable in-memory representation of a plan.\n\nIt stores:\n\n- `title`\n- `summary`\n- `goal`\n- `workoutType`\n- `level`\n- `durationWeeks`\n- `daysPerWeek`\n- `timeRange`\n- `selectedEquipment`\n- `audience`\n- `days`\n\nIt also exposes derived values used by the UI and validation:\n\n- `equipmentSummary`\n- `trimmedTitle`\n- `hasWorkoutDay`\n- `isValid`\n\n### Defaults\n\nA new draft starts with:\n\n- empty title and summary\n- `goal = .generalFitness`\n- `workoutType = .fullBody`\n- `level = .beginner`\n- `durationWeeks = .eight`\n- `daysPerWeek = .three`\n- `timeRange = .fortyFiveToSixty`\n- `selectedEquipment = [.bodyweight]`\n- `audience = .all`\n- one workout day: `Day 1`\n\nThese defaults are defined directly in `WorkoutPlanDraft`.\n\n### `WorkoutPlanDayDraft`\n\nRepresents one editable day in the draft.\n\nFields:\n\n- `id`\n- `displayName`\n- `isRestDay`\n- `sortOrder`\n- `exercises`\n\nThe `id` is stable and is used by the view model to keep bindings attached to the correct day even after reordering.\n\n### `WorkoutPlanExerciseDraft`\n\nRepresents one editable exercise prescription inside a day.\n\nFields:\n\n- `id`\n- `selection`\n- `setsText`\n- `repsText`\n- `notes`\n- `sortOrder`\n\nDefaults:\n\n- `setsText = \"3\"`\n- `repsText = \"8-12\"`\n- `notes = \"\"`\n\nThe draft stores sets and reps as strings because the builder UI is text-field driven. Conversion into the persisted model happens later in `WorkoutPlanCreationService`.\n\n---\n\n## Metadata enums\n\nThe module includes several small enums that back the plan metadata pickers.\n\n### `WorkoutPlanGoal`\n\nPossible plan goals:\n\n- `Build Muscle`\n- `General Fitness`\n- `Increase Endurance`\n- `Increase Strength`\n- `Lose Fat`\n- `Sports Performance`\n\n### `WorkoutPlanType`\n\nPossible plan structures:\n\n- `Cardio`\n- `Full Body`\n- `Single Muscle Group`\n- `Split`\n- `Sports Training`\n\n### `WorkoutPlanLevel`\n\nTraining levels:\n\n- `Advanced`\n- `Beginner`\n- `Intermediate`\n\n### `WorkoutPlanDurationWeeks`\n\nDuration options backed by `Int` raw values.\n\nExamples include:\n\n- `1 week`\n- `8 weeks`\n- `12 weeks`\n- `52 weeks`\n\n`title` formats singular/plural correctly, while `weeks` exposes the numeric value.\n\n### `WorkoutPlanDaysPerWeek`\n\nDays-per-week options from 1 to 7.\n\n### `WorkoutPlanTimeRange`\n\nRepresents a min/max workout duration window.\n\nExamples:\n\n- `10-20 minutes`\n- `45-60 minutes`\n- `90-120 minutes`\n\nThis type is not an enum; it is a value type with predefined static cases and an `allCases` array.\n\n### `WorkoutPlanEquipment`\n\nEquipment options include:\n\n- `Bodyweight`\n- `Dumbbells`\n- `Barbell`\n- `Bands`\n- `Machines`\n- `None`\n- `Other`\n\nThe draft stores selected equipment as a `Set<WorkoutPlanEquipment>`.\n\n### `WorkoutPlanAudience`\n\nAudience options:\n\n- `Female`\n- `Male`\n- `Male & Female`\n\n---\n\n## `WorkoutPlanBuilderViewModel`\n\n`WorkoutPlanBuilderViewModel` is the state and behavior layer for the builder UI.\n\nIt is marked:\n\n- `@MainActor`\n- `@Observable`\n\nThis makes it suitable for direct use from SwiftUI while keeping all mutations on the main thread.\n\n### Responsibilities\n\nThe view model:\n\n- owns the current `WorkoutPlanDraft`\n- exposes `canSave`\n- creates SwiftUI `Binding`s into nested draft state\n- manages equipment selection\n- adds, deletes, and reorders days\n- adds, deletes, and reorders exercises\n- validates and converts the draft through `makePlan()`\n\n### Binding helpers\n\nThe builder UI edits nested data structures, so the view model provides binding helpers:\n\n- `binding(_:)` for top-level draft properties\n- `bindingForDay(_:_: )` for day fields\n- `bindingForExercise(dayID:exerciseID:_: )` for exercise fields\n\nThese helpers resolve items by stable `id`, not by array index alone. That matters when days or exercises are moved or deleted while the UI is still holding onto a binding.\n\n#### Why stable identity matters\n\nThe tests referenced in this area verify that bindings continue to resolve the correct day after reordering and that deleted items fail gracefully during reconciliation. The implementation uses a fallback snapshot value when the target item disappears.\n\n### Equipment selection\n\n`isEquipmentSelected(_:)` and `setEquipment(_:isSelected:)` manage the selected equipment set.\n\nSpecial behavior:\n\n- Selecting `.none` clears all other equipment and leaves only `.none`\n- Selecting any other equipment removes `.none`\n- Deselecting removes only that item\n\nThis keeps the equipment state semantically consistent.\n\n### Day management\n\n- `addWorkoutDay()`\n- `addRestDay()`\n- `deleteDay(_:)`\n- `moveDay(_:direction:)`\n\nAfter any structural change, the view model calls `normalizeDayOrdering()` so `sortOrder` stays contiguous and reflects the current visual order.\n\nImportant constraint:\n\n- `deleteDay(_:)` refuses to remove the last remaining day\n\n### Exercise management\n\n- `addExercise(_:to:)`\n- `deleteExercise(_:from:)`\n- `moveExercise(_:in:direction:)`\n\nAdding an exercise automatically converts the target day into a workout day:\n\n- `isRestDay = false`\n\nThis prevents a rest day from silently containing exercises.\n\nAfter deletion or reordering, `normalizeExerciseOrdering(in:)` renumbers the exercises in that day.\n\n### Validation and save\n\n`canSave` simply mirrors `draft.isValid`.\n\n`makePlan()` delegates to `WorkoutPlanCreationService` and maps service errors to user-facing validation messages:\n\n- `missingTitle` → “Add a plan title before saving.”\n- `missingWorkoutDay` → “Add at least one workout day.”\n- `workoutDayMissingExercise` → “Workout days need at least one exercise.”\n- any other error → “Review the plan before saving.”\n\n---\n\n## `WorkoutPlanCreationService`\n\n`WorkoutPlanCreationService` is the conversion layer from editable draft to persisted `WorkoutPlan`.\n\nIt performs three jobs:\n\n1. Validate required fields\n2. Normalize ordering and identifiers\n3. Build the final `WorkoutPlan` model\n\n### Validation rules\n\n`makePlan(from:)` rejects drafts when:\n\n- the trimmed title is empty\n- there are no workout days\n- any workout day has no exercises\n\nRest days are exempt from the exercise requirement.\n\n### Normalization\n\nThe service generates a local plan ID:\n\n- `local-plan-\\(UUID().uuidString)`\n\nIt then builds normalized day and exercise definitions:\n\n- days are sorted by `sortOrder`\n- each day gets a stable generated ID based on the plan ID and day index\n- exercises are sorted by `sortOrder`\n- each exercise gets a stable generated ID based on plan ID, day index, and exercise index\n\n### Exercise identity requirements\n\n`normalizedExercises(from:planID:dayIndex:)` requires:\n\n- a non-empty trimmed `selection.displayName`\n- a non-empty `selection.sourceID`\n\nIf either is missing, it throws `WorkoutPlanCreationError.exerciseMissingIdentity`.\n\n### Persisted `WorkoutPlan` fields\n\nThe service maps draft metadata into the persisted model:\n\n- `title` from trimmed draft title\n- `summary` from trimmed draft summary, or fallback to `goal.title`\n- `mainGoal` from `goal.title`\n- `workoutType` from `workoutType.title`\n- `trainingLevel` from `level.title`\n- `durationText` from `durationWeeks.title`\n- `durationWeeks` from `durationWeeks.weeks`\n- `daysPerWeekText` from `daysPerWeek.title`\n- `daysPerWeek` from `daysPerWeek.days`\n- `timePerWorkoutText` from `timeRange.title`\n- `timePerWorkoutMinutesMinimum` / `Maximum` from the selected range\n- `equipmentText` from `draft.equipmentSummary`\n- `equipmentTags` from selected equipment titles in display order\n- `targetGender` from `audience.title`\n- `days` from normalized day definitions\n- `ownership = .localUser`\n- `source = .scratch`\n- `localContentID = UUID().uuidString`\n\n### Equipment ordering\n\nThe `Set<WorkoutPlanEquipment>` extension `sortedByDisplayOrder()` preserves the enum declaration order when converting to display strings and tags.\n\n---\n\n## `WorkoutPlanBuilderView`\n\n`WorkoutPlanBuilderView` is the main SwiftUI entry point for creating a plan.\n\nIt uses:\n\n- `@Environment(\\.dismiss)`\n- `@Environment(\\.modelContext)`\n- `@Query(sort: \\Exercise.name)`\n- `@Query(sort: \\ExerciseTemplate.name)`\n\nIt owns:\n\n- `@State private var viewModel = WorkoutPlanBuilderViewModel()`\n- `@State private var pickerRequest: PlanExercisePickerRequest?`\n\n### Layout\n\nThe view is a `NavigationStack` containing a `Form` with:\n\n1. `WorkoutPlanMetadataSection`\n2. validation message, if present\n3. a `Days` section with editable day cards\n4. buttons to add a workout day or rest day\n\n### Toolbar actions\n\n- **Cancel** dismisses the sheet\n- **Save** calls `save()` and is disabled when `viewModel.canSave` is false\n\n### Save behavior\n\n`save()`:\n\n1. calls `viewModel.makePlan()`\n2. inserts the returned `WorkoutPlan` into SwiftData\n3. dismisses the builder\n\nErrors are intentionally swallowed here because the view model already translated them into `validationMessage`.\n\n### Exercise picker flow\n\nTapping **Add Exercise** on a workout day sets `pickerRequest` to the target day ID.\n\nThat presents `WorkoutPlanExercisePickerView`, which returns an `ExerciseSelection` to `viewModel.addExercise(_:to:)`.\n\n---\n\n## Builder subviews\n\n### `WorkoutPlanMetadataSection`\n\nRenders the plan-level fields:\n\n- title\n- summary\n- goal\n- workout type\n- level\n- weeks\n- days per week\n- time per workout\n- equipment selector\n- audience\n\nIt uses `WorkoutPlanMetadataPicker` for the enum-backed pickers.\n\n### `WorkoutPlanMetadataPicker`\n\nA generic picker wrapper for `Hashable & Identifiable` options.\n\nIt is used for the plan metadata fields that are backed by `allCases`.\n\n### `WorkoutPlanEquipmentSelector`\n\nA `DisclosureGroup` containing toggles for every `WorkoutPlanEquipment` case.\n\nThe label shows a summary string from `viewModel.draft.equipmentSummary`, or “No equipment selected” when empty.\n\n### `WorkoutPlanDayEditor`\n\nRenders one editable day card.\n\nIt includes:\n\n- editable day name\n- rest-day toggle\n- exercise list for workout days\n- “Recovery day” label for rest days\n- move up/down buttons\n- delete button\n\nBehavior:\n\n- toggling rest state changes whether exercises are shown\n- workout days show `WorkoutPlanExerciseDraftList`\n- rest days suppress exercise editing\n\n### `WorkoutPlanExerciseDraftList`\n\nIterates over `day.exercises` and creates a `WorkoutPlanExercisePrescriptionRow` for each exercise.\n\nIt uses `bindingForExercise(dayID:exerciseID:_: )` to keep the text fields attached to the correct exercise even if the list changes.\n\n### `WorkoutPlanExercisePrescriptionRow`\n\nRenders one exercise prescription with:\n\n- exercise display name\n- equipment/source label\n- sets field\n- reps field\n- notes field\n- move up/down buttons\n- delete button\n\nThe row displays `exercise.selection.displayName` and `exercise.selection.equipment`, so it works for both catalog and custom selections.\n\n### `WorkoutPlanExercisePickerView`\n\nA modal picker for adding exercises to a day.\n\nIt combines:\n\n- `Exercise` records from SwiftData\n- `ExerciseTemplate` records from SwiftData\n\nIt builds `ExerciseSelection` values from both sources, filters them by search text, and limits the result count:\n\n- up to 120 items when search is empty\n- up to 200 items when searching\n\nSelecting an item calls the provided `onSelect` closure and dismisses the sheet.\n\n### `ExerciseSelectionRow`\n\nA simple row used by the picker.\n\nIt shows:\n\n- display name\n- equipment, if present\n- a “Custom” badge when `selection.isCustomTemplate` is true\n\n---\n\n## Execution details worth knowing\n\n### Day and exercise ordering\n\nThe builder maintains `sortOrder` separately from array position. After structural edits, it renumbers items so the persisted plan has clean 1-based ordering.\n\nThis is important because the service sorts by `sortOrder` before generating the final plan.\n\n### Rest days\n\nRest days are represented explicitly in the draft and persisted plan.\n\nRules:\n\n- rest days can exist without exercises\n- adding an exercise to a rest day converts it into a workout day\n- rest days are persisted with an empty exercise list\n\n### Validation feedback\n\nThe UI does not show inline field-level validation. Instead, save-time validation is surfaced as a single message at the top of the form.\n\nThis keeps the builder simple while still preventing invalid plans from being saved.\n\n---\n\n## Connections to the rest of the app\n\n### SwiftData\n\nThe builder inserts the final `WorkoutPlan` into the model context. That makes the new plan immediately available to:\n\n- `WorkoutPlansView`\n- `WorkoutPlanDetailView`\n- any plan progress or enrollment logic\n\n### `WorkoutPlansView`\n\n`WorkoutPlansView` exposes the builder through the toolbar action:\n\n- **Create Plan** → `router.presentSheet(.workoutPlanBuilder)`\n\nOnce saved, the new plan appears in the plan library because the view queries `WorkoutPlan` records directly.\n\n### `WorkoutPlanDetailView`\n\nThe detail view consumes the persisted `WorkoutPlan` produced by this module.\n\nIt expects normalized day and exercise definitions, including:\n\n- `dayIndex`\n- `displayName`\n- `isRestDay`\n- `sortOrder`\n- exercise prescription fields\n\nThat is why `WorkoutPlanCreationService` generates stable normalized IDs and ordering.\n\n### `WorkoutPlanService`\n\nThis module does not start or end plans itself, but the resulting `WorkoutPlan` is designed to work with `WorkoutPlanService` methods such as:\n\n- `startPlan(_:in:)`\n- `startSelectedWorkoutSession(...)`\n- `skipCurrentScheduledDay(...)`\n- `endPlan(_:in:)`\n\n### Exercise catalog and templates\n\nThe picker merges:\n\n- `Exercise`\n- `ExerciseTemplate`\n\nThis allows plan authors to build plans from both standard catalog exercises and custom templates without needing separate workflows.\n\n---\n\n## Practical extension points\n\n### Adding a new metadata field\n\nTo add a new plan-level field:\n\n1. Add the property to `WorkoutPlanDraft`\n2. Add UI in `WorkoutPlanMetadataSection`\n3. Update `WorkoutPlanCreationService.makePlan(from:)`\n4. Update validation if the field is required\n\n### Adding a new equipment option\n\nAdd a new case to `WorkoutPlanEquipment`.\n\nBecause the UI iterates `WorkoutPlanEquipment.allCases`, the selector updates automatically.\n\n### Changing validation rules\n\nUpdate both:\n\n- `WorkoutPlanDraft.isValid`\n- `WorkoutPlanCreationService.makePlan(from:)`\n\nThe view model’s `canSave` and save-time error mapping should stay aligned with the service.\n\n### Supporting richer exercise prescriptions\n\nThe current draft stores sets, reps, and notes as strings. If the plan model evolves to support more structured prescriptions, the conversion logic should be updated in `normalizedExercises(from:planID:dayIndex:)`.\n\n---\n\n## Summary\n\nThe Workout Plan Creation module is the authoring pipeline for local workout plans. It keeps editing state separate from persisted models, uses stable IDs to support reordering, validates at save time, and produces normalized `WorkoutPlan` data that the rest of the app can consume immediately.\n\nThe key files are:\n\n- `WorkoutPlanBuilderView.swift`\n- `WorkoutPlanBuilderViewModel.swift`\n- `WorkoutPlanCreationService.swift`\n- `WorkoutPlanDraft.swift`\n- the metadata enums in `Features/PlanCreation/`\n\nTogether, they provide a complete plan-building experience from draft to persisted plan.","workout-session-features":"# Workout Session — Features\n\n# Workout Session — Features\n\nThis module contains the non-UI logic that powers workout session logging. It is responsible for:\n\n- creating and deleting exercise logs within an active `WorkoutSession`\n- creating and deleting `SetLog` entries\n- translating draft text inputs into persisted set data\n- applying previous-set values back into the draft UI state\n- resolving rest timer behavior\n- finalizing or deleting a workout session and triggering side effects\n\nThe module is intentionally split into three small pieces:\n\n- `WorkoutSessionCoordinator` — orchestration and persistence\n- `WorkoutSessionInputFormatter` — input normalization and previous-set formatting\n- `WorkoutSessionSideEffects` — injectable side-effect bundles used by the coordinator\n\n## Architecture overview\n\n`WorkoutSessionCoordinator` is the main entry point. It coordinates SwiftData mutations on `WorkoutSession`, `ExerciseLog`, and `SetLog`, while delegating formatting and UI-adjacent behavior to helper types.\n\n```mermaid\nflowchart LR\n View[WorkoutSessionView] --> Coord[WorkoutSessionCoordinator]\n Coord --> Fmt[WorkoutSessionInputFormatter]\n Coord --> Side[WorkoutSessionSideEffects]\n Coord --> SD[SwiftData ModelContext]\n Coord --> Hist[WorkoutHistoryRepository]\n Coord --> Plan[WorkoutPlanService]\n```\n\nThe coordinator is annotated with `@MainActor` and `@Observable`, which makes it suitable for use from SwiftUI views while keeping state mutations on the main thread.\n\n---\n\n## `WorkoutSessionCoordinator`\n\n`WorkoutSessionCoordinator` centralizes the session-editing workflow. It does not own the session data itself; instead, it mutates model objects passed in from the caller and persists changes through `ModelContext`.\n\n### `addExercise(_:to:in:defaultRestTime:)`\n\nCreates a new `ExerciseLog` for the given `Exercise` and appends it to the session.\n\nBehavior:\n\n- assigns the new log an `order` equal to the current number of exercise logs\n- seeds `restTimeOverride` only when the exercise’s default rest time differs from the session default\n- links the log to the session via `log.workoutSession = session`\n- inserts the log into the provided `ModelContext`\n- returns the assigned order\n\nThe rest override logic is handled by the private `seededRestOverride(for:defaultRestTime:)` helper.\n\n### `saveSet(for:in:mode:draftInputs:modelContext:restDuration:minimumWeight:defaultDraftWeight:defaultDraftDurationMinutes:sideEffects:)`\n\nCreates and persists a `SetLog` from the current draft inputs.\n\nKey behavior:\n\n- parses `draftInputs.repsInput` into an integer\n- rejects the save unless:\n - the mode is `.durationOnly`, or\n - reps are greater than zero\n- builds the `SetLog` using `makeSet(...)`\n- attaches the set to the exercise log\n- inserts the set into SwiftData\n- updates `session.lastOpenedExerciseOrder`\n- starts the rest timer through `WorkoutSessionSetLoggingSideEffects`\n- publishes a `WorkoutSessionLiveActivitySnapshot`\n- saves the model context\n\nThis method is the bridge between the draft UI state and persisted workout data.\n\n#### Live activity snapshot\n\nThe snapshot passed to `updateLiveActivity` includes:\n\n- session duration\n- exercise count\n- total set count across all exercise logs\n- rest timer end time\n- whether the timer is active\n\nThat snapshot is constructed immediately after the rest timer is started, so the live activity reflects the newly logged set.\n\n### `deleteSet(_:from:modelContext:)`\n\nDeletes a set and renumbers the remaining sets in the exercise log.\n\nBehavior:\n\n- deletes the target `SetLog` from the model context\n- filters out the deleted set from `log.sets`\n- sorts remaining sets by `setNumber`\n- reassigns `setNumber` values starting at 1\n\nThis keeps set numbering contiguous after deletions.\n\n### `deleteExercise(_:from:in:currentExerciseIndex:modelContext:normalizeSuperset:persistLastOpenedExerciseSelection:)`\n\nDeletes an exercise log and normalizes the surrounding session state.\n\nBehavior:\n\n- determines whether the deleted exercise was the last visible exercise\n- remembers the removed `supersetGroupID`, if any\n- deletes the log from SwiftData\n- renumbers remaining exercise logs by `order`\n- updates `currentExerciseIndex` to keep selection valid\n- clears `session.lastOpenedExerciseOrder` when the session becomes empty\n- normalizes the superset group through the injected closure when needed\n- persists the last-opened exercise selection through the injected closure\n\nThis method is careful to preserve UI selection and ordering invariants after removal.\n\n### `previousSetApplication(_:mode:currentInputs:minimumWeight:defaultDraftWeight:defaultDraftDurationMinutes:primaryInputField:)`\n\nDelegates to `WorkoutSessionInputFormatter.previousSetApplication(...)` and returns a `WorkoutSessionPreviousSetApplication`.\n\nThis is used by the UI when the user taps a previous set to prefill the draft inputs.\n\n### `resolvedRestDuration(for:defaultRestTime:)`\n\nResolves the effective rest duration for an exercise log using precedence rules:\n\n1. `log.supersetRestTimeOverride`, if present and greater than zero\n2. `log.restTimeOverride`, if present and greater than zero\n3. the provided `defaultRestTime`\n\nThis precedence is important because superset-specific rest timing overrides exercise-level timing.\n\n### `finishWorkout(session:exercises:modelContext:historyRepository:planService:sideEffects:)`\n\nFinalizes an active workout session.\n\nBehavior:\n\n- sets `session.endTime` to `Date()`\n- marks the session inactive\n- populates session metadata via `WorkoutHistoryRepository`\n- completes the plan day if needed via `WorkoutPlanService`\n- saves the model context\n- refreshes profile stats\n- rebuilds achievements and captures the resulting `AchievementEvent`s\n- queues home celebrations\n- schedules achievement notifications\n- refreshes widgets\n- dismisses the workout session UI\n\nThis method is the main completion path for a successful workout.\n\n### `deleteWorkout(session:modelContext:sideEffects:)`\n\nDeletes the entire workout session.\n\nBehavior:\n\n- determines whether the workout was already completed\n- deletes the session from SwiftData\n- saves the model context\n- refreshes profile stats only if the workout had been completed\n- refreshes widgets\n- dismisses the UI\n\nThe conditional stats refresh avoids unnecessary work for in-progress sessions that were never completed.\n\n### Private helpers\n\n#### `makeSet(mode:setNumber:reps:draftInputs:minimumWeight:defaultDraftWeight:defaultDraftDurationMinutes:)`\n\nBuilds a `SetLog` appropriate for the current logging mode.\n\nMode mapping:\n\n- `.durationAndReps` → cardio set with duration and reps\n- `.durationOnly` → cardio set with duration only\n- `.weightAndReps` → strength set with weight and reps\n- `.repsOnly` → strength set with reps only\n\nImportant details:\n\n- duration values are converted from minutes to seconds using `WorkoutSessionInputFormatter.durationMinutesAsTimeInterval(...)`\n- weight is clamped to at least `minimumWeight`\n- reps are passed through as an integer\n\n#### `seededRestOverride(for:defaultRestTime:)`\n\nReturns an exercise-specific rest override only when:\n\n- `exercise.defaultRestTime > 0`\n- `exercise.defaultRestTime != defaultRestTime`\n\nOtherwise it returns `nil`, allowing the session default to apply.\n\n---\n\n## `WorkoutSessionInputFormatter`\n\nThis type contains the input normalization and previous-set formatting logic used by the workout session UI.\n\nIt is intentionally stateless and static, which makes it easy to reuse from views and tests.\n\n### Input field and draft models\n\n#### `WorkoutSessionInputField`\n\nRepresents the editable field currently being targeted in the draft UI:\n\n- `.weight`\n- `.duration`\n- `.reps`\n\n#### `WorkoutSessionDraftInputs`\n\nA simple value type holding the current text values for:\n\n- `weightInput`\n- `durationInput`\n- `repsInput`\n\n#### `WorkoutSessionPreviousSetApplication`\n\nRepresents the result of applying a previous set to the draft UI:\n\n- updated `draftInputs`\n- `editingField` to focus next\n- `hiddenShadowSetNumber` for UI bookkeeping\n- `highlightedFields` to emphasize the fields relevant to the current logging mode\n\n### Formatting helpers\n\n#### `formatWeight(_:)`\n\nFormats a `Double` weight value with up to one fractional digit.\n\nThis is used both for display and for sanitizing numeric input.\n\n#### `formatDurationMinutes(_:defaultDraftDurationMinutes:)`\n\nConverts a duration value into a minute string.\n\nBehavior:\n\n- uses the provided duration if available\n- otherwise falls back to `defaultDraftDurationMinutes`\n- clamps the result to at least `1`\n\nThe function works in seconds internally, then converts to whole minutes for display.\n\n#### `durationMinutesAsTimeInterval(_:defaultDraftDurationMinutes:)`\n\nConverts a raw minute string into a `TimeInterval` in seconds.\n\nBehavior:\n\n- parses the string as an integer\n- falls back to `defaultDraftDurationMinutes` if parsing fails\n- clamps to at least `1`\n- returns minutes × 60\n\nThis is the inverse of `formatDurationMinutes(...)` for the duration input field.\n\n### Sanitization helpers\n\n#### `sanitizedWeightInput(_:minimumWeight:defaultDraftWeight:)`\n\nNormalizes a raw weight string by:\n\n- parsing it as `Double`\n- falling back to `defaultDraftWeight` if parsing fails\n- clamping to `minimumWeight`\n- formatting the result with `formatWeight(_:)`\n\n#### `sanitizedDurationInput(_:defaultDraftDurationMinutes:)`\n\nNormalizes a raw duration string to a positive integer minute string.\n\n#### `sanitizedRepsInput(_:)`\n\nNormalizes a raw reps string to a non-negative integer string.\n\nIt accepts decimal-looking input by parsing through `Double` first, then truncating to `Int`.\n\n### Keypad input handling\n\n#### `applyInput(_:to:allowsDecimal:)`\n\nApplies a single keypad key to an existing string value.\n\nSupported keys:\n\n- `\"⌫\"` — deletes the last character, returning `\"0\"` if the result would be empty\n- `\".\"` — appends a decimal point if decimals are allowed and one is not already present\n- numeric characters — appended unless the current value is already at the six-character limit\n\nBehavior details:\n\n- non-numeric keys are ignored\n- a current value of `\"0\"` is replaced by the new digit\n- the function enforces a simple maximum length of 6 characters\n\nThis helper is used by the workout session keypad flow in the view layer.\n\n### Previous-set application\n\n#### `previousSetApplication(from:mode:currentInputs:minimumWeight:defaultDraftWeight:defaultDraftDurationMinutes:primaryInputField:)`\n\nBuilds a `WorkoutSessionPreviousSetApplication` from a `SetLog`.\n\nBehavior depends on the current `ExerciseLoggingMode`:\n\n- `.durationAndReps` and `.durationOnly`\n - populate `durationInput` from `set.duration`\n- `.weightAndReps`\n - populate `weightInput` from `set.weight`, clamped to `minimumWeight`\n- all modes except `.durationOnly`\n - populate `repsInput` from `set.reps`, defaulting to `10` when absent\n - for `.durationAndReps`, reps are only copied when the source set actually has reps\n\nThe returned value also includes:\n\n- `editingField` set to the provided `primaryInputField`\n- `hiddenShadowSetNumber` set to `set.setNumber`\n- `highlightedFields` derived from `highlightedFields(for:)`\n\nThis is the core logic behind the “apply previous set” interaction in the UI.\n\n#### `highlightedFields(for:)`\n\nReturns the set of fields that should be emphasized for a given logging mode:\n\n- `.weightAndReps` → `[.weight, .reps]`\n- `.durationAndReps` → `[.duration, .reps]`\n- `.repsOnly` → `[.reps]`\n- `.durationOnly` → `[.duration]`\n\n---\n\n## `WorkoutSessionSideEffects`\n\nThis file defines small value types that bundle closures for side effects. They make the coordinator testable by allowing callers to inject no-op or mocked behavior.\n\n### `WorkoutSessionRestTimerSnapshot`\n\nRepresents the state of the rest timer after starting or updating it.\n\nProperties:\n\n- `endTime: Date?`\n- `isActive: Bool`\n\n`WorkoutSessionRestTimerSnapshot.inactive` is a convenience constant for the disabled state.\n\n### `WorkoutSessionLiveActivitySnapshot`\n\nRepresents the data pushed to the workout live activity.\n\nProperties:\n\n- `duration`\n- `exerciseCount`\n- `setCount`\n- `restTimerEndTime`\n- `isTimerActive`\n\nThis is the payload created by `saveSet(...)`.\n\n### `WorkoutSessionSetLoggingSideEffects`\n\nUsed by `saveSet(...)` to isolate timer and live activity behavior.\n\nClosures:\n\n- `startRestTimer: (TimeInterval) -> WorkoutSessionRestTimerSnapshot`\n- `updateLiveActivity: (WorkoutSessionLiveActivitySnapshot) -> Void`\n\n`WorkoutSessionSetLoggingSideEffects.none` provides a no-op implementation for tests or call sites that do not need side effects.\n\n### `WorkoutSessionFinishSideEffects`\n\nUsed by `finishWorkout(...)` to isolate completion-time behavior.\n\nClosures:\n\n- `refreshProfileStats`\n- `rebuildAchievements`\n- `queueHomeCelebrations`\n- `scheduleAchievementNotifications`\n- `refreshWidgets`\n- `dismiss`\n\n`WorkoutSessionFinishSideEffects.none` is the no-op default.\n\n### `WorkoutSessionDeleteSideEffects`\n\nUsed by `deleteWorkout(...)` to isolate deletion-time behavior.\n\nClosures:\n\n- `refreshProfileStats`\n- `refreshWidgets`\n- `dismiss`\n\n`WorkoutSessionDeleteSideEffects.none` is the no-op default.\n\n---\n\n## Execution flow in the UI\n\nThe view layer in `WorkoutSessionView.swift` uses this module as the business-logic boundary.\n\n### Logging a set\n\n1. The user edits draft inputs in the UI.\n2. The view calls `WorkoutSessionCoordinator.saveSet(...)`.\n3. The coordinator validates reps and builds a `SetLog`.\n4. The set is inserted into SwiftData.\n5. Rest timer and live activity side effects are triggered.\n\n### Applying a previous set\n\n1. The user taps a previous set row.\n2. The view calls `WorkoutSessionCoordinator.previousSetApplication(...)`.\n3. The coordinator delegates to `WorkoutSessionInputFormatter.previousSetApplication(...)`.\n4. The formatter returns updated draft inputs and field-highlighting metadata.\n5. The view uses that result to prefill and focus the editor.\n\n### Finishing or deleting a workout\n\n1. The view calls `finishWorkout(...)` or `deleteWorkout(...)`.\n2. The coordinator updates the session model and persists it.\n3. Injected side effects refresh stats, widgets, achievements, notifications, and dismiss the screen as appropriate.\n\n---\n\n## Design notes\n\n### Why the coordinator is `@MainActor`\n\nThe coordinator mutates SwiftData model objects and is intended to be called from SwiftUI interactions. Keeping it on the main actor avoids cross-thread model updates and matches the UI-driven nature of the feature.\n\n### Why side effects are injected\n\nThe coordinator performs actions that are not pure model mutations:\n\n- starting timers\n- updating live activities\n- refreshing widgets\n- rebuilding achievements\n- dismissing the screen\n\nBundling these into `WorkoutSessionSetLoggingSideEffects`, `WorkoutSessionFinishSideEffects`, and `WorkoutSessionDeleteSideEffects` keeps the coordinator testable and prevents direct coupling to app-wide services.\n\n### Why formatting is separate\n\n`WorkoutSessionInputFormatter` isolates string parsing and display rules from persistence logic. This keeps the coordinator focused on session state changes and makes input behavior reusable from both the view layer and tests.\n\n---\n\n## Related types and dependencies\n\nThis module works with several model and service types defined elsewhere in the app:\n\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `Exercise`\n- `ExerciseLoggingMode`\n- `WorkoutHistoryRepository`\n- `WorkoutPlanService`\n- `AchievementEvent`\n- `ModelContext`\n\nIt also integrates with the workout session view layer, which is responsible for collecting user input and invoking the coordinator methods at the right time.","workout-session-views":"# Workout Session — Views\n\n# Workout Session — Views\n\nThis module contains the SwiftUI screens that drive the workout flow:\n\n- `WorkoutSessionView`: the main in-session workout editor and logger\n- `WorkoutSessionNotesView`: metadata editing for a completed or active session\n- `WorkoutStartSheet`: the entry point for starting a workout from recommendations, plans, or templates\n\nThese views are tightly coupled to the app’s SwiftData models and workout-session coordination layer. They are responsible for rendering state, collecting user input, and delegating persistence and business logic to supporting services such as `WorkoutSessionCoordinator`, `WorkoutPlanService`, `WorkoutHistoryRepository`, `RestTimerManager`, and `AchievementService`.\n\n---\n\n## Module responsibilities\n\nThe views in this module cover three distinct phases of the workout lifecycle:\n\n1. **Start a workout**\n - Show recommendations, plan-based options, and quick templates.\n - Create or resume a `WorkoutSession`.\n\n2. **Run a workout session**\n - Display exercises and sets in a paged workout UI.\n - Log sets, edit values, manage rest timers, and handle supersets.\n - Support exercise insertion, deletion, and navigation between exercises.\n\n3. **Annotate a workout**\n - Edit gym location, tags, and freeform notes for a session.\n\nThe module is intentionally UI-heavy, but it still performs some session orchestration directly when needed, especially around navigation, draft input state, and side effects that must happen in response to user actions.\n\n---\n\n## High-level architecture\n\n```mermaid\nflowchart TD\n A[WorkoutStartSheet] --> B[WorkoutSessionView]\n B --> C[WorkoutSessionNotesView]\n\n B --> D[WorkoutSessionCoordinator]\n B --> E[RestTimerManager]\n B --> F[WorkoutHistoryRepository]\n B --> G[AchievementService]\n B --> H[WidgetDataRefresher]\n B --> I[AppRouter]\n\n A --> J[WorkoutPlanService]\n A --> K[UnifiedWorkoutRecommendationService]\n A --> L[WorkoutStartCarouselCardView]\n```\n\n`WorkoutStartSheet` creates or resumes sessions. `WorkoutSessionView` owns the interactive workout experience and delegates persistence-heavy operations to `WorkoutSessionCoordinator`. `WorkoutSessionNotesView` edits session metadata. Navigation to exercise detail screens is handled through `AppRouter`.\n\n---\n\n## `WorkoutSessionView`\n\n`WorkoutSessionView` is the core screen for logging a workout session. It renders the current exercise, set list, draft input controls, rest timer state, and exercise picker.\n\n### Core state and dependencies\n\nThe view uses a mix of environment values, SwiftData queries, app storage, and local state:\n\n- `@Environment(\\.modelContext)` for persistence\n- `@Environment(\\.dismiss)` for fallback dismissal\n- `@Environment(AppRouter.self)` for navigation\n- `@Query private var exercises` to resolve exercise metadata\n- `@Query private var profiles` to update profile stats\n- `@Query(filter: !$0.isActive, sort: \\.date, order: .reverse)` for completed sessions\n- `@AppStorage(\"defaultRestTime\")` and `@AppStorage(\"hapticEnabled\")` for user preferences\n- `@Bindable var session` for live editing of the current `WorkoutSession`\n\nIt also maintains a substantial amount of local UI state, including:\n\n- current exercise index in the pager\n- keypad popup visibility and target\n- draft inputs for weight, duration, and reps\n- search text and focus state for exercise picking\n- rest timer manager\n- hidden shadow-set tracking\n- highlighted input fields after applying a previous set\n\n### Session modes\n\nThe view adapts to whether the session is active or being edited:\n\n- `isEditMode` is derived from `!session.isActive`\n- active sessions show elapsed time and logging controls\n- edit mode shows the session date and destructive actions for deleting saved workouts\n\n### Exercise paging model\n\nThe workout content is organized as a `TabView` with three conceptual pages:\n\n- the empty-workout placeholder when there are no exercises\n- one page per `ExerciseLog`\n- an add-exercise page at `addExerciseIndex`\n\nThe current page is tracked by `currentExerciseIndex`. The view uses this to determine:\n\n- whether the user is adding an exercise\n- which exercise log is currently active\n- whether the bottom logging controls should show a rest timer or a save button\n\n### Exercise and set rendering\n\nFor each `ExerciseLog`, the view renders:\n\n- the exercise name\n- optional superset label\n- a menu for superset linking and reordering\n- a “How to” button that navigates to exercise detail\n- planned prescription text, muscle group, and equipment pills\n- a previous-workout “shadow set” section\n- the current set list\n\nThe set rows are editable in place. Tapping the primary metric or summary metric opens the keypad popup for that set.\n\n### Draft logging controls\n\nThe bottom card changes based on the exercise’s `ExerciseLoggingMode`:\n\n- `weightAndReps`\n- `durationAndReps`\n- `repsOnly`\n- `durationOnly`\n\nThe view derives the primary input field from the logging mode and uses compact metric controls to adjust values with plus/minus buttons or open the keypad popup for direct entry.\n\nWhen no rest timer is active, the bottom card shows:\n\n- compact metric inputs\n- optional weight-step picker\n- a `LogSetButton`\n\nWhen a rest timer is active, it shows `RestTimerView` instead.\n\n### Keypad popup\n\nThe keypad overlay is used for both draft inputs and saved set editing.\n\nIt supports targets such as:\n\n- `draftWeight`\n- `draftDuration`\n- `draftReps`\n- `savedSetWeight(PersistentIdentifier)`\n- `savedSetDuration(PersistentIdentifier)`\n- `savedSetReps(PersistentIdentifier)`\n\nThe popup title and available fields are derived from the current target and the exercise’s logging mode. Input is processed through `WorkoutSessionInputFormatter`.\n\n### Previous workout “shadow sets”\n\nA notable feature of this view is the shadow-set system:\n\n- `previousWorkoutReference(for:)` finds the most recent completed session containing the same exercise\n- `shadowSet(for:)` returns the set from that prior workout that corresponds to the next set number\n- `previousSetRow(set:currentSetNumber:log:)` renders a tappable ghost set\n- `applyPreviousSet(_:for:)` copies values from the shadow set into the current draft inputs\n\nThis gives the user a fast way to repeat prior performance.\n\n### Supersets\n\nThe view supports grouping exercises into supersets using:\n\n- `linkExercise(_:direction:)`\n- `moveExerciseWithinSuperset(_:direction:)`\n- `removeExerciseFromSuperset(_:)`\n- `normalizeSuperset(groupID:)`\n\nThe `supersetMenu(for:)` exposes these actions conditionally based on the current exercise’s position and group membership.\n\n### Rest timer integration\n\n`RestTimerManager` is created on appear and used to drive the rest timer UI and live activity updates.\n\nThe save flow passes a `startRestTimer` closure into `WorkoutSessionCoordinator.saveSet(...)`, which starts the timer and returns a snapshot used to update the live activity.\n\n### Lifecycle behavior\n\nImportant lifecycle hooks:\n\n- `onAppear`\n - creates `RestTimerManager`\n - disables the idle timer\n - schedules visible exercise state synchronization\n- `onChange(of: currentExerciseIndex)`\n - clears keypad/highlight state\n - seeds inputs for the new exercise or resets drafts\n - persists the last opened exercise selection when visible\n- `onChange(of: exerciseLogs.count)`\n - resyncs visible exercise state\n- `onChange(of: isSessionVisible)`\n - schedules sync when shown\n - persists selection when hidden\n- `onDisappear`\n - cancels sync task\n - populates session metadata\n - saves the model context\n - rebuilds achievements for edited sessions\n - refreshes widgets\n- `onChange(of: router.path)`\n - returns to the exercise list after navigating to exercise detail\n\n### Persistence and side effects\n\nThe view delegates most mutation-heavy operations to `WorkoutSessionCoordinator`, but it still performs some direct persistence:\n\n- saving edited set values from the keypad\n- updating superset metadata\n- persisting `lastOpenedExerciseOrder`\n- saving session notes and metadata on disappear\n- updating profile stats after finishing or deleting a workout\n\n---\n\n## `WorkoutSessionView` key helpers\n\n### Input and formatting helpers\n\nThese methods centralize draft input behavior:\n\n- `adjustWeight(by:)`\n- `adjustDuration(by:)`\n- `adjustReps(by:)`\n- `applyInput(_:to:allowsDecimal:)`\n- `sanitizedWeightInput(_:)`\n- `sanitizedDurationInput(_:)`\n- `formatWeight(_:)`\n- `formatDurationMinutes(_:)`\n- `durationMinutesAsTimeInterval(_:)`\n\nThey all route through `WorkoutSessionInputFormatter` where appropriate.\n\n### Draft seeding\n\n`seedInputs(from:)` initializes the draft controls from:\n\n1. the current exercise’s last logged set\n2. the previous completed workout’s matching exercise\n3. default values if no prior data exists\n\nThis is what makes the workout feel continuous across sessions.\n\n### Exercise selection persistence\n\nThe view remembers the last opened exercise using:\n\n- `persistLastOpenedExerciseSelection()`\n- `restoreLastOpenedExerciseSelection()`\n- `syncVisibleExerciseState()`\n- `scheduleVisibleExerciseStateSync()`\n\nThis prevents the user from losing their place when navigating away and back.\n\n### Workout completion and deletion\n\nThe primary actions are:\n\n- `finishWorkout()`\n- `deleteWorkout()`\n- `deleteExercise(_:)`\n- `deleteSet(_:from:)`\n\nThese are routed through `WorkoutSessionCoordinator` and trigger downstream effects such as:\n\n- profile stat refresh\n- achievement rebuilds\n- widget refreshes\n- dismissal\n\n### Previous workout lookup\n\nThe shadow-set feature depends on:\n\n- `previousWorkoutReference(for:)`\n- `matchesExercise(_:currentLog:)`\n- `shadowSet(for:)`\n\nThe lookup prefers matching `exerciseId` when available, and falls back to `exerciseName`.\n\n---\n\n## `WorkoutSessionView` execution flow\n\n### Logging a set\n\n1. User adjusts draft inputs or opens the keypad.\n2. User taps `Log Set`.\n3. `saveSet(for:)` calls `WorkoutSessionCoordinator.saveSet(...)`.\n4. The coordinator persists the set and starts the rest timer via the provided closure.\n5. Live activity state is updated.\n6. Haptics fire if enabled.\n7. The shadow-set cache for that exercise is cleared.\n\n### Applying a previous set\n\n1. User taps a shadow set.\n2. `applyPreviousSet(_:for:)` asks the coordinator for a draft-input application.\n3. Draft inputs, highlighted fields, and the active editing field are updated.\n4. Optional haptic feedback is triggered.\n5. Highlighting is cleared after a short delay.\n\n### Finishing or deleting a workout\n\n1. User taps the primary toolbar action.\n2. A confirmation alert is shown.\n3. `finishWorkout()` or `deleteWorkout()` is called.\n4. The coordinator performs the mutation.\n5. Profile stats, achievements, and widgets are refreshed as needed.\n6. The session is dismissed.\n\n---\n\n## `WorkoutSessionNotesView`\n\n`WorkoutSessionNotesView` is a lightweight editor for session metadata.\n\n### Purpose\n\nIt lets the user edit:\n\n- `session.gymLocation`\n- `session.workoutTags`\n- `session.sessionNotes`\n\nIt also displays contextual information about the workout, such as:\n\n- source workout plan day title\n- routine title from `WorkoutHistoryRepository`\n- session date and time\n\n### State handling\n\nThe view uses:\n\n- `@Bindable var session` for direct model editing\n- `@State private var workoutTagsInput` as a comma-separated text buffer\n\nOn appear, it seeds the text field from `session.workoutTags`. On disappear, it parses the comma-separated input back into an array and writes it to the session.\n\n### Tag parsing\n\n`parseWorkoutTags(_:)`:\n\n- splits on commas\n- trims whitespace and newlines\n- removes empty entries\n\nThis keeps the stored tags normalized while allowing freeform editing.\n\n---\n\n## `WorkoutStartSheet`\n\n`WorkoutStartSheet` is the entry screen for starting a workout. It presents recommendations, plan-based actions, and quick templates.\n\n### Core dependencies\n\nThe view reads from SwiftData and app preferences:\n\n- `@Query(sort: \\WorkoutTemplate.createdAt, order: .reverse)` for templates\n- `@Query(sort: \\WorkoutSession.date, order: .reverse)` for recent sessions\n- `@Query(sort: \\WorkoutPlan.title)` for plans\n- `@Query private var userWorkoutPlans`\n- `@Query private var exercises`\n- `@AppStorage` values for calorie-goal preferences\n\nIt also uses:\n\n- `WorkoutPlanService`\n- `UnifiedWorkoutRecommendationService`\n- `AppRouter`\n\n### Derived recommendation state\n\nThe view computes a `recommendationRefreshSignature` from:\n\n- workout plan IDs\n- user workout plan IDs and progress signatures\n- recent session IDs and progress signatures\n- exercise count\n- calorie-goal preferences\n\nThat signature drives a `.task(id:)` refresh so the recommendation UI stays in sync with underlying data changes.\n\n### Main UI sections\n\nThe screen is split into three areas:\n\n1. **Recommendation area**\n - shows a carousel of `WorkoutStartCard`\n - falls back to an empty state when no cards are available\n\n2. **Template strip**\n - shows quick-start workout templates horizontally\n\n3. **Action bar**\n - provides cancel and navigation actions\n\n### Plan-aware actions\n\nFor cards that represent a plan context, `planDayActions(for:)` builds a `WorkoutStartPlanDayActionsView` using:\n\n- `planSnapshot(for:)`\n- `dayStateLabels(for:snapshot:)`\n- `dayStateTint(for:)`\n- `startSelectedPlanDay(_:snapshot:)`\n- `skipCurrentPlanDay(_:)`\n\nThis is where the view bridges recommendation UI to plan progression logic.\n\n### Starting workouts\n\nThe view supports multiple entry paths:\n\n- start from a recommended plan\n- start from a specific plan day\n- start from a template\n- browse all workout plans\n\nThe actual session creation logic is delegated to `WorkoutPlanService` and related helpers.\n\n---\n\n## Data flow between views\n\n```mermaid\nflowchart LR\n A[WorkoutStartSheet] -->|creates session| B[WorkoutSessionView]\n B -->|notes button| C[WorkoutSessionNotesView]\n B -->|exercise guide| D[AppRouter.exerciseDetail]\n B -->|save/delete/finish| E[WorkoutSessionCoordinator]\n C -->|writes metadata| B\n```\n\n`WorkoutStartSheet` creates the session entry point. `WorkoutSessionView` owns the live workout experience and can navigate to notes or exercise detail. `WorkoutSessionNotesView` edits session metadata directly on the same `WorkoutSession` model.\n\n---\n\n## Integration with the rest of the codebase\n\n### SwiftData models\n\nThese views operate on the core workout models:\n\n- `WorkoutSession`\n- `ExerciseLog`\n- `SetLog`\n- `Exercise`\n- `UserProfile`\n- `WorkoutTemplate`\n- `WorkoutPlan`\n- `UserWorkoutPlan`\n\nThe views assume these models are already configured in the model container and that relationships are available through SwiftData.\n\n### Coordination and services\n\nThe module relies on several non-view components:\n\n- `WorkoutSessionCoordinator`\n - centralizes set logging, exercise deletion, workout completion, and workout deletion\n- `WorkoutPlanService`\n - starts plan-based workouts and computes plan progress snapshots\n- `WorkoutHistoryRepository`\n - provides historical workout metadata and summaries\n- `RestTimerManager`\n - manages rest timer state for the active session\n- `AchievementService`\n - rebuilds achievements and schedules notifications\n- `WidgetDataRefresher`\n - refreshes widget data after session changes\n- `LiveActivityManager`\n - receives live workout activity updates\n- `WorkoutSessionInputFormatter`\n - sanitizes and formats numeric input\n- `AppRouter`\n - handles navigation to exercise detail screens\n\n### Shared UI components\n\nThe module also composes reusable UI pieces from elsewhere in the app, including:\n\n- `SearchBar`\n- `RestTimerView`\n- `customToolBar`\n- `keypadButtonStyle`\n\n---\n\n## Notable implementation details\n\n### `WorkoutSessionView` uses a lot of local state\n\nThis is intentional. The screen needs to manage transient UI state that should not be persisted immediately, such as:\n\n- keypad editing target\n- highlighted fields\n- search focus\n- current pager index\n- temporary draft values\n\n### Saved-set editing is supported inline\n\nThe keypad popup can edit existing sets, not just draft inputs. When editing a saved set, `applyKeypadValue()` writes directly to the `SetLog` and updates its type fields as needed.\n\n### Superset normalization is defensive\n\n`normalizeSuperset(groupID:)` collapses invalid groups:\n\n- if fewer than two exercises remain in a group, the group is removed\n- otherwise, orders are renumbered sequentially\n\nThis prevents stale superset metadata from lingering after deletions or reordering.\n\n### Session metadata is refreshed on disappear\n\n`onDisappear` calls `historyRepository.populateSessionMetadata(session, exercises:)` before saving. This ensures the session has the derived metadata needed for history and stats views.\n\n---\n\n## Preview setup\n\nThe `#Preview` creates an in-memory `ModelContainer` with the relevant models and inserts a sample `WorkoutSession` containing one `ExerciseLog`. This is useful for validating the workout UI without requiring app state or persistent storage.\n\n---\n\n## Extending this module\n\nWhen adding new behavior, prefer these patterns:\n\n- **Use `WorkoutSessionCoordinator` for mutations** that affect sets, exercises, or session lifecycle.\n- **Keep transient UI state in `@State`** rather than on the model.\n- **Route numeric input through `WorkoutSessionInputFormatter`** to preserve consistent validation.\n- **Refresh derived state on lifecycle changes** if the UI depends on external data such as recent sessions or plan progress.\n- **Preserve selection state** when navigating away from the workout screen, especially for the add-exercise flow.\n\nIf you add new workout logging modes or set-editing behaviors, update all of the following together:\n\n- `loggingMode(for:)`\n- `primaryInputField(for:)`\n- `showsRepsInput(for:)`\n- `calculatorInputCard(log:)`\n- keypad popup field availability and titles\n- draft seeding logic in `seedInputs(from:)`\n\n---\n\n## Summary of key functions and views\n\n### `WorkoutSessionView`\n- `saveSet(for:)`\n- `finishWorkout()`\n- `deleteWorkout()`\n- `deleteExercise(_:)`\n- `deleteSet(_:from:)`\n- `openExercisePicker()`\n- `openExerciseGuide(_:)`\n- `applyPreviousSet(_:for:)`\n- `seedInputs(from:)`\n- `syncVisibleExerciseState()`\n- `normalizeSuperset(groupID:)`\n\n### `WorkoutSessionNotesView`\n- `parseWorkoutTags(_:)`\n\n### `WorkoutStartSheet`\n- recommendation and plan-card rendering\n- plan-day action wiring\n- template-based workout start flow\n\nThis module is the main user-facing surface for workout execution, and it sits at the center of the app’s workout data flow: starting sessions, logging sets, editing metadata, and finishing workouts all converge here.","workout-session":"# Workout Session\n\n# Workout Session\n\nThe **Workout Session** module group powers the end-to-end workout logging flow: starting a session, editing exercises and sets, applying draft input, managing rest timing, and finishing or deleting a workout. It is split into a UI layer and a supporting feature layer that together keep session state, persistence, and side effects coordinated.\n\n## Sub-modules\n\n- [Workout Session — Views](workout-session-views.md) — SwiftUI entry points for starting and running a workout session.\n- [Workout Session — Features](workout-session-features.md) — non-UI logic for set logging, input formatting, rest timer handling, and session completion/deletion.\n\n## How the pieces fit together\n\nThe views own the user experience and delegate business logic to the feature layer and related services:\n\n- `WorkoutStartSheet` starts a workout from recommendations, plans, or templates, typically through `WorkoutPlanService`.\n- `WorkoutSessionView` is the main in-session editor. It renders exercise rows, handles keypad input, and calls into `WorkoutSessionCoordinator`-backed logic for creating and updating logs.\n- `WorkoutSessionNotesView` edits session metadata without taking over the logging flow.\n\nThe feature layer keeps the session mechanics consistent across those screens:\n\n- `WorkoutSessionCoordinator` handles orchestration and persistence for exercise logs and `SetLog` entries.\n- `WorkoutSessionInputFormatter` normalizes draft text input and applies previous-set values back into `WorkoutSessionDraftInputs`.\n- `WorkoutSessionSideEffects` packages injectable actions used when a session is finished or deleted, including rest timer and cleanup behavior.\n\n## Key workflows\n\n### Starting a workout\n`WorkoutStartSheet` routes the user into an active session from a recommendation, plan day, or template. This flow connects the start UI to `WorkoutPlanService` and the underlying workout template/plan data.\n\n### Logging sets during a session\nInside `WorkoutSessionView`, keypad and calculator interactions are translated into draft values, then persisted as set logs. Previous-set application uses `WorkoutSessionDraftInputs` and the formatter helpers so the UI can quickly reuse prior weight, reps, or duration values.\n\n### Rest timer and session state\nSaving a set can produce a `WorkoutSessionRestTimerSnapshot`, which keeps rest behavior aligned with the current session state. The view layer also syncs visible exercise state so the UI stays consistent as logs change.\n\n### Finishing or deleting a workout\nEnding a session triggers side effects through `WorkoutSessionSideEffects`, while deleting a workout cascades into widget refresh and history recalculation. These flows ensure downstream summaries such as streaks and session volume stay up to date after session changes."};
var TREE = [{"name":"App Entry & Root Navigation","slug":"app-entry-root-navigation","files":["BigRoosterWorkouts/BigRoosterWorkoutsApp.swift","BigRoosterWorkouts/ContentView.swift"]},{"name":"Core Domain Models","slug":"core-domain-models","files":["BigRoosterWorkouts/Models/AchievementEvent.swift","BigRoosterWorkouts/Models/AppTheme.swift","BigRoosterWorkouts/Models/BackgroundConfiguration.swift","BigRoosterWorkouts/Models/Exercise.swift","BigRoosterWorkouts/Models/ExerciseLog.swift","BigRoosterWorkouts/Models/ExerciseSearchIndex.swift","BigRoosterWorkouts/Models/ExerciseTemplate.swift","BigRoosterWorkouts/Models/FilterState.swift","BigRoosterWorkouts/Models/HeatmapWidgetConfiguration.swift","BigRoosterWorkouts/Models/HomeBackgroundConfiguration.swift","BigRoosterWorkouts/Models/MuscleAnatomy.swift","BigRoosterWorkouts/Models/PendingAchievementCelebration.swift","BigRoosterWorkouts/Models/SetLog.swift","BigRoosterWorkouts/Models/ThemeColorSet.swift","BigRoosterWorkouts/Models/UserProfile.swift","BigRoosterWorkouts/Models/WidgetDataModels.swift","BigRoosterWorkouts/Models/WorkoutActivityAttributes.swift","BigRoosterWorkouts/Models/WorkoutContentOwnership.swift","BigRoosterWorkouts/Models/WorkoutPlan.swift","BigRoosterWorkouts/Models/WorkoutSession.swift","BigRoosterWorkouts/Models/WorkoutTemplate.swift"]},{"name":"Exercise Creation","slug":"exercise-creation","files":["BigRoosterWorkouts/Features/ExerciseCreation/ExerciseCreationService.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseSelection.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateBuilderViewModel.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateDraft.swift","BigRoosterWorkouts/Views/ExerciseTemplateBuilderView.swift","BigRoosterWorkouts/Views/CustomExercisesView.swift","BigRoosterWorkouts/Views/ExerciseGuideView.swift","BigRoosterWorkouts/Views/TemplateListView.swift"]},{"name":"Workout Plan Creation","slug":"workout-plan-creation","files":["BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanAudience.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderView.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderViewModel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanCreationService.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDaysPerWeek.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDraft.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDurationWeeks.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanEquipment.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanGoal.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanLevel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanTimeRange.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanType.swift","BigRoosterWorkouts/Views/WorkoutPlansView.swift","BigRoosterWorkouts/Views/WorkoutPlanDetailView.swift","BigRoosterWorkouts/Views/WorkoutDetailView.swift","BigRoosterWorkouts/Views/WorkoutTemplateBuilderView.swift"]},{"name":"Workout Session","slug":"workout-session","files":[],"children":[{"name":"Workout Session — Features","slug":"workout-session-features","files":["BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionCoordinator.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionInputFormatter.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionSideEffects.swift"]},{"name":"Workout Session — Views","slug":"workout-session-views","files":["BigRoosterWorkouts/Views/WorkoutSessionView.swift","BigRoosterWorkouts/Views/WorkoutSessionNotesView.swift","BigRoosterWorkouts/Views/WorkoutStartSheet.swift"]}]},{"name":"Workout History & Statistics","slug":"workout-history-statistics","files":["BigRoosterWorkouts/Utilities/TrainingStatsCalculator.swift","BigRoosterWorkouts/Utilities/WorkoutHistoryRepository.swift","BigRoosterWorkouts/Views/WorkoutHistoryView.swift","BigRoosterWorkouts/Views/DetailedStatsView.swift","BigRoosterWorkouts/Views/RecordsHistoryView.swift"]},{"name":"Recommendations & Plan Scheduling","slug":"recommendations-plan-scheduling","files":["BigRoosterWorkouts/Utilities/UnifiedWorkoutRecommendationService.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendation.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendationEngine.swift","BigRoosterWorkouts/Utilities/WorkoutPlanService.swift","BigRoosterWorkouts/Utilities/WorkoutRecommendation.swift"]},{"name":"Achievements & Badges","slug":"achievements-badges","files":["BigRoosterWorkouts/Utilities/AchievementService.swift","BigRoosterWorkouts/Views/Components/AchievementBadgeViews.swift"]},{"name":"Home Dashboard","slug":"home-dashboard","files":["BigRoosterWorkouts/Views/HomeView.swift","BigRoosterWorkouts/Views/HomeBackgroundEditorView.swift","BigRoosterWorkouts/Views/HomeBackgroundViews.swift","BigRoosterWorkouts/Views/Components/MiniAppPreview.swift","BigRoosterWorkouts/Views/Components/MuscleIllustrationCard.swift"]},{"name":"Home Backgrounds & Weather","slug":"home-backgrounds-weather","files":["BigRoosterWorkouts/Utilities/BackgroundRenderer.swift","BigRoosterWorkouts/Utilities/HomeBackgroundManager.swift","BigRoosterWorkouts/Utilities/HomeBackgroundResolver.swift","BigRoosterWorkouts/Utilities/HomeBackgroundStorage.swift","BigRoosterWorkouts/Utilities/HomeBackgroundWeatherService.swift"]},{"name":"Theme & Appearance","slug":"theme-appearance","files":["BigRoosterWorkouts/Utilities/AppAppearanceMode.swift","BigRoosterWorkouts/Utilities/DesignSystem.swift","BigRoosterWorkouts/Utilities/ThemeEnvironment.swift","BigRoosterWorkouts/Utilities/ThemeManager.swift","BigRoosterWorkouts/Utilities/ThemeStorage.swift","BigRoosterWorkouts/Views/ThemeEditorView.swift","BigRoosterWorkouts/Views/ThemeListView.swift","BigRoosterWorkouts/Views/DesignSystemShowcaseView.swift"]},{"name":"Health, Activity & Widgets","slug":"health-activity-widgets","files":["BigRoosterWorkouts/Utilities/HealthKitManager.swift","BigRoosterWorkouts/Utilities/LiveActivityManager.swift","BigRoosterWorkouts/Utilities/WidgetDataProvider.swift","BigRoosterWorkouts/Utilities/WidgetDataRefresher.swift"]},{"name":"Notifications & Rest Timer","slug":"notifications-rest-timer","files":["BigRoosterWorkouts/Utilities/RestTimerManager.swift","BigRoosterWorkouts/Utilities/WorkoutReminderNotificationScheduler.swift","BigRoosterWorkouts/Utilities/WorkoutReminderPreferences.swift","BigRoosterWorkouts/Views/Components/RestTimerView.swift","BigRoosterWorkouts/Views/Components/WorkoutReminderSettingsControls.swift"]},{"name":"Shared Utilities & UI Helpers","slug":"shared-utilities-ui-helpers","files":["BigRoosterWorkouts/Utilities/BackgroundUndoManager.swift","BigRoosterWorkouts/Utilities/CustomToolBarModifier.swift","BigRoosterWorkouts/Utilities/ExerciseSearchService.swift","BigRoosterWorkouts/Utilities/PreferenceKey.swift","BigRoosterWorkouts/Utilities/Router.swift","BigRoosterWorkouts/Utilities/SeedDataImporter.swift","BigRoosterWorkouts/Utilities/SharedContainer.swift","BigRoosterWorkouts/Utilities/SwiftUIConditionalHelpers.swift","BigRoosterWorkouts/Utilities/TextTransition.swift","BigRoosterWorkouts/Utilities/WorkoutGoalPreferences.swift","BigRoosterWorkouts/Utilities/WorkoutPlanImporter.swift","BigRoosterWorkouts/Utilities/UIScreen+ScreenCorners.swift"]},{"name":"Vertical Split Layout","slug":"vertical-split-layout","files":["BigRoosterWorkouts/Utilities/VerticalSplit/Accessories.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Helpers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Modifiers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/SplitDetent.swift","BigRoosterWorkouts/Utilities/VerticalSplit/VerticalSplitView.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Wrappers.swift"]},{"name":"Profile & Settings","slug":"profile-settings","files":["BigRoosterWorkouts/Views/ProfileView.swift","BigRoosterWorkouts/Views/ProfileDashboardViews.swift","BigRoosterWorkouts/Views/SettingsView.swift","BigRoosterWorkouts/Views/CreditsView.swift"]},{"name":"Data & Seed Assets","slug":"data-seed-assets","files":["BigRoosterWorkouts/Data/mongo_seed_validation_summary.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.backup.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.json","BigRoosterWorkouts/Data/workout_programs.txt"]},{"name":"Scripts","slug":"scripts","files":["Scripts/convert_mongo_seed_data.py","Scripts/convert_workout_plans.py"]},{"name":"Other","slug":"other","files":[],"children":[{"name":"Other — AGENTS.md","slug":"other-agents-md","files":["AGENTS.md"]},{"name":"Other — BigRoosterWorkouts.xcodeproj","slug":"other-bigroosterworkouts-xcodeproj","files":["BigRoosterWorkouts.xcodeproj/project.pbxproj"]},{"name":"Other — project.xcworkspace","slug":"other-project-xcworkspace","files":["BigRoosterWorkouts.xcodeproj/project.xcworkspace/contents.xcworkspacedata","BigRoosterWorkouts.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"]},{"name":"Other — xcshareddata","slug":"other-xcshareddata","files":["BigRoosterWorkouts.xcodeproj/xcshareddata/xcschemes/BigRoosterWorkouts.xcscheme"]},{"name":"Other — Assets.xcassets","slug":"other-assets-xcassets","files":["BigRoosterWorkouts/Assets.xcassets/AccentColor.colorset/Contents.json","BigRoosterWorkouts/Assets.xcassets/AppIcon.appiconset/Contents.json","BigRoosterWorkouts/Assets.xcassets/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_evening.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_cloudy_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_fog_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_rain_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_snow_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_storm_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/100 workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/30 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/7 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/first workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/longest time record.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/new record calories burned.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-posterior-outer-inner-muscles.imageset/Contents.json"]},{"name":"Other — BigRoosterWorkouts","slug":"other-bigroosterworkouts","files":["BigRoosterWorkouts/BigRoosterWorkouts.entitlements"]},{"name":"Other — Views","slug":"other-views","files":["BigRoosterWorkouts/Views/BackgroundBuilderView.swift","BigRoosterWorkouts/Views/Components/FilterChip.swift","BigRoosterWorkouts/Views/Components/SearchBar.swift","BigRoosterWorkouts/Views/Components/UpcomingWorkoutPreviewCard.swift","BigRoosterWorkouts/Views/Components/WorkoutGoalSettingsControls.swift","BigRoosterWorkouts/Views/Components/WorkoutRecommendationPreviewCard.swift","BigRoosterWorkouts/Views/DatePickerSheet.swift","BigRoosterWorkouts/Views/FilterSheet.swift"]},{"name":"Other — BigRoosterWorkoutsTests","slug":"other-bigroosterworkoutstests","files":["BigRoosterWorkoutsTests/HomeBackgroundResolverTests.swift","BigRoosterWorkoutsTests/OpenMeteoHomeBackgroundWeatherProviderTests.swift","BigRoosterWorkoutsTests/SwiftUIConditionalHelpersTests.swift","BigRoosterWorkoutsTests/ThemeAppearanceTests.swift","BigRoosterWorkoutsTests/UnifiedWorkoutRecommendationServiceTests.swift","BigRoosterWorkoutsTests/WorkoutContentCreationTests.swift","BigRoosterWorkoutsTests/WorkoutHistoryRepositoryTests.swift","BigRoosterWorkoutsTests/WorkoutPlanFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutReminderFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutSessionCoordinatorTests.swift","BigRoosterWorkoutsTests/WorkoutStructurePersistenceTests.swift"]},{"name":"Other — CLAUDE.md","slug":"other-claude-md","files":["CLAUDE.md"]},{"name":"Other — GITHUB_ISSUE_CHECKLIST.md","slug":"other-github-issue-checklist-md","files":["GITHUB_ISSUE_CHECKLIST.md"]},{"name":"Other — TestPlan.xctestplan","slug":"other-testplan-xctestplan","files":["TestPlan.xctestplan"]},{"name":"Other — build_output.txt","slug":"other-build-output-txt","files":["build_output.txt"]},{"name":"Other — docs","slug":"other-docs","files":["docs/APP_GOAL_PROGRESS_REPORT.md","docs/CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md","docs/PR_REVIEW_CHECKLIST.md"]},{"name":"Other — changes","slug":"other-changes","files":["openspec/changes/add-home-weather-backgrounds/design.md","openspec/changes/add-home-weather-backgrounds/proposal.md","openspec/changes/add-home-weather-backgrounds/specs/home-weather-backgrounds/spec.md","openspec/changes/add-home-weather-backgrounds/tasks.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/design.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/proposal.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/specs/workout-start-sheet-plan-day-actions/spec.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/tasks.md","openspec/changes/add-plan-day-selection-and-skip/design.md","openspec/changes/add-plan-day-selection-and-skip/proposal.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-selection/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-skip/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-scheduling-core/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-start-advancement-fix/spec.md","openspec/changes/add-plan-day-selection-and-skip/tasks.md","openspec/changes/add-upcoming-workout-reminders/design.md","openspec/changes/add-upcoming-workout-reminders/proposal.md","openspec/changes/add-upcoming-workout-reminders/specs/workout-reminders/spec.md","openspec/changes/add-upcoming-workout-reminders/tasks.md","openspec/changes/add-workout-plan-search-tabs/design.md","openspec/changes/add-workout-plan-search-tabs/proposal.md","openspec/changes/add-workout-plan-search-tabs/specs/workout-plan-browsing/spec.md","openspec/changes/add-workout-plan-search-tabs/tasks.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/design.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/proposal.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/specs/achievement-badge-assets/spec.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/tasks.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/design.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/proposal.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/specs/swiftui-conditional-helpers/spec.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/tasks.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/design.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/proposal.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-component-primitives/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-design-tokens/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-screen-migration/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/tasks.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/design.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/proposal.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/specs/dead-code-cleanup/spec.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/tasks.md","openspec/changes/assess-original-product-goal-progress/design.md","openspec/changes/assess-original-product-goal-progress/proposal.md","openspec/changes/assess-original-product-goal-progress/specs/product-goal-progress-audit/spec.md","openspec/changes/assess-original-product-goal-progress/tasks.md","openspec/changes/core-workout-refactor/design.md","openspec/changes/core-workout-refactor/proposal.md","openspec/changes/core-workout-refactor/specs/workout-session-behavior-coverage/spec.md","openspec/changes/core-workout-refactor/specs/workout-session-domain/spec.md","openspec/changes/core-workout-refactor/tasks.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/design.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/proposal.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/specs/workout-history-typography/spec.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/tasks.md","openspec/changes/fix-home-wallpaper-contrast/design.md","openspec/changes/fix-home-wallpaper-contrast/proposal.md","openspec/changes/fix-home-wallpaper-contrast/specs/home-wallpaper-readability/spec.md","openspec/changes/fix-home-wallpaper-contrast/tasks.md","openspec/changes/fix-plan-readiness-and-adherence/design.md","openspec/changes/fix-plan-readiness-and-adherence/proposal.md","openspec/changes/fix-plan-readiness-and-adherence/specs/plan-readiness/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/secondary-workout-recommendations/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-goals/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-plan-adherence/spec.md","openspec/changes/fix-plan-readiness-and-adherence/tasks.md","openspec/changes/fix-theme-appearance-sync/design.md","openspec/changes/fix-theme-appearance-sync/proposal.md","openspec/changes/fix-theme-appearance-sync/specs/app-theme-appearance/spec.md","openspec/changes/fix-theme-appearance-sync/tasks.md","openspec/changes/import-mongo-seed-data/design.md","openspec/changes/import-mongo-seed-data/proposal.md","openspec/changes/import-mongo-seed-data/specs/mongo-seed-data-import/spec.md","openspec/changes/import-mongo-seed-data/tasks.md","openspec/changes/improve-empty-workout-placeholder/design.md","openspec/changes/improve-empty-workout-placeholder/proposal.md","openspec/changes/improve-empty-workout-placeholder/specs/empty-workout-placeholder/spec.md","openspec/changes/improve-empty-workout-placeholder/tasks.md","openspec/changes/improve-home-plan-card-and-stats-widgets/design.md","openspec/changes/improve-home-plan-card-and-stats-widgets/proposal.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-active-plan-card/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-stats-widgets/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/tasks.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/design.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/proposal.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-recommendation-card-copy/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-schedule-rebasing/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/workout-start-sheet-actions/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/tasks.md","openspec/changes/namespace-home-weather-wallpapers/design.md","openspec/changes/namespace-home-weather-wallpapers/proposal.md","openspec/changes/namespace-home-weather-wallpapers/specs/home-weather-wallpaper-assets/spec.md","openspec/changes/namespace-home-weather-wallpapers/tasks.md","openspec/changes/normalize-seed-data-field-names/design.md","openspec/changes/normalize-seed-data-field-names/proposal.md","openspec/changes/normalize-seed-data-field-names/specs/seed-data-normalization/spec.md","openspec/changes/normalize-seed-data-field-names/tasks.md","openspec/changes/plan-exercise-creation/design.md","openspec/changes/plan-exercise-creation/proposal.md","openspec/changes/plan-exercise-creation/specs/exercise-creation-domain/spec.md","openspec/changes/plan-exercise-creation/specs/workout-content-ownership/spec.md","openspec/changes/plan-exercise-creation/specs/workout-plan-creation/spec.md","openspec/changes/plan-exercise-creation/tasks.md","openspec/changes/redesign-profile-view/design.md","openspec/changes/redesign-profile-view/proposal.md","openspec/changes/redesign-profile-view/specs/profile-dashboard/spec.md","openspec/changes/redesign-profile-view/tasks.md","openspec/changes/refactor-workout-session-actionbar/design.md","openspec/changes/refactor-workout-session-actionbar/proposal.md","openspec/changes/refactor-workout-session-actionbar/specs/workout-session-actionbar-layout/spec.md","openspec/changes/refactor-workout-session-actionbar/tasks.md","openspec/changes/remove-food-step-tracking/design.md","openspec/changes/remove-food-step-tracking/proposal.md","openspec/changes/remove-food-step-tracking/specs/product-boundary-cleanup/spec.md","openspec/changes/remove-food-step-tracking/tasks.md","openspec/changes/stabilize-plan-builder-metadata-inputs/design.md","openspec/changes/stabilize-plan-builder-metadata-inputs/proposal.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-metadata-inputs/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-reorder-stability/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/tasks.md","openspec/changes/standardize-app-design-system/design.md","openspec/changes/standardize-app-design-system/proposal.md","openspec/changes/standardize-app-design-system/specs/app-color-token-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-spacing-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-typography-standardization/spec.md","openspec/changes/standardize-app-design-system/tasks.md","openspec/changes/streamline-app-ui-colors/design.md","openspec/changes/streamline-app-ui-colors/proposal.md","openspec/changes/streamline-app-ui-colors/specs/app-ui-color-consistency/spec.md","openspec/changes/streamline-app-ui-colors/tasks.md","openspec/changes/track-workout-plan-progress/design.md","openspec/changes/track-workout-plan-progress/proposal.md","openspec/changes/track-workout-plan-progress/specs/workout-plan-progress/spec.md","openspec/changes/track-workout-plan-progress/tasks.md","openspec/changes/unify-workout-recommendations/design.md","openspec/changes/unify-workout-recommendations/proposal.md","openspec/changes/unify-workout-recommendations/specs/workout-recommendations/spec.md","openspec/changes/unify-workout-recommendations/tasks.md"]},{"name":"Other — openspec","slug":"other-openspec","files":["openspec/config.yaml"]},{"name":"Other — specs","slug":"other-specs","files":["openspec/specs/achievement-badge-assets/spec.md","openspec/specs/dead-code-cleanup/spec.md","openspec/specs/shadcn-swiftui-component-primitives/spec.md","openspec/specs/shadcn-swiftui-design-tokens/spec.md","openspec/specs/shadcn-swiftui-screen-migration/spec.md","openspec/specs/swiftui-conditional-helpers/spec.md"]},{"name":"Other — skills-lock.json","slug":"other-skills-lock-json","files":["skills-lock.json"]}]}];
var META = {"fromCommit":"cbd91d6a8f2c23c0d6f2f835ebecb5119fd28900","generatedAt":"2026-05-21T04:39:42.222Z","model":"gpt-5.4-mini","moduleFiles":{"App Entry & Root Navigation":["BigRoosterWorkouts/BigRoosterWorkoutsApp.swift","BigRoosterWorkouts/ContentView.swift"],"Core Domain Models":["BigRoosterWorkouts/Models/AchievementEvent.swift","BigRoosterWorkouts/Models/AppTheme.swift","BigRoosterWorkouts/Models/BackgroundConfiguration.swift","BigRoosterWorkouts/Models/Exercise.swift","BigRoosterWorkouts/Models/ExerciseLog.swift","BigRoosterWorkouts/Models/ExerciseSearchIndex.swift","BigRoosterWorkouts/Models/ExerciseTemplate.swift","BigRoosterWorkouts/Models/FilterState.swift","BigRoosterWorkouts/Models/HeatmapWidgetConfiguration.swift","BigRoosterWorkouts/Models/HomeBackgroundConfiguration.swift","BigRoosterWorkouts/Models/MuscleAnatomy.swift","BigRoosterWorkouts/Models/PendingAchievementCelebration.swift","BigRoosterWorkouts/Models/SetLog.swift","BigRoosterWorkouts/Models/ThemeColorSet.swift","BigRoosterWorkouts/Models/UserProfile.swift","BigRoosterWorkouts/Models/WidgetDataModels.swift","BigRoosterWorkouts/Models/WorkoutActivityAttributes.swift","BigRoosterWorkouts/Models/WorkoutContentOwnership.swift","BigRoosterWorkouts/Models/WorkoutPlan.swift","BigRoosterWorkouts/Models/WorkoutSession.swift","BigRoosterWorkouts/Models/WorkoutTemplate.swift"],"Exercise Creation":["BigRoosterWorkouts/Features/ExerciseCreation/ExerciseCreationService.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseSelection.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateBuilderViewModel.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateDraft.swift","BigRoosterWorkouts/Views/ExerciseTemplateBuilderView.swift","BigRoosterWorkouts/Views/CustomExercisesView.swift","BigRoosterWorkouts/Views/ExerciseGuideView.swift","BigRoosterWorkouts/Views/TemplateListView.swift"],"Workout Plan Creation":["BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanAudience.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderView.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderViewModel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanCreationService.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDaysPerWeek.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDraft.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDurationWeeks.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanEquipment.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanGoal.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanLevel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanTimeRange.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanType.swift","BigRoosterWorkouts/Views/WorkoutPlansView.swift","BigRoosterWorkouts/Views/WorkoutPlanDetailView.swift","BigRoosterWorkouts/Views/WorkoutDetailView.swift","BigRoosterWorkouts/Views/WorkoutTemplateBuilderView.swift"],"Workout Session":["BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionCoordinator.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionInputFormatter.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionSideEffects.swift","BigRoosterWorkouts/Views/WorkoutSessionView.swift","BigRoosterWorkouts/Views/WorkoutSessionNotesView.swift","BigRoosterWorkouts/Views/WorkoutStartSheet.swift"],"Workout Session — Features":["BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionCoordinator.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionInputFormatter.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionSideEffects.swift"],"Workout Session — Views":["BigRoosterWorkouts/Views/WorkoutSessionView.swift","BigRoosterWorkouts/Views/WorkoutSessionNotesView.swift","BigRoosterWorkouts/Views/WorkoutStartSheet.swift"],"Workout History & Statistics":["BigRoosterWorkouts/Utilities/TrainingStatsCalculator.swift","BigRoosterWorkouts/Utilities/WorkoutHistoryRepository.swift","BigRoosterWorkouts/Views/WorkoutHistoryView.swift","BigRoosterWorkouts/Views/DetailedStatsView.swift","BigRoosterWorkouts/Views/RecordsHistoryView.swift"],"Recommendations & Plan Scheduling":["BigRoosterWorkouts/Utilities/UnifiedWorkoutRecommendationService.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendation.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendationEngine.swift","BigRoosterWorkouts/Utilities/WorkoutPlanService.swift","BigRoosterWorkouts/Utilities/WorkoutRecommendation.swift"],"Achievements & Badges":["BigRoosterWorkouts/Utilities/AchievementService.swift","BigRoosterWorkouts/Views/Components/AchievementBadgeViews.swift"],"Home Dashboard":["BigRoosterWorkouts/Views/HomeView.swift","BigRoosterWorkouts/Views/HomeBackgroundEditorView.swift","BigRoosterWorkouts/Views/HomeBackgroundViews.swift","BigRoosterWorkouts/Views/Components/MiniAppPreview.swift","BigRoosterWorkouts/Views/Components/MuscleIllustrationCard.swift"],"Home Backgrounds & Weather":["BigRoosterWorkouts/Utilities/BackgroundRenderer.swift","BigRoosterWorkouts/Utilities/HomeBackgroundManager.swift","BigRoosterWorkouts/Utilities/HomeBackgroundResolver.swift","BigRoosterWorkouts/Utilities/HomeBackgroundStorage.swift","BigRoosterWorkouts/Utilities/HomeBackgroundWeatherService.swift"],"Theme & Appearance":["BigRoosterWorkouts/Utilities/AppAppearanceMode.swift","BigRoosterWorkouts/Utilities/DesignSystem.swift","BigRoosterWorkouts/Utilities/ThemeEnvironment.swift","BigRoosterWorkouts/Utilities/ThemeManager.swift","BigRoosterWorkouts/Utilities/ThemeStorage.swift","BigRoosterWorkouts/Views/ThemeEditorView.swift","BigRoosterWorkouts/Views/ThemeListView.swift","BigRoosterWorkouts/Views/DesignSystemShowcaseView.swift"],"Health, Activity & Widgets":["BigRoosterWorkouts/Utilities/HealthKitManager.swift","BigRoosterWorkouts/Utilities/LiveActivityManager.swift","BigRoosterWorkouts/Utilities/WidgetDataProvider.swift","BigRoosterWorkouts/Utilities/WidgetDataRefresher.swift"],"Notifications & Rest Timer":["BigRoosterWorkouts/Utilities/RestTimerManager.swift","BigRoosterWorkouts/Utilities/WorkoutReminderNotificationScheduler.swift","BigRoosterWorkouts/Utilities/WorkoutReminderPreferences.swift","BigRoosterWorkouts/Views/Components/RestTimerView.swift","BigRoosterWorkouts/Views/Components/WorkoutReminderSettingsControls.swift"],"Shared Utilities & UI Helpers":["BigRoosterWorkouts/Utilities/BackgroundUndoManager.swift","BigRoosterWorkouts/Utilities/CustomToolBarModifier.swift","BigRoosterWorkouts/Utilities/ExerciseSearchService.swift","BigRoosterWorkouts/Utilities/PreferenceKey.swift","BigRoosterWorkouts/Utilities/Router.swift","BigRoosterWorkouts/Utilities/SeedDataImporter.swift","BigRoosterWorkouts/Utilities/SharedContainer.swift","BigRoosterWorkouts/Utilities/SwiftUIConditionalHelpers.swift","BigRoosterWorkouts/Utilities/TextTransition.swift","BigRoosterWorkouts/Utilities/WorkoutGoalPreferences.swift","BigRoosterWorkouts/Utilities/WorkoutPlanImporter.swift","BigRoosterWorkouts/Utilities/UIScreen+ScreenCorners.swift"],"Vertical Split Layout":["BigRoosterWorkouts/Utilities/VerticalSplit/Accessories.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Helpers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Modifiers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/SplitDetent.swift","BigRoosterWorkouts/Utilities/VerticalSplit/VerticalSplitView.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Wrappers.swift"],"Profile & Settings":["BigRoosterWorkouts/Views/ProfileView.swift","BigRoosterWorkouts/Views/ProfileDashboardViews.swift","BigRoosterWorkouts/Views/SettingsView.swift","BigRoosterWorkouts/Views/CreditsView.swift"],"Data & Seed Assets":["BigRoosterWorkouts/Data/mongo_seed_validation_summary.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.backup.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.json","BigRoosterWorkouts/Data/workout_programs.txt"],"Scripts":["Scripts/convert_mongo_seed_data.py","Scripts/convert_workout_plans.py"],"Other":["AGENTS.md","BigRoosterWorkouts.xcodeproj/project.pbxproj","BigRoosterWorkouts.xcodeproj/project.xcworkspace/contents.xcworkspacedata","BigRoosterWorkouts.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved","BigRoosterWorkouts.xcodeproj/xcshareddata/xcschemes/BigRoosterWorkouts.xcscheme","BigRoosterWorkouts/Assets.xcassets/AccentColor.colorset/Contents.json","BigRoosterWorkouts/Assets.xcassets/AppIcon.appiconset/Contents.json","BigRoosterWorkouts/Assets.xcassets/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_evening.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_cloudy_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_fog_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_rain_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_snow_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_storm_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/100 workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/30 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/7 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/first workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/longest time record.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/new record calories burned.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/BigRoosterWorkouts.entitlements","BigRoosterWorkouts/Views/BackgroundBuilderView.swift","BigRoosterWorkouts/Views/Components/FilterChip.swift","BigRoosterWorkouts/Views/Components/SearchBar.swift","BigRoosterWorkouts/Views/Components/UpcomingWorkoutPreviewCard.swift","BigRoosterWorkouts/Views/Components/WorkoutGoalSettingsControls.swift","BigRoosterWorkouts/Views/Components/WorkoutRecommendationPreviewCard.swift","BigRoosterWorkouts/Views/DatePickerSheet.swift","BigRoosterWorkouts/Views/FilterSheet.swift","BigRoosterWorkoutsTests/HomeBackgroundResolverTests.swift","BigRoosterWorkoutsTests/OpenMeteoHomeBackgroundWeatherProviderTests.swift","BigRoosterWorkoutsTests/SwiftUIConditionalHelpersTests.swift","BigRoosterWorkoutsTests/ThemeAppearanceTests.swift","BigRoosterWorkoutsTests/UnifiedWorkoutRecommendationServiceTests.swift","BigRoosterWorkoutsTests/WorkoutContentCreationTests.swift","BigRoosterWorkoutsTests/WorkoutHistoryRepositoryTests.swift","BigRoosterWorkoutsTests/WorkoutPlanFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutReminderFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutSessionCoordinatorTests.swift","BigRoosterWorkoutsTests/WorkoutStructurePersistenceTests.swift","CLAUDE.md","GITHUB_ISSUE_CHECKLIST.md","TestPlan.xctestplan","build_output.txt","docs/APP_GOAL_PROGRESS_REPORT.md","docs/CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md","docs/PR_REVIEW_CHECKLIST.md","openspec/changes/add-home-weather-backgrounds/design.md","openspec/changes/add-home-weather-backgrounds/proposal.md","openspec/changes/add-home-weather-backgrounds/specs/home-weather-backgrounds/spec.md","openspec/changes/add-home-weather-backgrounds/tasks.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/design.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/proposal.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/specs/workout-start-sheet-plan-day-actions/spec.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/tasks.md","openspec/changes/add-plan-day-selection-and-skip/design.md","openspec/changes/add-plan-day-selection-and-skip/proposal.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-selection/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-skip/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-scheduling-core/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-start-advancement-fix/spec.md","openspec/changes/add-plan-day-selection-and-skip/tasks.md","openspec/changes/add-upcoming-workout-reminders/design.md","openspec/changes/add-upcoming-workout-reminders/proposal.md","openspec/changes/add-upcoming-workout-reminders/specs/workout-reminders/spec.md","openspec/changes/add-upcoming-workout-reminders/tasks.md","openspec/changes/add-workout-plan-search-tabs/design.md","openspec/changes/add-workout-plan-search-tabs/proposal.md","openspec/changes/add-workout-plan-search-tabs/specs/workout-plan-browsing/spec.md","openspec/changes/add-workout-plan-search-tabs/tasks.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/design.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/proposal.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/specs/achievement-badge-assets/spec.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/tasks.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/design.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/proposal.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/specs/swiftui-conditional-helpers/spec.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/tasks.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/design.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/proposal.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-component-primitives/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-design-tokens/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-screen-migration/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/tasks.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/design.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/proposal.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/specs/dead-code-cleanup/spec.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/tasks.md","openspec/changes/assess-original-product-goal-progress/design.md","openspec/changes/assess-original-product-goal-progress/proposal.md","openspec/changes/assess-original-product-goal-progress/specs/product-goal-progress-audit/spec.md","openspec/changes/assess-original-product-goal-progress/tasks.md","openspec/changes/core-workout-refactor/design.md","openspec/changes/core-workout-refactor/proposal.md","openspec/changes/core-workout-refactor/specs/workout-session-behavior-coverage/spec.md","openspec/changes/core-workout-refactor/specs/workout-session-domain/spec.md","openspec/changes/core-workout-refactor/tasks.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/design.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/proposal.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/specs/workout-history-typography/spec.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/tasks.md","openspec/changes/fix-home-wallpaper-contrast/design.md","openspec/changes/fix-home-wallpaper-contrast/proposal.md","openspec/changes/fix-home-wallpaper-contrast/specs/home-wallpaper-readability/spec.md","openspec/changes/fix-home-wallpaper-contrast/tasks.md","openspec/changes/fix-plan-readiness-and-adherence/design.md","openspec/changes/fix-plan-readiness-and-adherence/proposal.md","openspec/changes/fix-plan-readiness-and-adherence/specs/plan-readiness/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/secondary-workout-recommendations/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-goals/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-plan-adherence/spec.md","openspec/changes/fix-plan-readiness-and-adherence/tasks.md","openspec/changes/fix-theme-appearance-sync/design.md","openspec/changes/fix-theme-appearance-sync/proposal.md","openspec/changes/fix-theme-appearance-sync/specs/app-theme-appearance/spec.md","openspec/changes/fix-theme-appearance-sync/tasks.md","openspec/changes/import-mongo-seed-data/design.md","openspec/changes/import-mongo-seed-data/proposal.md","openspec/changes/import-mongo-seed-data/specs/mongo-seed-data-import/spec.md","openspec/changes/import-mongo-seed-data/tasks.md","openspec/changes/improve-empty-workout-placeholder/design.md","openspec/changes/improve-empty-workout-placeholder/proposal.md","openspec/changes/improve-empty-workout-placeholder/specs/empty-workout-placeholder/spec.md","openspec/changes/improve-empty-workout-placeholder/tasks.md","openspec/changes/improve-home-plan-card-and-stats-widgets/design.md","openspec/changes/improve-home-plan-card-and-stats-widgets/proposal.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-active-plan-card/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-stats-widgets/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/tasks.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/design.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/proposal.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-recommendation-card-copy/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-schedule-rebasing/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/workout-start-sheet-actions/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/tasks.md","openspec/changes/namespace-home-weather-wallpapers/design.md","openspec/changes/namespace-home-weather-wallpapers/proposal.md","openspec/changes/namespace-home-weather-wallpapers/specs/home-weather-wallpaper-assets/spec.md","openspec/changes/namespace-home-weather-wallpapers/tasks.md","openspec/changes/normalize-seed-data-field-names/design.md","openspec/changes/normalize-seed-data-field-names/proposal.md","openspec/changes/normalize-seed-data-field-names/specs/seed-data-normalization/spec.md","openspec/changes/normalize-seed-data-field-names/tasks.md","openspec/changes/plan-exercise-creation/design.md","openspec/changes/plan-exercise-creation/proposal.md","openspec/changes/plan-exercise-creation/specs/exercise-creation-domain/spec.md","openspec/changes/plan-exercise-creation/specs/workout-content-ownership/spec.md","openspec/changes/plan-exercise-creation/specs/workout-plan-creation/spec.md","openspec/changes/plan-exercise-creation/tasks.md","openspec/changes/redesign-profile-view/design.md","openspec/changes/redesign-profile-view/proposal.md","openspec/changes/redesign-profile-view/specs/profile-dashboard/spec.md","openspec/changes/redesign-profile-view/tasks.md","openspec/changes/refactor-workout-session-actionbar/design.md","openspec/changes/refactor-workout-session-actionbar/proposal.md","openspec/changes/refactor-workout-session-actionbar/specs/workout-session-actionbar-layout/spec.md","openspec/changes/refactor-workout-session-actionbar/tasks.md","openspec/changes/remove-food-step-tracking/design.md","openspec/changes/remove-food-step-tracking/proposal.md","openspec/changes/remove-food-step-tracking/specs/product-boundary-cleanup/spec.md","openspec/changes/remove-food-step-tracking/tasks.md","openspec/changes/stabilize-plan-builder-metadata-inputs/design.md","openspec/changes/stabilize-plan-builder-metadata-inputs/proposal.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-metadata-inputs/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-reorder-stability/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/tasks.md","openspec/changes/standardize-app-design-system/design.md","openspec/changes/standardize-app-design-system/proposal.md","openspec/changes/standardize-app-design-system/specs/app-color-token-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-spacing-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-typography-standardization/spec.md","openspec/changes/standardize-app-design-system/tasks.md","openspec/changes/streamline-app-ui-colors/design.md","openspec/changes/streamline-app-ui-colors/proposal.md","openspec/changes/streamline-app-ui-colors/specs/app-ui-color-consistency/spec.md","openspec/changes/streamline-app-ui-colors/tasks.md","openspec/changes/track-workout-plan-progress/design.md","openspec/changes/track-workout-plan-progress/proposal.md","openspec/changes/track-workout-plan-progress/specs/workout-plan-progress/spec.md","openspec/changes/track-workout-plan-progress/tasks.md","openspec/changes/unify-workout-recommendations/design.md","openspec/changes/unify-workout-recommendations/proposal.md","openspec/changes/unify-workout-recommendations/specs/workout-recommendations/spec.md","openspec/changes/unify-workout-recommendations/tasks.md","openspec/config.yaml","openspec/specs/achievement-badge-assets/spec.md","openspec/specs/dead-code-cleanup/spec.md","openspec/specs/shadcn-swiftui-component-primitives/spec.md","openspec/specs/shadcn-swiftui-design-tokens/spec.md","openspec/specs/shadcn-swiftui-screen-migration/spec.md","openspec/specs/swiftui-conditional-helpers/spec.md","skills-lock.json"],"Other — AGENTS.md":["AGENTS.md"],"Other — BigRoosterWorkouts.xcodeproj":["BigRoosterWorkouts.xcodeproj/project.pbxproj"],"Other — project.xcworkspace":["BigRoosterWorkouts.xcodeproj/project.xcworkspace/contents.xcworkspacedata","BigRoosterWorkouts.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"],"Other — xcshareddata":["BigRoosterWorkouts.xcodeproj/xcshareddata/xcschemes/BigRoosterWorkouts.xcscheme"],"Other — Assets.xcassets":["BigRoosterWorkouts/Assets.xcassets/AccentColor.colorset/Contents.json","BigRoosterWorkouts/Assets.xcassets/AppIcon.appiconset/Contents.json","BigRoosterWorkouts/Assets.xcassets/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_evening.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_cloudy_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_fog_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_rain_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_snow_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_storm_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/100 workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/30 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/7 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/first workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/longest time record.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/new record calories burned.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-posterior-outer-inner-muscles.imageset/Contents.json"],"Other — BigRoosterWorkouts":["BigRoosterWorkouts/BigRoosterWorkouts.entitlements"],"Other — Views":["BigRoosterWorkouts/Views/BackgroundBuilderView.swift","BigRoosterWorkouts/Views/Components/FilterChip.swift","BigRoosterWorkouts/Views/Components/SearchBar.swift","BigRoosterWorkouts/Views/Components/UpcomingWorkoutPreviewCard.swift","BigRoosterWorkouts/Views/Components/WorkoutGoalSettingsControls.swift","BigRoosterWorkouts/Views/Components/WorkoutRecommendationPreviewCard.swift","BigRoosterWorkouts/Views/DatePickerSheet.swift","BigRoosterWorkouts/Views/FilterSheet.swift"],"Other — BigRoosterWorkoutsTests":["BigRoosterWorkoutsTests/HomeBackgroundResolverTests.swift","BigRoosterWorkoutsTests/OpenMeteoHomeBackgroundWeatherProviderTests.swift","BigRoosterWorkoutsTests/SwiftUIConditionalHelpersTests.swift","BigRoosterWorkoutsTests/ThemeAppearanceTests.swift","BigRoosterWorkoutsTests/UnifiedWorkoutRecommendationServiceTests.swift","BigRoosterWorkoutsTests/WorkoutContentCreationTests.swift","BigRoosterWorkoutsTests/WorkoutHistoryRepositoryTests.swift","BigRoosterWorkoutsTests/WorkoutPlanFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutReminderFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutSessionCoordinatorTests.swift","BigRoosterWorkoutsTests/WorkoutStructurePersistenceTests.swift"],"Other — CLAUDE.md":["CLAUDE.md"],"Other — GITHUB_ISSUE_CHECKLIST.md":["GITHUB_ISSUE_CHECKLIST.md"],"Other — TestPlan.xctestplan":["TestPlan.xctestplan"],"Other — build_output.txt":["build_output.txt"],"Other — docs":["docs/APP_GOAL_PROGRESS_REPORT.md","docs/CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md","docs/PR_REVIEW_CHECKLIST.md"],"Other — changes":["openspec/changes/add-home-weather-backgrounds/design.md","openspec/changes/add-home-weather-backgrounds/proposal.md","openspec/changes/add-home-weather-backgrounds/specs/home-weather-backgrounds/spec.md","openspec/changes/add-home-weather-backgrounds/tasks.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/design.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/proposal.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/specs/workout-start-sheet-plan-day-actions/spec.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/tasks.md","openspec/changes/add-plan-day-selection-and-skip/design.md","openspec/changes/add-plan-day-selection-and-skip/proposal.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-selection/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-skip/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-scheduling-core/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-start-advancement-fix/spec.md","openspec/changes/add-plan-day-selection-and-skip/tasks.md","openspec/changes/add-upcoming-workout-reminders/design.md","openspec/changes/add-upcoming-workout-reminders/proposal.md","openspec/changes/add-upcoming-workout-reminders/specs/workout-reminders/spec.md","openspec/changes/add-upcoming-workout-reminders/tasks.md","openspec/changes/add-workout-plan-search-tabs/design.md","openspec/changes/add-workout-plan-search-tabs/proposal.md","openspec/changes/add-workout-plan-search-tabs/specs/workout-plan-browsing/spec.md","openspec/changes/add-workout-plan-search-tabs/tasks.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/design.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/proposal.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/specs/achievement-badge-assets/spec.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/tasks.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/design.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/proposal.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/specs/swiftui-conditional-helpers/spec.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/tasks.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/design.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/proposal.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-component-primitives/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-design-tokens/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-screen-migration/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/tasks.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/design.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/proposal.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/specs/dead-code-cleanup/spec.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/tasks.md","openspec/changes/assess-original-product-goal-progress/design.md","openspec/changes/assess-original-product-goal-progress/proposal.md","openspec/changes/assess-original-product-goal-progress/specs/product-goal-progress-audit/spec.md","openspec/changes/assess-original-product-goal-progress/tasks.md","openspec/changes/core-workout-refactor/design.md","openspec/changes/core-workout-refactor/proposal.md","openspec/changes/core-workout-refactor/specs/workout-session-behavior-coverage/spec.md","openspec/changes/core-workout-refactor/specs/workout-session-domain/spec.md","openspec/changes/core-workout-refactor/tasks.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/design.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/proposal.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/specs/workout-history-typography/spec.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/tasks.md","openspec/changes/fix-home-wallpaper-contrast/design.md","openspec/changes/fix-home-wallpaper-contrast/proposal.md","openspec/changes/fix-home-wallpaper-contrast/specs/home-wallpaper-readability/spec.md","openspec/changes/fix-home-wallpaper-contrast/tasks.md","openspec/changes/fix-plan-readiness-and-adherence/design.md","openspec/changes/fix-plan-readiness-and-adherence/proposal.md","openspec/changes/fix-plan-readiness-and-adherence/specs/plan-readiness/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/secondary-workout-recommendations/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-goals/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-plan-adherence/spec.md","openspec/changes/fix-plan-readiness-and-adherence/tasks.md","openspec/changes/fix-theme-appearance-sync/design.md","openspec/changes/fix-theme-appearance-sync/proposal.md","openspec/changes/fix-theme-appearance-sync/specs/app-theme-appearance/spec.md","openspec/changes/fix-theme-appearance-sync/tasks.md","openspec/changes/import-mongo-seed-data/design.md","openspec/changes/import-mongo-seed-data/proposal.md","openspec/changes/import-mongo-seed-data/specs/mongo-seed-data-import/spec.md","openspec/changes/import-mongo-seed-data/tasks.md","openspec/changes/improve-empty-workout-placeholder/design.md","openspec/changes/improve-empty-workout-placeholder/proposal.md","openspec/changes/improve-empty-workout-placeholder/specs/empty-workout-placeholder/spec.md","openspec/changes/improve-empty-workout-placeholder/tasks.md","openspec/changes/improve-home-plan-card-and-stats-widgets/design.md","openspec/changes/improve-home-plan-card-and-stats-widgets/proposal.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-active-plan-card/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-stats-widgets/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/tasks.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/design.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/proposal.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-recommendation-card-copy/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-schedule-rebasing/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/workout-start-sheet-actions/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/tasks.md","openspec/changes/namespace-home-weather-wallpapers/design.md","openspec/changes/namespace-home-weather-wallpapers/proposal.md","openspec/changes/namespace-home-weather-wallpapers/specs/home-weather-wallpaper-assets/spec.md","openspec/changes/namespace-home-weather-wallpapers/tasks.md","openspec/changes/normalize-seed-data-field-names/design.md","openspec/changes/normalize-seed-data-field-names/proposal.md","openspec/changes/normalize-seed-data-field-names/specs/seed-data-normalization/spec.md","openspec/changes/normalize-seed-data-field-names/tasks.md","openspec/changes/plan-exercise-creation/design.md","openspec/changes/plan-exercise-creation/proposal.md","openspec/changes/plan-exercise-creation/specs/exercise-creation-domain/spec.md","openspec/changes/plan-exercise-creation/specs/workout-content-ownership/spec.md","openspec/changes/plan-exercise-creation/specs/workout-plan-creation/spec.md","openspec/changes/plan-exercise-creation/tasks.md","openspec/changes/redesign-profile-view/design.md","openspec/changes/redesign-profile-view/proposal.md","openspec/changes/redesign-profile-view/specs/profile-dashboard/spec.md","openspec/changes/redesign-profile-view/tasks.md","openspec/changes/refactor-workout-session-actionbar/design.md","openspec/changes/refactor-workout-session-actionbar/proposal.md","openspec/changes/refactor-workout-session-actionbar/specs/workout-session-actionbar-layout/spec.md","openspec/changes/refactor-workout-session-actionbar/tasks.md","openspec/changes/remove-food-step-tracking/design.md","openspec/changes/remove-food-step-tracking/proposal.md","openspec/changes/remove-food-step-tracking/specs/product-boundary-cleanup/spec.md","openspec/changes/remove-food-step-tracking/tasks.md","openspec/changes/stabilize-plan-builder-metadata-inputs/design.md","openspec/changes/stabilize-plan-builder-metadata-inputs/proposal.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-metadata-inputs/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-reorder-stability/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/tasks.md","openspec/changes/standardize-app-design-system/design.md","openspec/changes/standardize-app-design-system/proposal.md","openspec/changes/standardize-app-design-system/specs/app-color-token-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-spacing-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-typography-standardization/spec.md","openspec/changes/standardize-app-design-system/tasks.md","openspec/changes/streamline-app-ui-colors/design.md","openspec/changes/streamline-app-ui-colors/proposal.md","openspec/changes/streamline-app-ui-colors/specs/app-ui-color-consistency/spec.md","openspec/changes/streamline-app-ui-colors/tasks.md","openspec/changes/track-workout-plan-progress/design.md","openspec/changes/track-workout-plan-progress/proposal.md","openspec/changes/track-workout-plan-progress/specs/workout-plan-progress/spec.md","openspec/changes/track-workout-plan-progress/tasks.md","openspec/changes/unify-workout-recommendations/design.md","openspec/changes/unify-workout-recommendations/proposal.md","openspec/changes/unify-workout-recommendations/specs/workout-recommendations/spec.md","openspec/changes/unify-workout-recommendations/tasks.md"],"Other — openspec":["openspec/config.yaml"],"Other — specs":["openspec/specs/achievement-badge-assets/spec.md","openspec/specs/dead-code-cleanup/spec.md","openspec/specs/shadcn-swiftui-component-primitives/spec.md","openspec/specs/shadcn-swiftui-design-tokens/spec.md","openspec/specs/shadcn-swiftui-screen-migration/spec.md","openspec/specs/swiftui-conditional-helpers/spec.md"],"Other — skills-lock.json":["skills-lock.json"]},"moduleTree":[{"name":"App Entry & Root Navigation","slug":"app-entry-root-navigation","files":["BigRoosterWorkouts/BigRoosterWorkoutsApp.swift","BigRoosterWorkouts/ContentView.swift"]},{"name":"Core Domain Models","slug":"core-domain-models","files":["BigRoosterWorkouts/Models/AchievementEvent.swift","BigRoosterWorkouts/Models/AppTheme.swift","BigRoosterWorkouts/Models/BackgroundConfiguration.swift","BigRoosterWorkouts/Models/Exercise.swift","BigRoosterWorkouts/Models/ExerciseLog.swift","BigRoosterWorkouts/Models/ExerciseSearchIndex.swift","BigRoosterWorkouts/Models/ExerciseTemplate.swift","BigRoosterWorkouts/Models/FilterState.swift","BigRoosterWorkouts/Models/HeatmapWidgetConfiguration.swift","BigRoosterWorkouts/Models/HomeBackgroundConfiguration.swift","BigRoosterWorkouts/Models/MuscleAnatomy.swift","BigRoosterWorkouts/Models/PendingAchievementCelebration.swift","BigRoosterWorkouts/Models/SetLog.swift","BigRoosterWorkouts/Models/ThemeColorSet.swift","BigRoosterWorkouts/Models/UserProfile.swift","BigRoosterWorkouts/Models/WidgetDataModels.swift","BigRoosterWorkouts/Models/WorkoutActivityAttributes.swift","BigRoosterWorkouts/Models/WorkoutContentOwnership.swift","BigRoosterWorkouts/Models/WorkoutPlan.swift","BigRoosterWorkouts/Models/WorkoutSession.swift","BigRoosterWorkouts/Models/WorkoutTemplate.swift"]},{"name":"Exercise Creation","slug":"exercise-creation","files":["BigRoosterWorkouts/Features/ExerciseCreation/ExerciseCreationService.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseSelection.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateBuilderViewModel.swift","BigRoosterWorkouts/Features/ExerciseCreation/ExerciseTemplateDraft.swift","BigRoosterWorkouts/Views/ExerciseTemplateBuilderView.swift","BigRoosterWorkouts/Views/CustomExercisesView.swift","BigRoosterWorkouts/Views/ExerciseGuideView.swift","BigRoosterWorkouts/Views/TemplateListView.swift"]},{"name":"Workout Plan Creation","slug":"workout-plan-creation","files":["BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanAudience.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderView.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanBuilderViewModel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanCreationService.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDaysPerWeek.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDraft.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanDurationWeeks.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanEquipment.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanGoal.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanLevel.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanTimeRange.swift","BigRoosterWorkouts/Features/PlanCreation/WorkoutPlanType.swift","BigRoosterWorkouts/Views/WorkoutPlansView.swift","BigRoosterWorkouts/Views/WorkoutPlanDetailView.swift","BigRoosterWorkouts/Views/WorkoutDetailView.swift","BigRoosterWorkouts/Views/WorkoutTemplateBuilderView.swift"]},{"name":"Workout Session","slug":"workout-session","files":[],"children":[{"name":"Workout Session — Features","slug":"workout-session-features","files":["BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionCoordinator.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionInputFormatter.swift","BigRoosterWorkouts/Features/WorkoutSession/WorkoutSessionSideEffects.swift"]},{"name":"Workout Session — Views","slug":"workout-session-views","files":["BigRoosterWorkouts/Views/WorkoutSessionView.swift","BigRoosterWorkouts/Views/WorkoutSessionNotesView.swift","BigRoosterWorkouts/Views/WorkoutStartSheet.swift"]}]},{"name":"Workout History & Statistics","slug":"workout-history-statistics","files":["BigRoosterWorkouts/Utilities/TrainingStatsCalculator.swift","BigRoosterWorkouts/Utilities/WorkoutHistoryRepository.swift","BigRoosterWorkouts/Views/WorkoutHistoryView.swift","BigRoosterWorkouts/Views/DetailedStatsView.swift","BigRoosterWorkouts/Views/RecordsHistoryView.swift"]},{"name":"Recommendations & Plan Scheduling","slug":"recommendations-plan-scheduling","files":["BigRoosterWorkouts/Utilities/UnifiedWorkoutRecommendationService.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendation.swift","BigRoosterWorkouts/Utilities/UpcomingWorkoutRecommendationEngine.swift","BigRoosterWorkouts/Utilities/WorkoutPlanService.swift","BigRoosterWorkouts/Utilities/WorkoutRecommendation.swift"]},{"name":"Achievements & Badges","slug":"achievements-badges","files":["BigRoosterWorkouts/Utilities/AchievementService.swift","BigRoosterWorkouts/Views/Components/AchievementBadgeViews.swift"]},{"name":"Home Dashboard","slug":"home-dashboard","files":["BigRoosterWorkouts/Views/HomeView.swift","BigRoosterWorkouts/Views/HomeBackgroundEditorView.swift","BigRoosterWorkouts/Views/HomeBackgroundViews.swift","BigRoosterWorkouts/Views/Components/MiniAppPreview.swift","BigRoosterWorkouts/Views/Components/MuscleIllustrationCard.swift"]},{"name":"Home Backgrounds & Weather","slug":"home-backgrounds-weather","files":["BigRoosterWorkouts/Utilities/BackgroundRenderer.swift","BigRoosterWorkouts/Utilities/HomeBackgroundManager.swift","BigRoosterWorkouts/Utilities/HomeBackgroundResolver.swift","BigRoosterWorkouts/Utilities/HomeBackgroundStorage.swift","BigRoosterWorkouts/Utilities/HomeBackgroundWeatherService.swift"]},{"name":"Theme & Appearance","slug":"theme-appearance","files":["BigRoosterWorkouts/Utilities/AppAppearanceMode.swift","BigRoosterWorkouts/Utilities/DesignSystem.swift","BigRoosterWorkouts/Utilities/ThemeEnvironment.swift","BigRoosterWorkouts/Utilities/ThemeManager.swift","BigRoosterWorkouts/Utilities/ThemeStorage.swift","BigRoosterWorkouts/Views/ThemeEditorView.swift","BigRoosterWorkouts/Views/ThemeListView.swift","BigRoosterWorkouts/Views/DesignSystemShowcaseView.swift"]},{"name":"Health, Activity & Widgets","slug":"health-activity-widgets","files":["BigRoosterWorkouts/Utilities/HealthKitManager.swift","BigRoosterWorkouts/Utilities/LiveActivityManager.swift","BigRoosterWorkouts/Utilities/WidgetDataProvider.swift","BigRoosterWorkouts/Utilities/WidgetDataRefresher.swift"]},{"name":"Notifications & Rest Timer","slug":"notifications-rest-timer","files":["BigRoosterWorkouts/Utilities/RestTimerManager.swift","BigRoosterWorkouts/Utilities/WorkoutReminderNotificationScheduler.swift","BigRoosterWorkouts/Utilities/WorkoutReminderPreferences.swift","BigRoosterWorkouts/Views/Components/RestTimerView.swift","BigRoosterWorkouts/Views/Components/WorkoutReminderSettingsControls.swift"]},{"name":"Shared Utilities & UI Helpers","slug":"shared-utilities-ui-helpers","files":["BigRoosterWorkouts/Utilities/BackgroundUndoManager.swift","BigRoosterWorkouts/Utilities/CustomToolBarModifier.swift","BigRoosterWorkouts/Utilities/ExerciseSearchService.swift","BigRoosterWorkouts/Utilities/PreferenceKey.swift","BigRoosterWorkouts/Utilities/Router.swift","BigRoosterWorkouts/Utilities/SeedDataImporter.swift","BigRoosterWorkouts/Utilities/SharedContainer.swift","BigRoosterWorkouts/Utilities/SwiftUIConditionalHelpers.swift","BigRoosterWorkouts/Utilities/TextTransition.swift","BigRoosterWorkouts/Utilities/WorkoutGoalPreferences.swift","BigRoosterWorkouts/Utilities/WorkoutPlanImporter.swift","BigRoosterWorkouts/Utilities/UIScreen+ScreenCorners.swift"]},{"name":"Vertical Split Layout","slug":"vertical-split-layout","files":["BigRoosterWorkouts/Utilities/VerticalSplit/Accessories.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Helpers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Modifiers.swift","BigRoosterWorkouts/Utilities/VerticalSplit/SplitDetent.swift","BigRoosterWorkouts/Utilities/VerticalSplit/VerticalSplitView.swift","BigRoosterWorkouts/Utilities/VerticalSplit/Wrappers.swift"]},{"name":"Profile & Settings","slug":"profile-settings","files":["BigRoosterWorkouts/Views/ProfileView.swift","BigRoosterWorkouts/Views/ProfileDashboardViews.swift","BigRoosterWorkouts/Views/SettingsView.swift","BigRoosterWorkouts/Views/CreditsView.swift"]},{"name":"Data & Seed Assets","slug":"data-seed-assets","files":["BigRoosterWorkouts/Data/mongo_seed_validation_summary.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.backup.json","BigRoosterWorkouts/Data/stretch_exercise_dataset.json","BigRoosterWorkouts/Data/workout_programs.txt"]},{"name":"Scripts","slug":"scripts","files":["Scripts/convert_mongo_seed_data.py","Scripts/convert_workout_plans.py"]},{"name":"Other","slug":"other","files":[],"children":[{"name":"Other — AGENTS.md","slug":"other-agents-md","files":["AGENTS.md"]},{"name":"Other — BigRoosterWorkouts.xcodeproj","slug":"other-bigroosterworkouts-xcodeproj","files":["BigRoosterWorkouts.xcodeproj/project.pbxproj"]},{"name":"Other — project.xcworkspace","slug":"other-project-xcworkspace","files":["BigRoosterWorkouts.xcodeproj/project.xcworkspace/contents.xcworkspacedata","BigRoosterWorkouts.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"]},{"name":"Other — xcshareddata","slug":"other-xcshareddata","files":["BigRoosterWorkouts.xcodeproj/xcshareddata/xcschemes/BigRoosterWorkouts.xcscheme"]},{"name":"Other — Assets.xcassets","slug":"other-assets-xcassets","files":["BigRoosterWorkouts/Assets.xcassets/AccentColor.colorset/Contents.json","BigRoosterWorkouts/Assets.xcassets/AppIcon.appiconset/Contents.json","BigRoosterWorkouts/Assets.xcassets/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_evening.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_clear_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_cloudy_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_fog_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_rain_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_snow_day.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/HomeWeatherBackgrounds/home_weather_storm_night.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/100 workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/30 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/7 day streak.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/first workout completed.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/longest time record.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/badges/new record calories burned.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-abductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-back-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-biceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-brachioradialis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-calves-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-chest-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-medial-lateral-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-deltoid-posterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-forearms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gastrocnemius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-gluteus-maximus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hamstrings-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-adductors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hip-flexors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-hips-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-infraspinatus-and-teres-minor-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-latissimus-dorsi-and-teres-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-levator-scapulae-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-neck-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pectoralis-major-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-pronators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-quadriceps-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-rhomboids-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-serratus-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-shoulders-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-soleus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-splenius-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-sternocleidomastoid-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-subscapularis-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supinators-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-supraspinatus-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-thighs-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-tibialis-anterior-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-lower-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-middle-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-trapezius-upper-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-triceps-brachii-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-upper-arms-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-extensors-posterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-anterior-outer-inner-muscles.imageset/Contents.json","BigRoosterWorkouts/Assets.xcassets/muscles/muscle-wrist-flexors-posterior-outer-inner-muscles.imageset/Contents.json"]},{"name":"Other — BigRoosterWorkouts","slug":"other-bigroosterworkouts","files":["BigRoosterWorkouts/BigRoosterWorkouts.entitlements"]},{"name":"Other — Views","slug":"other-views","files":["BigRoosterWorkouts/Views/BackgroundBuilderView.swift","BigRoosterWorkouts/Views/Components/FilterChip.swift","BigRoosterWorkouts/Views/Components/SearchBar.swift","BigRoosterWorkouts/Views/Components/UpcomingWorkoutPreviewCard.swift","BigRoosterWorkouts/Views/Components/WorkoutGoalSettingsControls.swift","BigRoosterWorkouts/Views/Components/WorkoutRecommendationPreviewCard.swift","BigRoosterWorkouts/Views/DatePickerSheet.swift","BigRoosterWorkouts/Views/FilterSheet.swift"]},{"name":"Other — BigRoosterWorkoutsTests","slug":"other-bigroosterworkoutstests","files":["BigRoosterWorkoutsTests/HomeBackgroundResolverTests.swift","BigRoosterWorkoutsTests/OpenMeteoHomeBackgroundWeatherProviderTests.swift","BigRoosterWorkoutsTests/SwiftUIConditionalHelpersTests.swift","BigRoosterWorkoutsTests/ThemeAppearanceTests.swift","BigRoosterWorkoutsTests/UnifiedWorkoutRecommendationServiceTests.swift","BigRoosterWorkoutsTests/WorkoutContentCreationTests.swift","BigRoosterWorkoutsTests/WorkoutHistoryRepositoryTests.swift","BigRoosterWorkoutsTests/WorkoutPlanFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutReminderFeatureTests.swift","BigRoosterWorkoutsTests/WorkoutSessionCoordinatorTests.swift","BigRoosterWorkoutsTests/WorkoutStructurePersistenceTests.swift"]},{"name":"Other — CLAUDE.md","slug":"other-claude-md","files":["CLAUDE.md"]},{"name":"Other — GITHUB_ISSUE_CHECKLIST.md","slug":"other-github-issue-checklist-md","files":["GITHUB_ISSUE_CHECKLIST.md"]},{"name":"Other — TestPlan.xctestplan","slug":"other-testplan-xctestplan","files":["TestPlan.xctestplan"]},{"name":"Other — build_output.txt","slug":"other-build-output-txt","files":["build_output.txt"]},{"name":"Other — docs","slug":"other-docs","files":["docs/APP_GOAL_PROGRESS_REPORT.md","docs/CORE_WORKOUT_APP_FUNCTIONALITY_AUDIT.md","docs/PR_REVIEW_CHECKLIST.md"]},{"name":"Other — changes","slug":"other-changes","files":["openspec/changes/add-home-weather-backgrounds/design.md","openspec/changes/add-home-weather-backgrounds/proposal.md","openspec/changes/add-home-weather-backgrounds/specs/home-weather-backgrounds/spec.md","openspec/changes/add-home-weather-backgrounds/tasks.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/design.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/proposal.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/specs/workout-start-sheet-plan-day-actions/spec.md","openspec/changes/add-plan-day-actions-to-workout-start-sheet/tasks.md","openspec/changes/add-plan-day-selection-and-skip/design.md","openspec/changes/add-plan-day-selection-and-skip/proposal.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-selection/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-day-skip/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-scheduling-core/spec.md","openspec/changes/add-plan-day-selection-and-skip/specs/plan-start-advancement-fix/spec.md","openspec/changes/add-plan-day-selection-and-skip/tasks.md","openspec/changes/add-upcoming-workout-reminders/design.md","openspec/changes/add-upcoming-workout-reminders/proposal.md","openspec/changes/add-upcoming-workout-reminders/specs/workout-reminders/spec.md","openspec/changes/add-upcoming-workout-reminders/tasks.md","openspec/changes/add-workout-plan-search-tabs/design.md","openspec/changes/add-workout-plan-search-tabs/proposal.md","openspec/changes/add-workout-plan-search-tabs/specs/workout-plan-browsing/spec.md","openspec/changes/add-workout-plan-search-tabs/tasks.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/design.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/proposal.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/specs/achievement-badge-assets/spec.md","openspec/changes/archive/2026-05-12-complete-profile-achievement-badge-assets/tasks.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/design.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/proposal.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/specs/swiftui-conditional-helpers/spec.md","openspec/changes/archive/2026-05-17-add-swiftui-conditional-helpers/tasks.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/design.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/proposal.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-component-primitives/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-design-tokens/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/specs/shadcn-swiftui-screen-migration/spec.md","openspec/changes/archive/2026-05-17-adopt-shadcn-style-design-system/tasks.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/design.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/proposal.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/specs/dead-code-cleanup/spec.md","openspec/changes/archive/2026-05-17-remove-unused-ui-components/tasks.md","openspec/changes/assess-original-product-goal-progress/design.md","openspec/changes/assess-original-product-goal-progress/proposal.md","openspec/changes/assess-original-product-goal-progress/specs/product-goal-progress-audit/spec.md","openspec/changes/assess-original-product-goal-progress/tasks.md","openspec/changes/core-workout-refactor/design.md","openspec/changes/core-workout-refactor/proposal.md","openspec/changes/core-workout-refactor/specs/workout-session-behavior-coverage/spec.md","openspec/changes/core-workout-refactor/specs/workout-session-domain/spec.md","openspec/changes/core-workout-refactor/tasks.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/design.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/proposal.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/specs/workout-history-typography/spec.md","openspec/changes/correct-workout-history-fonts-with-rest-of-app/tasks.md","openspec/changes/fix-home-wallpaper-contrast/design.md","openspec/changes/fix-home-wallpaper-contrast/proposal.md","openspec/changes/fix-home-wallpaper-contrast/specs/home-wallpaper-readability/spec.md","openspec/changes/fix-home-wallpaper-contrast/tasks.md","openspec/changes/fix-plan-readiness-and-adherence/design.md","openspec/changes/fix-plan-readiness-and-adherence/proposal.md","openspec/changes/fix-plan-readiness-and-adherence/specs/plan-readiness/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/secondary-workout-recommendations/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-goals/spec.md","openspec/changes/fix-plan-readiness-and-adherence/specs/workout-plan-adherence/spec.md","openspec/changes/fix-plan-readiness-and-adherence/tasks.md","openspec/changes/fix-theme-appearance-sync/design.md","openspec/changes/fix-theme-appearance-sync/proposal.md","openspec/changes/fix-theme-appearance-sync/specs/app-theme-appearance/spec.md","openspec/changes/fix-theme-appearance-sync/tasks.md","openspec/changes/import-mongo-seed-data/design.md","openspec/changes/import-mongo-seed-data/proposal.md","openspec/changes/import-mongo-seed-data/specs/mongo-seed-data-import/spec.md","openspec/changes/import-mongo-seed-data/tasks.md","openspec/changes/improve-empty-workout-placeholder/design.md","openspec/changes/improve-empty-workout-placeholder/proposal.md","openspec/changes/improve-empty-workout-placeholder/specs/empty-workout-placeholder/spec.md","openspec/changes/improve-empty-workout-placeholder/tasks.md","openspec/changes/improve-home-plan-card-and-stats-widgets/design.md","openspec/changes/improve-home-plan-card-and-stats-widgets/proposal.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-active-plan-card/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/specs/home-stats-widgets/spec.md","openspec/changes/improve-home-plan-card-and-stats-widgets/tasks.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/design.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/proposal.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-recommendation-card-copy/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/plan-schedule-rebasing/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/specs/workout-start-sheet-actions/spec.md","openspec/changes/improve-plan-recommendation-card-scheduling-copy/tasks.md","openspec/changes/namespace-home-weather-wallpapers/design.md","openspec/changes/namespace-home-weather-wallpapers/proposal.md","openspec/changes/namespace-home-weather-wallpapers/specs/home-weather-wallpaper-assets/spec.md","openspec/changes/namespace-home-weather-wallpapers/tasks.md","openspec/changes/normalize-seed-data-field-names/design.md","openspec/changes/normalize-seed-data-field-names/proposal.md","openspec/changes/normalize-seed-data-field-names/specs/seed-data-normalization/spec.md","openspec/changes/normalize-seed-data-field-names/tasks.md","openspec/changes/plan-exercise-creation/design.md","openspec/changes/plan-exercise-creation/proposal.md","openspec/changes/plan-exercise-creation/specs/exercise-creation-domain/spec.md","openspec/changes/plan-exercise-creation/specs/workout-content-ownership/spec.md","openspec/changes/plan-exercise-creation/specs/workout-plan-creation/spec.md","openspec/changes/plan-exercise-creation/tasks.md","openspec/changes/redesign-profile-view/design.md","openspec/changes/redesign-profile-view/proposal.md","openspec/changes/redesign-profile-view/specs/profile-dashboard/spec.md","openspec/changes/redesign-profile-view/tasks.md","openspec/changes/refactor-workout-session-actionbar/design.md","openspec/changes/refactor-workout-session-actionbar/proposal.md","openspec/changes/refactor-workout-session-actionbar/specs/workout-session-actionbar-layout/spec.md","openspec/changes/refactor-workout-session-actionbar/tasks.md","openspec/changes/remove-food-step-tracking/design.md","openspec/changes/remove-food-step-tracking/proposal.md","openspec/changes/remove-food-step-tracking/specs/product-boundary-cleanup/spec.md","openspec/changes/remove-food-step-tracking/tasks.md","openspec/changes/stabilize-plan-builder-metadata-inputs/design.md","openspec/changes/stabilize-plan-builder-metadata-inputs/proposal.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-metadata-inputs/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/specs/plan-builder-reorder-stability/spec.md","openspec/changes/stabilize-plan-builder-metadata-inputs/tasks.md","openspec/changes/standardize-app-design-system/design.md","openspec/changes/standardize-app-design-system/proposal.md","openspec/changes/standardize-app-design-system/specs/app-color-token-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-spacing-standardization/spec.md","openspec/changes/standardize-app-design-system/specs/app-typography-standardization/spec.md","openspec/changes/standardize-app-design-system/tasks.md","openspec/changes/streamline-app-ui-colors/design.md","openspec/changes/streamline-app-ui-colors/proposal.md","openspec/changes/streamline-app-ui-colors/specs/app-ui-color-consistency/spec.md","openspec/changes/streamline-app-ui-colors/tasks.md","openspec/changes/track-workout-plan-progress/design.md","openspec/changes/track-workout-plan-progress/proposal.md","openspec/changes/track-workout-plan-progress/specs/workout-plan-progress/spec.md","openspec/changes/track-workout-plan-progress/tasks.md","openspec/changes/unify-workout-recommendations/design.md","openspec/changes/unify-workout-recommendations/proposal.md","openspec/changes/unify-workout-recommendations/specs/workout-recommendations/spec.md","openspec/changes/unify-workout-recommendations/tasks.md"]},{"name":"Other — openspec","slug":"other-openspec","files":["openspec/config.yaml"]},{"name":"Other — specs","slug":"other-specs","files":["openspec/specs/achievement-badge-assets/spec.md","openspec/specs/dead-code-cleanup/spec.md","openspec/specs/shadcn-swiftui-component-primitives/spec.md","openspec/specs/shadcn-swiftui-design-tokens/spec.md","openspec/specs/shadcn-swiftui-screen-migration/spec.md","openspec/specs/swiftui-conditional-helpers/spec.md"]},{"name":"Other — skills-lock.json","slug":"other-skills-lock-json","files":["skills-lock.json"]}]}]};
(function() {
var activePage = 'overview';
document.addEventListener('DOMContentLoaded', function() {
mermaid.initialize({ startOnLoad: false, theme: 'neutral', securityLevel: 'loose' });
renderMeta();
renderNav();
document.getElementById('menu-toggle').addEventListener('click', function() {
document.getElementById('sidebar').classList.toggle('open');
});
if (location.hash && location.hash.length > 1) {
activePage = decodeURIComponent(location.hash.slice(1));
}
navigateTo(activePage);
});
function renderMeta() {
if (!META) return;
var el = document.getElementById('meta-info');
var parts = [];
if (META.generatedAt) {
parts.push(new Date(META.generatedAt).toLocaleDateString());
}
if (META.model) parts.push(META.model);
if (META.fromCommit) parts.push(META.fromCommit.slice(0, 8));
el.textContent = parts.join(' \u00b7 ');
}
function renderNav() {
var container = document.getElementById('nav-tree');
var html = '<div class="nav-section">';
html += '<a class="nav-item overview" data-page="overview" href="#overview">Overview</a>';
html += '</div>';
if (TREE.length > 0) {
html += '<div class="nav-group-label">Modules</div>';
html += buildNavTree(TREE);
}
container.innerHTML = html;
container.addEventListener('click', function(e) {
var target = e.target;
while (target && !target.dataset.page) { target = target.parentElement; }
if (target && target.dataset.page) {
e.preventDefault();
navigateTo(target.dataset.page);
}
});
}
function buildNavTree(nodes) {
var html = '';
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
html += '<div class="nav-section">';
html += '<a class="nav-item" data-page="' + escH(node.slug) + '" href="#' + encodeURIComponent(node.slug) + '">' + escH(node.name) + '</a>';
if (node.children && node.children.length > 0) {
html += '<div class="nav-children">' + buildNavTree(node.children) + '</div>';
}
html += '</div>';
}
return html;
}
function escH(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function navigateTo(page) {
activePage = page;
location.hash = encodeURIComponent(page);
var items = document.querySelectorAll('.nav-item');
for (var i = 0; i < items.length; i++) {
if (items[i].dataset.page === page) {
items[i].classList.add('active');
} else {
items[i].classList.remove('active');
}
}
var contentEl = document.getElementById('content');
var md = PAGES[page];
if (!md) {
contentEl.innerHTML = '<div class="empty-state"><h2>Page not found</h2><p>' + escH(page) + '.md does not exist.</p></div>';
return;
}
contentEl.innerHTML = marked.parse(md);
// Rewrite .md links to hash navigation
var links = contentEl.querySelectorAll('a[href]');
for (var i = 0; i < links.length; i++) {
var href = links[i].getAttribute('href');
if (href && href.endsWith('.md') && href.indexOf('://') === -1) {
var slug = href.replace(/\.md$/, '');
links[i].setAttribute('href', '#' + encodeURIComponent(slug));
(function(s) {
links[i].addEventListener('click', function(e) {
e.preventDefault();
navigateTo(s);
});
})(slug);
}
}
// Convert mermaid code blocks into mermaid divs
var mermaidBlocks = contentEl.querySelectorAll('pre code.language-mermaid');
for (var i = 0; i < mermaidBlocks.length; i++) {
var pre = mermaidBlocks[i].parentElement;
var div = document.createElement('div');
div.className = 'mermaid';
div.textContent = mermaidBlocks[i].textContent;
pre.parentNode.replaceChild(div, pre);
}
try { mermaid.run({ querySelector: '.mermaid' }); } catch(e) {}
window.scrollTo(0, 0);
document.getElementById('sidebar').classList.remove('open');
}
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment