From 249790b8c8f3cf284971e11a1964d58ada72830d Mon Sep 17 00:00:00 2001 From: Christopher Rotnes Date: Sat, 16 May 2026 00:05:45 +0200 Subject: [PATCH 1/3] =?UTF-8?q?chore(#251):=20codebase=20cleanup=20?= =?UTF-8?q?=E2=80=94=20dead=20files,=20stale=20docs,=20wrong=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 5 + CLAUDE.md | 33 +- README.md | 11 +- app/package.json | 2 +- app/src/components/Bibliotek.jsx | 469 ---------------------------- app/src/components/OvelseDetail.jsx | 143 --------- 6 files changed, 21 insertions(+), 642 deletions(-) delete mode 100644 app/src/components/Bibliotek.jsx delete mode 100644 app/src/components/OvelseDetail.jsx diff --git a/CHANGELOG.md b/CHANGELOG.md index c370d7e..f95c90e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to Workout Lens are documented here. +## [1.5.11] — 2026-05-16 + +### Developer / Infrastructure +- **Codebase cleanup (issue #251)** — deleted `Bibliotek.jsx` and `OvelseDetail.jsx` (both unreferenced dead code); fixed wrong file paths in README project structure table (`carbon-tokens.css`, `app.css`, `staticwebapp.config.json`); bumped `package.json` version from stale `1.1.0-rc.1` to `1.5.10`; removed historical migration narrative from CLAUDE.md (Carbon "What was done" section, gym-wide RLS policy diff, sets/reps removal note, react-day-picker/Bebas Neue removed list); fixed stale API security header description (now correctly documents `X-Supabase-Token`). + ## [1.5.10] — 2026-05-15 ### Security diff --git a/CLAUDE.md b/CLAUDE.md index e63e221..f27540b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,7 @@ Canonical definitions for domain terms. When a term is ambiguous in an issue or - **AI:** Anthropic Claude API — proxied via Azure Function (server-side); model IDs managed in `app/src/lib/prompts.js` - **Hosting:** Azure Static Web Apps — **live at [workout.umulig.org](https://workout.umulig.org)** - **CI/CD:** GitHub Actions — push to `master` → auto-deploy to Azure SWA -- **i18n:** `react-i18next` — three locales: `nb` (Norwegian, default), `en` (English), `fa` (Persian/RTL); locale files in `app/public/locales/`; singleton in `app/src/lib/i18n.js`; all date/time formatting via `Intl.DateTimeFormat`; `date-fns` has been removed entirely — use `toIsoDate()` and `isoWeekMonday()` from `utils.js` for any new date-string / ISO-week-start needs +- **i18n:** `react-i18next` — three locales: `nb` (Norwegian, default), `en` (English), `fa` (Persian/RTL); locale files in `app/public/locales/`; singleton in `app/src/lib/i18n.js`; all date/time formatting via `Intl.DateTimeFormat`; use `toIsoDate()` and `isoWeekMonday()` from `utils.js` for date-string / ISO-week needs (no `date-fns`) ## Muscle ID system (17 total) ``` @@ -87,17 +87,13 @@ Each has a `view` (front/back) and Norwegian `label` in the `MUSCLES` object in ## Carbon design system -Fully migrated to IBM Carbon Design System (issue #8, resolved 2026-04-29). +Uses `@carbon/react` and `@carbon/icons-react`. IBM Plex fonts (Sans, Mono, Serif, Condensed) bundled locally in `app/public/fonts/` — no Google Fonts, no CDN. -### What was done -- Installed `@carbon/react` and `@carbon/icons-react` -- IBM Plex fonts (Sans, Mono, Serif, Condensed) bundled locally in `app/public/fonts/` — no Google Fonts, no CDN - `app/src/styles/carbon-tokens.css` — all Carbon CSS variables for g10 (light) and g100 (dark) themes, plus `@font-face` declarations; font URLs use `/fonts/...` (Vite public-dir absolute paths) - `app/src/theme.jsx` — `ThemeProvider` sets `data-theme="g10"` or `data-theme="g100"` on ``, persists to `localStorage`. Default (no saved preference): respects OS `prefers-color-scheme` — dark OS → g100, light OS → g10. This is intentional; the app does not force dark mode on first visit. - `Login.jsx` → Carbon `TextInput`, `Button`, `InlineNotification`, `Email` icon; `getDailyQuote()` renders a date-aware motivational quote below the subtitle — English only (hardcoded; language preference is unknown before login); keyed by `MM-DD` for special dates (`01-01`, `12-24`), falls back to a per-weekday quote; 13px italic `var(--cds-text-secondary)` - `MuscleMap.jsx` → orchestrator (352 lines): `useReducer` + all 4 `useEffect` hooks + `addImage`/`handleFiles`/`analyze`/`confirm`/`recommend` callbacks + step-indicator strip; delegates rendering to `MuscleMapUpload`, `MuscleMapConfirm`, `MuscleMapResult`; exports `initialState`, `reducer`, `localDateStr`. Sub-components: `MuscleMapUpload.jsx` (dropzone, image grid, ghost shortcuts, analyze CTA); `MuscleMapConfirm.jsx` (layer-02 wrapper, today/other-day pill, date picker, gym-class selector, exercise list, confidence dots, save CTA; includes `getConfidenceColor`); `MuscleMapResult.jsx` (KPI strip, save status, body map, muscle chips, exercise list, recommendations). - `History.jsx` → orchestrator (525 lines): all state (sessions, selectedDate, muscleFilter, sessionEdits Map, classHistory Map), all callbacks, session-row header rendering; delegates expanded panel to `SessionEditPanel` and calendar to `MonthGrid`. `MonthGrid.jsx` (103 lines): 7-column CSS grid heatmap, today/selected outlines, interactive day buttons; includes `calHeatColor`. `SessionEditPanel.jsx` (218 lines): gym-class selector, `BodyPanel`, hover-detail card, exercise list via `ExerciseRowWithAutocomplete`, re-upload button, class-history panel, dirty-state save/discard bar; imports `checkGymCalendarConflict` directly. Per-session edit state is `Map` (no global `editMode` boolean); `PageHeading` has `minHeight: 72` to prevent layout shift; all date formatting via `Intl.DateTimeFormat`. -- `Bibliotek.jsx` → **RETIRED** (issue #174). Replaced by the SetSammen ecosystem below. File kept in the repo for reference but no longer referenced by `App.jsx`. - `SetSammen.jsx` → landing page for the «Sett sammen»-tab. Two-column grid of `ActionCard` components. Two-color system: magenta (`--accent`) = gruppetimer, green (`--exercise`) = øvelser. Props: `onShowGruppetimePicker`, `onShowOvelsePicker`. - `GruppetimePicker.jsx` → lists all templates with live search, mini front-view `BodySVG` thumbnail per row, and a featured magenta «Ny gruppetime» card that expands to a `TextInput` + create form inline. On row click → `onEditTemplate(tpl)`. - `OvelsePicker.jsx` → lists all library exercises with region filter chips (Alle / Overkropp / Kjerne / Underkropp / Kondisjon; chips with count=0 are hidden except «Alle»), debounced search, and a featured green «Ny øvelse» card. Exercise rows show up to 3 primary muscle names + «BRUKT I N GT» count (colored `--accent-soft` to visually separate it from the muscle names). Clicking a row opens `ExerciseForm` directly for editing (no intermediate detail screen). Uses `fetchExerciseTemplateCounts()` to batch-load template usage counts on mount. @@ -109,12 +105,9 @@ Fully migrated to IBM Carbon Design System (issue #8, resolved 2026-04-29). - `BodySVG` / `HeatmapBodySVG` muscle highlights: primary → `var(--heat-4)` solid green, secondary → diagonal blue hatch (`#001d6c` base + `#4589ff` lines). `HeatmapBodySVG` accepts `onHover(id|null)` and `hovered` props — when `onHover` is provided the internal floating tooltip is suppressed and the caller manages the detail card. - `Home.jsx` → `SectionLabel` + `PageHeading` headings; last session card with gym-class identity hero; 7-day weekly strip with heat colors — clicking a day that has a session navigates to History pre-selected on that date; `fetchThisWeekSessions` in `db.js` - `Report.jsx` → `SectionLabel` eyebrow with period + active day filters on two separate `display:block` spans; three separate `flexWrap: wrap` filter rows (period / weekdays / session types) with `1px solid var(--border-subtle-wl)` top borders between groups; "Nullstill filter" always rendered (opacity-toggled); KPI tiles → heatmap body → hover detail → heat legend → frequency table → gap callout card (with `AccentChip` per untrained muscle) → recommendation button → recs list; when all primary muscles trained shows positive fallback message; when some muscles secondary-only shows those as blue tags; recommendation rows have 3px accent left strip + round `+` button that saves the exercise inline via `saveLibraryExercise`; "Oppdater anbefalinger" ghost button (`Renew` icon) below the recs list — re-runs Claude call and overwrites the cache entry; no `StickyCta`; recs are persisted in the shared `recommendation_cache` Supabase table (see data model) and restored on mount/filter-change via `fetchRecsCache`; prefill prop applied on mount via `useRef` — supports `periodDays`, `selectedDays`, `selectedTypes`, `weekday`, `sessionType`; `KpiTile` (42px Plex Light value); `muscleLastDate` in useMemo -- `History.jsx` / `MonthGrid.jsx` → `MonthGrid` is now in its own file (`MonthGrid.jsx`); `sessionCountMap` useMemo in parent; `SectionLabel` + `PageHeading` at top; removed `react-day-picker` dependency entirely - `PageShell.jsx` → exports: `SectionLabel` (mono 12px, 0.16em tracking, 3px `var(--accent)` left border; accepts optional `renderIcon` prop — renders the Carbon icon at 14px before the label text), `PageHeading` (Cond 700 28px), `PageTitle` (alias for SectionLabel), `AccentChip` (magenta pill: `var(--accent-bg-14)` bg, `var(--accent-soft)` text), `StickyCta` (sticky bottom bar with top border), `BackButton`, `useNavHints()` hook (returns `[hints: boolean, toggle(val): void]`; reads/writes `localStorage` key `wl-nav-hints`, defaults `true`; syncs across all instances in the same tab via a `wl-nav-hints-change` custom event); `NavBtn` is a `forwardRef` component accepting `l1` and `l2` props — renders a 2-line Plex Condensed (8px) label below the icon; nav bar height is 56px; nav icons in order: Camera → RecentlyViewed → Analytics → EventSchedule (Planlegger) → Notebook (Sett-sammen) → Settings — 6 icons each 48px wide; theme toggle and logout removed from header (now in Settings view); `ChangelogModal` no longer rendered here - `carbon-tokens.css` → added `--heat-1..5` green scale (#044317 → #42be65); WL custom tokens: `--accent` (#ee2c80 magenta), `--surface-card`, `--border-subtle-wl`, `--text-muted-wl`, `--accent-bg-08/14/30`, `--accent-soft`, `--r-card` (16px), `--r-pill` (999px), `--r-tile` (10px), `--cond` (IBM Plex Sans Condensed), `--exercise` (#7af2a4 green, g10 override #1a8c4e), `--exercise-soft` (rgba 12%), `--exercise-mid` (rgba 35%); g10 light-mode overrides for all WL tokens - `app.css` → global `html, body { overflow-x: hidden }` to prevent horizontal viewport bleed from chip rows; do not use `overflow: hidden` on direct parents of `flexWrap: wrap` chip containers — it clips instead of scrolling -- Removed: Bebas Neue, DM Sans, Google Fonts import, custom `C` token objects, all raw hex colors, rounded corners, `react-day-picker`, `date-fns` - ### Hard rules (must not regress) - **Sentence case** for all labels — `Add exercise`, not `Add Exercise` - **0px border-radius** on buttons, inputs, cards — exceptions: Tags/pill chips use `var(--r-pill)` (999px), cards use `var(--r-card)` (16px), tiles use `var(--r-tile)` (10px) @@ -171,7 +164,7 @@ The sessions table has `UNIQUE (gym_calendar_id)` — updating to a gym class th enabled: boolean // toggled in confirm/template step } ``` -Sets and reps were removed in issue #200. Group class instructors log *what exercises were in the program*, not volume. DB columns (`sets`, `reps`, `default_sets`, `default_reps`) still exist and are nullable — no destructive migration. +Sets and reps are not tracked — group class instructors log *what exercises were in the program*, not volume. DB columns (`sets`, `reps`, `default_sets`, `default_reps`) exist and are nullable. ## Exercise library + session templates data model (issue #38) @@ -213,17 +206,11 @@ week_plan_days `week_plan_days.template_id` nullable — an empty slot is a valid row with `template_id = null`. RLS on both tables restricts all operations to the owning user (`auth.uid() = user_id` / exists check via join). -## Gym-wide shared templates and exercise library (2026-05-14) - -`session_templates` and `exercise_library` are **gym-wide**: any co-instructor at the same gym (via `user_gyms` join) can SELECT, INSERT, UPDATE, and DELETE. `user_id` is retained on both tables as "created by" for attribution display only — it is no longer an ownership gate. - -RLS policies replaced (migration `gym_wide_templates_and_exercises`): -- Old: `auth.uid() = user_id` (ALL ops) on all three tables -- New: separate INSERT policy (`auth.uid() = user_id`) + SELECT/UPDATE/DELETE policies using the same-gym EXISTS subquery already used for sessions; `session_template_exercises` uses a JOIN via `session_templates.user_id` +## Gym-wide shared templates and exercise library -`db.js` changes: removed `.eq("user_id", user.id)` defensive filters from `updateTemplateName`, `deleteTemplate`, `touchTemplate`, `updateLibraryExercise`, `deleteLibraryExercise`; added `profiles!user_id(display_name)` join to `fetchTemplates` and `fetchLibraryExercises`. +`session_templates` and `exercise_library` are **gym-wide**: any co-instructor at the same gym (via `user_gyms` join) can SELECT, INSERT, UPDATE, and DELETE. `user_id` is retained on both tables as "created by" for attribution display only — it is not an ownership gate. -**FK pitfall (migration `rewire_user_id_fk_to_profiles`):** `session_templates.user_id` and `exercise_library.user_id` originally referenced `auth.users(id)`. PostgREST cannot traverse `auth.users → profiles` so the `profiles!user_id(display_name)` join failed at runtime. Both FKs were rewired to reference `profiles(id)` instead — matching the pattern used by `sessions.trainer_id`. Do not change these back to `auth.users`. +**FK constraint:** `session_templates.user_id` and `exercise_library.user_id` reference `profiles(id)`, not `auth.users(id)`. PostgREST cannot traverse `auth.users → profiles`, so the `profiles!user_id(display_name)` join would fail at runtime if pointed at `auth.users`. Do not change these FKs back to `auth.users`. **Editing an exercise does NOT rewrite historical sessions.** `muscle_activations` rows are permanent snapshots written at log time with no FK to `exercise_library`. Correcting a muscle mapping in the library only affects future sessions. @@ -267,17 +254,17 @@ recommendation_cache - Claude returns muscle IDs directly in JSON — local keyword matching (EX_DB) was abandoned because Norwegian abbreviations and whiteboard variants didn't match reliably. EX_DB is kept only as fallback for manually added exercises. - SVG body uses `BODY_PATH` (bezier curves, viewBox `0 0 160 360`) — improved silhouette with curved shoulders, arms, waist and hips. Still simplified, not anatomically precise. `SHAPES` entries are either ellipses (`{ cx, cy, rx, ry }`) or SVG paths (`{ d }`); the render loop handles both. Key muscles with path shapes: `traps` (trapezoid with neck notch), `lats` (wing paths). `BodySVG` renders primary muscles as solid green glow, secondary as diagonal blue stripes (``). - `useIsMobile(breakpoint=500)` — exported hook from `bodymap.jsx`. Below breakpoint: single body view with Front/Bak toggle. Above: side-by-side. Consumed via `BodyPanel` — do not use directly in page components. -- **Shared exercise row:** `app/src/components/ExerciseRow.jsx` — renders one editable exercise row (checkbox, inline name edit, sets/reps inputs, delete). Props: `exercise`, `onChange(updates)`, `onDelete()`, `layer` ("layer-01"/"layer-02"), `validateNumbers`, `autoFocusName`, `onNameBlur` (optional callback fired when the name input blurs — used by `ExerciseRowWithAutocomplete` to trigger muscle inference). The outer row div has no click handler — only the Checkbox toggles `enabled` (prevents accidental untick when editing fields). Used by `MuscleMap.jsx`, `History.jsx`, and `TemplateSessionEditor.jsx`. +- **Shared exercise row:** `app/src/components/ExerciseRow.jsx` — renders one editable exercise row (checkbox, inline name edit, delete). Props: `exercise`, `onChange(updates)`, `onDelete()`, `layer` ("layer-01"/"layer-02"), `validateNumbers`, `autoFocusName`, `onNameBlur` (optional callback fired when the name input blurs — used by `ExerciseRowWithAutocomplete` to trigger muscle inference). The outer row div has no click handler — only the Checkbox toggles `enabled` (prevents accidental untick when editing fields). Used by `MuscleMap.jsx`, `History.jsx`, and `TemplateSessionEditor.jsx`. - **Planlegger:** `app/src/components/Planlegger.jsx` — weekly training planner view (issue #59). State: `weekOffset` (±week navigation), `assignments` (`{ [dow 1-7]: template | null }`), `templates`, `weekSessions` (logged sessions for the visible ISO week — issue #143), `pickerDow`, `saving`, `saveError`, `hoveredMuscle`. Computed via `useMemo`: `monday`, `weekIso`, `weekLabel` (built inline with `Intl.DateTimeFormat` for the locale-aware month abbreviation + `t("planlegger.weekLabel", ...)`), `untrainedThisWeekIds` (muscle IDs not trained in any logged session for the visible ISO week — derived from `weekSessions` via `extractMuscles`; issue #143), `projectedExerciseMap` (union of all assigned templates' exercises via `buildMuscleMapFromExercises`), `sessionCount`, `muscleGroupCount`, `untrainedMuscleIds`, `showForslag` (≥2 untrained muscles), `forslagTemplates` (up to 3 templates from library covering untrained muscles). Layout: week nav chevrons → `PageHeading` → `SectionLabel "IKKE TRENT DENNE UKEN"` → wrap row of mono pill chips (History-style: `var(--r-pill)`, `var(--border-subtle-wl)`, `var(--text-muted-wl)`, `var(--cds-font-mono)` 11px) listing muscles not yet trained that week (or a single mono message when all 17 are trained) → `SectionLabel "PROJISERT DEKNING"` → projected `HeatmapBodySVG` (side-by-side/toggle) → fixed-height 48px hover-detail container (always rendered, prevents layout shift) → optional Forslag card → `SectionLabel "UKESPLAN"` → 7 × DayRow → inline `TemplatePicker` bottom-sheet overlay. No sticky save/delete bar — plan auto-saves on every add/remove; `deleteWeekPlan` is called automatically when all slots are cleared. Persists via `fetchWeekPlan` / `saveWeekPlan` / `deleteWeekPlan` in `db.js`; loads logged sessions via `fetchSessionsForWeek` in parallel with the plan fetch. Duration (`N MIN`) omitted — `session_templates` has no duration column. - **IntroModal:** `app/src/components/IntroModal.jsx` — one-time 5-slide onboarding modal (issue #162). Controlled by `open`/`onClose` props from `App.jsx`. Resets `step` to 0 via `useEffect` whenever `open` becomes true. `dismiss()` sets `localStorage` key `wl-intro-seen=1` then calls `onClose()`; the ×-close button and "Hopp over" also call `dismiss()`. Slide data is a static constant array of `{ Icon, titleKey, bodyKey }`. Step indicator and replay hint rendered in body below slide content. Responsive via an inline `