Restructure into a three-tab app with Progress, goals, and Meditation

The UX redesign's first landing (spec in UX-REDESIGN.md): ContentView
becomes a Today / Progress / Settings TabView, "Routine" replaces
"Split" in every user-facing string and view name (code-level types
keep their names), and workout starting moves to shared
WorkoutStarter / StartedWorkoutNavigator plumbing.

- New Progress tab: weekly goal streaks, workout trends, per-exercise
  weight progression, achievements, and the full history list
  (WorkoutLogsView -> WorkoutHistoryView).
- Goals: stable categories workouts roll up to, managed from Settings.
- New Meditation exercise + starter routine; timed sits record to
  Apple Health as Mind & Body sessions.

Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4
This commit is contained in:
2026-07-11 07:53:01 -04:00
parent 5c201289fb
commit 6e440317c4
97 changed files with 5354 additions and 1291 deletions
+21 -10
View File
@@ -15,7 +15,7 @@ struct SettingsView: View {
@Environment(SyncEngine.self) private var sync
@Environment(AppServices.self) private var services
@Query private var splits: [Split]
@Query private var routines: [Routine]
@AppStorage("restSeconds") private var restSeconds: Int = 45
@AppStorage("doneCountdownSeconds") private var doneCountdownSeconds: Int = 5
@@ -78,12 +78,23 @@ struct SettingsView: View {
// MARK: - Library Section
Section(header: Text("Library")) {
NavigationLink {
SplitListView()
GoalsListView()
} label: {
HStack {
Label("Splits", systemImage: "dumbbell.fill")
Label("Goals", systemImage: "target")
Spacer()
Text("\(splits.count)")
Text("\(GoalKind.allCases.count)")
.foregroundStyle(.secondary)
}
}
NavigationLink {
RoutineListView()
} label: {
HStack {
Label("Routines", systemImage: "dumbbell.fill")
Spacer()
Text("\(routines.count)")
.foregroundStyle(.secondary)
}
}
@@ -100,7 +111,7 @@ struct SettingsView: View {
}
}
// MARK: - Starter Splits Section
// MARK: - Starter Routines Section
Section {
Button {
Task {
@@ -109,12 +120,12 @@ struct SettingsView: View {
let n = await sync.restoreSeeds()
isRestoringSeeds = false
restoreSeedsMessage = n == 0
? "All starter splits are already present."
: "Restored \(n) starter split\(n == 1 ? "" : "s")."
? "All starter routines are already present."
: "Restored \(n) starter routine\(n == 1 ? "" : "s")."
}
} label: {
HStack {
Label("Restore Starter Splits", systemImage: "arrow.counterclockwise")
Label("Restore Starter Routines", systemImage: "arrow.counterclockwise")
if isRestoringSeeds {
Spacer()
ProgressView()
@@ -123,12 +134,12 @@ struct SettingsView: View {
}
.disabled(isRestoringSeeds || sync.iCloudStatus != .available)
} header: {
Text("Starter Splits")
Text("Starter Routines")
} footer: {
if let restoreSeedsMessage {
Text(restoreSeedsMessage)
} else {
Text("Brings back the bundled starter splits you've deleted. Splits you've edited or created are never touched.")
Text("Brings back the bundled starter routines you've deleted. Routines you've edited or created are never touched.")
}
}