From 55d2f217e3bb7e5e8bf59f157bc2966df7ad9977 Mon Sep 17 00:00:00 2001 From: DeccanHYD Date: Tue, 6 Oct 2026 03:21:11 +0200 Subject: [PATCH] feat(web): add native exercise library management --- .../2026-10-06-exercise-library-management.md | 13 + web/src/App.tsx | 10 +- web/src/data/schema.ts | 1 + web/src/data/store.ts | 47 +- web/src/domain/codecs.ts | 8 + web/src/domain/types.ts | 2 + web/src/features/ExerciseLibrary.tsx | 417 ++++++++++++++++++ web/src/features/Plans.tsx | 10 +- web/src/features/Settings.tsx | 10 +- web/src/styles.css | 35 ++ web/src/ui/components.tsx | 4 + web/src/ui/navigation.ts | 1 + web/tests/exercise-library-store.test.ts | 135 ++++++ web/tests/exercise-library-ui.test.tsx | 285 ++++++++++++ web/tests/navigation-parity.test.ts | 1 + 15 files changed, 963 insertions(+), 16 deletions(-) create mode 100644 docs/reviews/2026-10-06-exercise-library-management.md create mode 100644 web/src/features/ExerciseLibrary.tsx create mode 100644 web/tests/exercise-library-store.test.ts create mode 100644 web/tests/exercise-library-ui.test.tsx diff --git a/docs/reviews/2026-10-06-exercise-library-management.md b/docs/reviews/2026-10-06-exercise-library-management.md new file mode 100644 index 0000000..5bf1c0b --- /dev/null +++ b/docs/reviews/2026-10-06-exercise-library-management.md @@ -0,0 +1,13 @@ +# Exercise library management parity + +Settings now opens a separate exercise-library route instead of a dismiss-on-pick chooser. It follows Kotlin ExerciseLibraryScreen: All/Favorites/Custom and BW-only scopes, removable active chips, a filter sheet for muscles/equipment/category/movement/difficulty, 180 ms search, gym equipment exclusion, favorite-first/name ordering, recently completed exercise search chips and row navigation to progress. It uses Compose star/filter/delete paths, 72px minimum rows, 44px controls, shared themes and sheet/route transitions. + +All results are accessible. Web renders 100 at a time with Show more instead of Android's LazyColumn virtualization. Creation reuses the existing custom form with unmatched query prefilled; saving retains the library destination. Editing and tutorial links remain follow-up work; no new native tracking editor has been assumed. + +Favorites persist in the profile and canonical native favorite_exercises JSON setting, including double-encoded older JSON. Native settings override stale web-extension favorites during import. Each favorite mutation reads the current profile in a write transaction. Custom creation validates normalized duplicate names within its transaction, preventing simultaneous creates from duplicating a movement. Existing catalog overrides and recorded snapshots retain their behavior. + +Confirmed custom deletion protects built-ins, plan references and every workout status, including imported name-only references. Deletion, restored-catalog removal, favorite cleanup and reminder cleanup commit together. Referenced or failed writes keep the exercise and show the failure within the confirmation sheet. A removed restored custom exercise cannot return on bootstrap. + +Verification: 53 files / 263 unit tests, TypeScript/build and 185-file output checks. Tests cover concurrent duplicate creation/favorites, native and browser backup transfer, reference protection, transaction rollback, restored entry deletion, actual Settings navigation, filters, creation/confirmation/error behavior, recent names and results past the old limit. Isolated Chrome at 320px verifies favorite reload, filter scopes, creation/reload/cancel/delete, 200 accessible rows, progress/back origins and no horizontal overflow. Screenshots under ignored web/output/playwright/library*.png. Zero console errors/warnings. An older local preview worker displayed an update notice during the first QA attempt; normal Update & reload resolved that obstruction without changing storage or forcing clicks. Closing CI and deployment evidence is in the root handoff. + +P03/P14 remain partially open for tutorial/detail and editor workflows; P16/P17 still require native/web differential visual and motion checks. Native source, signed pre-alpha.9 APK and dirty UnifiedPort remain unchanged. diff --git a/web/src/App.tsx b/web/src/App.tsx index 0ee63d1..9403708 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -16,6 +16,7 @@ import { } from "./features/Progress"; import { Recovery, Ledger } from "./features/Recovery"; import { Settings } from "./features/Settings"; +import { ExerciseLibrary } from "./features/ExerciseLibrary"; import { Intelligence } from "./features/Intelligence"; import { HistoricalWorkoutEditor, WorkoutCalendar } from "./features/history/HistoryTools"; import { ExerciseProgress } from "./features/stats/ExerciseProgress"; @@ -27,6 +28,7 @@ import { backFrom } from "./ui/navigation"; const tabs = ["Home", "Plans", "Log", "Stats", "Settings"]; const tabRoutes = tabs.map((tab) => tab.toLowerCase()); const detailTitles: Record = { + library: "EXERCISE LIBRARY", workout: "ACTIVE WORKOUT", recovery: "MUSCLE RECOVERY", ledger: "IRON LEDGER", @@ -228,7 +230,7 @@ export function App() { ); const active = data.workouts.find((w) => w.status === "active"); const detail = !["home", "plans", "log", "stats", "settings"].includes(route); - const selectedTabRoute = route.startsWith("plan/") + const selectedTabRoute = route === "library" ? "settings" : route.startsWith("plan/") ? "plans" : route.startsWith("history/") || route === "calendar" ? "log" @@ -259,6 +261,8 @@ export function App() { ) : route === "settings" ? ( + ) : route === "library" ? ( + ) : route === "recovery" ? ( ) : route === "ledger" ? ( @@ -324,13 +328,13 @@ export function App() { label="Back" onClick={() => backFrom(route)} /> - + {route === "library" ?

Exercise library

: {route.startsWith("plan/") ? "EDIT PLAN" : route.startsWith("history/") ? "WORKOUT" : detailTitles[route] ?? "IRONLOG"} - +
}