Add the Library tab: reorderable routines pane and searchable grouped exercises pane

A fourth tab promotes routine and exercise management out of Settings.
LibraryView switches between two segments on one navigation stack. The
routines pane (RoutineListView reborn as RoutinesLibraryView) sorts by
the user's order, gains drag-to-reorder writing only changed orders,
swipe actions for duplicate/edit/delete, a Starter badge on bundled
seeds, and a last-trained caption. The exercises pane groups the library
into curated category sections searchable by name, category, or muscle.

Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
This commit is contained in:
2026-07-15 10:58:57 -04:00
parent 16b61946d5
commit 586804f771
6 changed files with 285 additions and 151 deletions
@@ -8,17 +8,40 @@
import SwiftUI
import SwiftData
/// The Exercises library screen, pushed from Settings Library: every exercise with
/// a bundled motion rig (the same catalog the picker offers), each opening its
/// reference page.
/// The Exercises segment of the Library tab: every bundled exercise (the same
/// catalog the picker offers), grouped into `ExerciseCatalog`'s curated sections and
/// searchable by name, category, or target muscle. Tapping a row opens its reference
/// page. Pushed from `LibraryView`, so it relies on that view's `NavigationStack`.
struct ExerciseLibraryView: View {
@State private var searchText = ""
private var sections: [ExerciseCatalogSection] {
ExerciseCatalog.sections(matching: searchText)
}
var body: some View {
List(ExerciseMotionLibrary.exerciseNames, id: \.self) { name in
NavigationLink(name) {
ExerciseLibraryDetailView(exerciseName: name)
Group {
if sections.isEmpty {
ContentUnavailableView.search(text: searchText)
} else {
List {
ForEach(sections, id: \.title) { section in
Section(section.title) {
ForEach(section.names, id: \.self) { name in
NavigationLink(name) {
ExerciseLibraryDetailView(exerciseName: name)
}
}
}
}
}
}
}
.navigationTitle("\(ExerciseMotionLibrary.exerciseNames.count) Exercises")
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Name, category, or muscle")
}
}