Skip to main content

Changelog

Notes follow the same versions as CHANGELOG.md in the repository.

Shipped updates, in plain language. For the full canonical list (including internal headings), see CHANGELOG.md in the project.

We use Keep a Changelog and Semantic Versioning. Below is a reader-friendly summary of what each release meant for you.

0.3.74

  • Auth E2E: coverage for unauthenticated visit to /admin redirecting to /login or Clerk /sign-in.
  • Body scroll lock: extracted lockBody / unlockBody helpers (src/lib/body-lock.ts) with reference counting so concurrent callers do not stomp on each other's saved overflow value; MobileNavDrawer and MobileShell use these instead of inline document.body.style.overflow mutations.
  • Changelog automation: scripts/generate-changelog-data.mjs parses CHANGELOG.md (Keep a Changelog format) and writes src/data/changelog.ts automatically; npm run generate:changelog and a prebuild hook keep the file in sync so it no longer needs manual edits.
  • Next.js 16 / Clerk: Clerk auth runs from src/proxy.ts instead of deprecated src/middleware.ts (same route protection behaviour).
  • Playwright: PLAYWRIGHT_BASE_URL overrides use.baseURL and webServer.url; set PLAYWRIGHT_SKIP_WEBSERVER=1 to run tests against an already-running dev server.
  • Deployment docs: runtime_timeout troubleshooting (CPU, RAM, timeout) and note that the app uses src/proxy.ts.

0.3.73

  • Auth E2E tests: broadened redirect URL assertion to match both the local /login route and Clerk's hosted sign-in page (which Clerk uses when NEXT_PUBLIC_CLERK_SIGN_IN_URL is not set); tightened the Continue button locator to { exact: true } so it no longer ambiguously matches the Google social button.

0.3.71

  • ESLint react-hooks/refs: destructured useDrawer return in MobileShell so ref objects and state are accessed as direct variables rather than properties of a hook-return object, silencing the "cannot access refs during render" rule.
  • ESLint react-hooks/immutability: replaced the tabButtonRefs: MutableRefObject prop on TabButton with a setRef callback so the mutation stays inside the owning WorkoutHistoryShell component, not inside a child via props.

0.3.70

  • Mobile nav drawer: focus is now trapped inside the drawer while open — Tab and Shift+Tab cycle through focusable elements within the panel rather than leaking into the page behind it (WCAG 2.1 SC 2.4.3).
  • Workout history / picker: endedAt in mapWorkoutDoc now uses a nullish check (!= null) instead of a falsy check so that a legitimate empty-string value is preserved rather than coerced to null.
  • Code readability: extracted mapWorkoutDoc, useNoteDialog, useDrawer, useSetOperations, and TabButton from the five most complex files to reduce cognitive load for new contributors.

0.3.69

  • Settings: Support links show on lg and up; Legal links show below lg, so mobile keeps legal entry points without duplicating the support CTA beside /dash/support.
  • In-app docs / knowledge base (InAppDocPageShell): centered title block and lead; removed the inner bordered card so body content sits in a flatter layout.

0.3.68

  • Support & guides: shared knowledge base (KnowledgeBaseContent / KnowledgeBaseSections, helpUrls for public vs dash) on /support and /dash/support; Settings section links to in-app support; landing footer Support link.
  • next.config: permanent redirects /help → /support and /dash/help → /dash/support.

0.3.67

  • provision:appwrite: optional folders collection gets composite key index idx_user_createdAt on userId + $createdAt (ascending then descending) so list queries can order by $createdAt after userId filter.
  • WorkoutFolderEditDialog: combobox popup portalRoot is set in useLayoutEffect when a folder is open so the portal targets the <dialog> after the ref attaches (avoids callback-ref state sync issues).
  • docs/appwrite-schema: folders indexes document idx_user and idx_user_createdAt.

0.3.66

  • Workout history: folder create/edit dialogs catch thrown server-action errors and surface setError instead of unhandled rejections.
  • WorkoutHistoryShell: Folders tab and panel render only when foldersFeatureEnabled; roving keyboard order follows visible tabs; displayTab avoids invalid selection when folders are off; folders / pickerWorkouts may be null after a failed history load.
  • WorkoutHistoryPage: history load vs folder load separated; outer failure sets foldersError, clears folder/picker data to null, and foldersFeatureEnabled is true only when the collection is configured and history loaded successfully.
  • verifyWorkoutIdsOwnedByUser: bounded concurrency via chunked getDocument calls (default chunk size 30).
  • provision:appwrite: index types use SDK v23 string literals ('key', 'unique') instead of DatabasesIndexType.

0.3.65

  • workout-folders: createWorkoutFolder sends workoutIds: [] so new folders satisfy schemas that require the attribute; listWorkoutFolders orders by $createdAt in the query instead of sorting in memory.
  • verifyWorkoutIdsOwnedByUser: missing workout documents surface Invalid workout selection (404 / not-found) instead of a raw Appwrite error.
  • WorkoutFolderCreateDialog: error text is linked to the name field with aria-describedby and a stable id; form reset when opening runs in the same effect as showModal / close.

0.3.64

  • Workout history folders: APPWRITE_COLLECTION_FOLDERS is optional — provision:appwrite skips the folders collection when unset; schema docs separate required collections from optional folders.
  • workout-session: export WORKOUT_STATUS_COMPLETED for shared filters; documents without status are treated as completed when listing; listCompletedWorkoutsForPicker pages with cursorAfter until enough completed rows.
  • workout-folders: paginated folder list past Appwrite’s page size; workout-session-detail / folder verification use the shared completed status constant.
  • WorkoutHistoryShell: history tablist uses roving tabIndex and Arrow Left/Right, Home, and End for keyboard navigation.
  • Folder create/edit dialogs: dialog title id via useId for stable aria-labelledby.
  • LogView: show current exercise position (N of M) under the session header.
  • createWorkoutFolderAction: getFolderCreationErrorMessage helper for catch-path mapping.

0.3.63

  • Workout history folders: optional folders tab when APPWRITE_COLLECTION_FOLDERS is set — create folders, assign completed workouts via server actions; workout-folders Appwrite helpers, WorkoutHistoryShell + create/edit dialogs; listCompletedWorkoutsForPicker for the picker; provision script + schema docs for folders collection.
  • ComboboxContent: optional portalRoot so the popup can portal into a host node (e.g. native <dialog> top layer).
  • LogView: drop auto-scroll to #main-content after Add set (fits footer/main landmark layout).

0.3.62

  • Dash in-app docs: remove redundant mobile ← Settings back link from Terms, Privacy, Accessibility, and Support (InAppDocPageShell / PlaceholderPage optional backHref / backLabel); rely on app shell navigation for Settings.

0.3.61

  • ClientAppShell: AppFooter is a sibling of <main> (not nested inside it) for clearer landmark structure; remove shell -mx-4 md:-mx-8 — horizontal padding comes from AppFooter (px-4 / md:px-8).
  • TopBar: drop xl:px-10 so horizontal padding stops at lg:px-8, consistent with the shell column.

0.3.60

  • InAppDocPageShell / PlaceholderPage: sticky mobile back header spacing — replace dynamic pt-[max(0px,env(safe-area-inset-top))] with fixed pt-3; add -mt-4 on small screens; add md:-mt-8 md:pt-4 for medium and up; still lg:hidden. Top padding is standardized with fixed values and negative margin tuning instead of safe-area-based top padding on that bar.
  • Mobile bottom tab bar (globals.css .tl-mobile-tab-bottom-anchor, MobileShell): anchor flush to the screen bottom; nav uses pb-[env(safe-area-inset-bottom,0px)] so the bar background fills the home-indicator zone.
  • ClientAppShell: AppFooter rendered inside <main> with -mx-4 md:-mx-8 alignment alongside page content.
  • AppFooter: mobile horizontal padding px-4 (was px-6) so footer content lines up with main p-4; md:px-8 unchanged.

0.3.59

  • Stylelint (stylelint, stylelint-config-recommended, stylelint-config-tailwindcss) with .stylelintrc.json; npm run lint:css runs stylelint on ***.css** files.
  • NeonButton: default type="button" is explicit in the destructuring so type can be overridden (e.g. submit) while keeping the safe default for nested usage.

0.3.58

  • exerciseFormToPayload (exercise-movement-helpers.ts): parameter type ExerciseFormData; optional strings use schema-trimmed values (no duplicate .trim() in the mapper).
  • 0.3.56 changelog (wording): NeonButton sizes listed as sm, md, and lg.
  • NoteDialog: successful save uses sanitizeText(result.data) so persisted text matches the validated parse, not raw props.value.
  • useWorkoutSessionModal: coalesced detail Map removes an entry only if the settled promise still matches; 60s safety eviction clears stuck entries.
  • NeonButton: type="button" so nested usage does not submit ancestor forms.
  • Combobox (combobox.tsx): ComboboxClear and ComboboxChipRemove default aria-label (Clear selection, Remove selection); ComboboxChip accepts chipRemoveProps.

0.3.57

  • REPS_DRAFT_PATTERN / WEIGHT_KG_DRAFT_PATTERN (src/lib/workout/parse-set-inputs.ts): strict draft shapes so values like 5reps or 12.5kg fail parsing instead of partially coercing.
  • nameSchema (src/lib/workout/schemas.ts): single Zod string for max-100 trimmed names; workoutNameSchema and sessionNameSchema alias it (DRY).
  • ExerciseFormData type export from the exercise form Zod object (inferred).
  • Exercise library form (exercise-movement-form.tsx, exercise-movement-panel.tsx): after successful client Zod parse, onSubmit receives ExerciseFormData; create/update server actions use that object (not a possibly stale form closure). Local form state is synced from result.data. Field errors use stable ids and aria-describedby on inputs and the category <select> (plus aria-invalid on the select when invalid).
  • Set entry parsing (parse-set-inputs.ts): weight must match decimal draft pattern before parseFloat; reps must be digits-only, parsed with parseInt(..., 10) (integer reps only). weightKgSchema / repsSchema updated to mirror the same rules (non-empty trimmed values must pass the patterns).
  • Workout session summary modal (use-workout-session-modal.ts): getWorkoutSessionDetailAction promises are coalesced per workout id (module Map) so rapid re-opens don’t stack identical in-flight requests; entries expire after a short TTL.
  • Mobile shell (mobile-shell.tsx): when the nav drawer closes, focus returns to the menu trigger (pairs with existing focus-to-close-button on open).
  • Queue panel: Clear all is disabled when the queue is empty (guarded onClick).
  • NeonButton: md / lg sizes use text-sm / text-base instead of text-xs for primary actions.
  • TextFieldShell: clear control uses Lucide X instead of a text glyph.
  • NoteDialog: validation error cleared when the dialog opens (before showModal) so a stale error doesn’t linger across opens.
  • input-group: shared inputGroupBaseClasses export; addon wrapper no longer nests role="group" (avoids redundant grouping inside the parent group).
  • combobox: ComboboxChips props use ComponentPropsWithoutRef for compatibility with Base UI typing.
  • SetRow: single trimmedNote for note preview, title, and conditional render (consistent trimming).

0.3.56

  • NeonButton (src/components/ui/neon-button.tsx): unified button with variants primary, ghost, danger-ghost, outline and sizes sm, md, and lg; replaces scattered inline button styling in dialogs, log bars, and forms.
  • Input / Textarea (src/components/ui/): shared dark-theme primitives with focus states and error styling via aria-invalid.
  • Zod schemas (src/lib/workout/schemas.ts): exercise fields (name, slug, category, kind, muscle label, description), exercise notes (max 500 chars), workout/session names (max 100 chars), weight and reps with numeric checks.
  • Sanitization (src/lib/sanitize.ts): DOMPurify strips HTML from user text before handlers persist data.
  • Client validation on exercise movement form, note dialog, and session name form: per-field errors, cleared on user input; note dialog adds a 500-character counter with aria-live="polite".
  • Form fields: aria-invalid and aria-describedby wired for errors and help text.
  • Mistral API (/api/mistral): restricts roles to user|assistant|system, max 50 messages, max 8 000 characters per message; returns 400 with safe messages; detailed errors logged server-side only.
  • Dialogs (NoteDialog, DiscardConfirmDialog, RemoveExerciseConfirmDialog): aria-labelledby via useId() for proper dialog labeling.
  • Mobile nav drawer: focus moves to the Close button on open (WCAG 2.4.3) via useRef + useEffect.
  • Navigation (sidebar, mobile shell, mobile drawer): aria-current="page" on the active route.
  • Icon / card controls: descriptive aria-label on note toggle (SetRow), remove-from-queue, clear-queue, close-drawer, and workout card actions; WorkoutSessionCardItem trigger uses aria-haspopup="dialog".
  • NoteDialog: title switches between "Add note" and "Edit note" when the field has content.
  • Dependencies: dompurify, zod, @base-ui/react, class-variance-authority, tw-animate-css; dev: shadcn, @types/dompurify.
  • Exercise movement form: Input / Textarea + field-level error state; sanitization on text passed to parents.
  • Log session bars (mobile/desktop): NeonButton instead of ad hoc button classes.
  • Queue panel: focusRingKinetic on remove controls.
  • SetRow: Input for numeric fields; aria-invalid when parse validation fails.
  • WorkoutNameField: Input + schema validation.
  • focusRingKinetic on dialog actions, queue actions, log add-set, set-row note, close-menu where it was missing.
  • Input/textarea focus ring opacity teal-500/25 → teal-500/50.
  • Shared DialogDismissButton for modal/drawer header close (icon + focus ring); mobile nav drawer keeps initial focus on dismiss via ref.
  • Exercise picker search: aria-label="Search exercises" instead of aria-labelledby on the section heading.
  • useWorkoutSessionModal: mountedRef avoids state updates after unmount; requestSeqRef also guards stale .catch() paths from concurrent fetches.
  • Mistral API: no upstream/internal error text in JSON responses to the client (server-side logging only).

0.3.55

  • Edit session name dialog: sync open with showModal/close via a callback ref and close on unmount (avoids ref timing issues vs. useEffect on the element).

0.3.54

  • Active workout log: edit session name via pencil next to “Current session”; clearing the field and saving restores the date-based default name.

0.3.53

  • New log (step 1): toggling an exercise scrolls #main-content to the top so the Selected section stays in view on long lists.
  • Add-exercise modal: Selected includes the highlighted exercise (not only queue); the picker scroll pane resets to the top when selecting.

0.3.52

  • Workout history summary: “use as template” opens new log with the session name and exercise order prefilled via ?prefill= and a one-time sessionStorage handoff (Strict Mode–safe).
  • Exercise picker: queue / in-workout exercises appear under Selected (A–Z) and are omitted from All exercises below; add-exercise modal matches this for exercises already in the session.

0.3.51

  • New log (step 1): optional workout name field with a date-based default title; session uses the custom name or default when starting the log.
  • New log: workout name placeholder and session default both use formatDefaultWorkoutName() (no separate frozen value on the flow context).
  • New log: default session title uses the date when the log starts (not fixed when the picker first mounts).
  • New log: cancel with a non-empty queue or a custom name opens a discard confirmation; leaving with nothing to lose navigates away immediately.
  • Exercise picker: “Exercises” labels the search region; list section defers to that heading for a cleaner outline (modal uses h3 under the dialog title).

0.3.50

  • Mobile safe-area: shared CSS variables and utilities (tl-scroll-pad-mobile-tab, tl-mobile-tab-bottom-anchor, tl-fixed-pad-bottom-*, tl-public-viewport-pad-bottom) so main scroll, fixed tab bar, modals, and public auth pages align with the home indicator.
  • Remove unused BottomTabNav and bottom-tab-nav-icons (bottom navigation is provided by MobileShell).
  • App shell: sidebar, mobile drawer, and bottom mobile tabs use Lucide icons via NavItemIcon instead of Unicode glyphs; start-log FAB uses Plus.
  • Placeholder pages: remove redundant secondary “Back to dashboard” link when the sticky back control is present.
  • Queue mobile bar: cancel is always available; queue count shows as a single badge (teal when non-empty, muted when empty).

0.3.49

  • lucide-react for consistent workout UI iconography.
  • Exercise picker rows use Lucide Check and Plus for queue state instead of text glyphs.
  • Workout log: remove-exercise and delete-set actions use Trash2; new-log queue cancel uses X; mobile session bar uses a shorter Finish label and keeps the exercise counter for screen readers only.
  • Queue mobile bar: cancel is disabled until at least one exercise is selected (help copy updated).
  • Set rows: long notes truncate with a title tooltip instead of multi-line clamp.

0.3.48

  • Workout log exercise picker now renders one unified "All exercises" list (removes the separate "Recommended" section and related picker-title plumbing).
  • Picker and flow context helpers were simplified by removing unused category-label map and recommended-title helpers from shared catalog-picker utilities.

0.3.47

  • Marketing auth: sign-up and waitlist embed wrappers match login (flex w-fit min-w-0 justify-center) for consistent intrinsic-width centering.

0.3.46

  • Public pages: shared tl-public-page-bg utility; landing hero gets layered radial accents and safe-area padding; marketing waitlist CTAs and Clerk waitlist primary button align with workout-cta-classes tokens.
  • Clerk sign-in, sign-up, and waitlist embeds: transparent card panels (no box shadow); login and waitlist footers use LandingFooter instead of duplicated copyright; auth headers and app top bars use border-white/10 for consistency.
  • Workout log mobile session bar: full-width bottom dock with inner card styling and preserved session controls.
  • Accessibility: root viewport no longer enforces maximumScale, allowing pinch zoom while retaining iOS form-control sizing safeguards.

0.3.45

  • Dashboard: loadDashPageData consolidates locale, user greeting, recent workouts, volume aggregation, and weekly stats; app/dash/loading.tsx skeleton while the route loads.
  • Clerk: getCachedAuth and getCachedCurrentUser wrap auth() / currentUser() with React cache() so layout and page share one Clerk resolution per request.
  • Marketing auth pages: sign-in, sign-up, and waitlist use tighter Clerk embed wrappers (w-fit / flex) so the panel matches intrinsic component width.
  • Appwrite requireClerkForAppwrite uses cached auth; authenticated shell uses cached currentUser.
  • Dashboard volume: filterWorkoutsByRange splits a single wide-range workout list into week/month buckets in memory after one range query.

0.3.44

  • iOS Safari: prevent focus-triggered input zoom (enforce 16px form control font-size).

0.3.43

  • Workout log: Add exercise modal close (X, Cancel, Escape, backdrop) works reliably—mount AddExerciseToWorkoutModal only when open, lazy-mount AddCustomExerciseDialog, unmount close() cleanup, and explicit z-[10000] / z-[9999] for portaled workout UI.

0.3.42

  • Workout log: AddExerciseToWorkoutModal no longer leaves an empty native <dialog> on screen (sync showModal/close with useLayoutEffect before paint; keep modal UI in the DOM like other dialogs so it never paints while open with no children).

0.3.41

  • Workout log mobile session bar: remove stray duplicate Discard button fragment left from a bad merge (restores valid JSX / Prettier).

0.3.40

  • Workout log session bars: clamp current exercise index for prev/next and progress text when the queue is empty or the index is out of range; desktop bar uses z-[9999] for stacking consistency with mobile; logic shared via getLogSessionExerciseNavState.

0.3.39

  • Workout flow: appendExerciseToActiveWorkout uses a queue snapshot via ref and stable useCallback deps ([phase] only) so the new exercise index matches the appended item without stale closure issues.

0.3.38

  • Mobile workout log bar: when Finish is disabled, show the finish helper as visible muted caption text (not screen-reader-only); aria-describedby unchanged.
  • Workout log: finish requires all queued exercises’ sets completed with valid weight/reps; discard disabled until no meaningful set data; mobile and desktop session bars use prev/next exercise navigation, shared CTA styles, and clearer finish/discard actions.
  • Workout log: remove exercise (with confirm when sets have data), add exercise modal from the log screen, and exercise picker extracted to ExercisePickerBody for build flow reuse.
  • Exercise picker rows can show disabled state when an exercise is already in the queue.
  • catalog-picker helpers and workout-cta-classes for shared workout UI tokens.

0.3.37

  • Web fonts: Anek Bangla loaded from Bunny Fonts (fonts.bunny.net) instead of Google Fonts via next/font; unified CSS on --font-anek-bangla, optical html font-size, and simplified workout font scope; privacy subprocessors table updated for Bunny Fonts.

0.3.36

  • App footer: show legal links on small screens with in-app (/dash/*) targets; keep marketing (/privacy, etc.) links on large screens.
  • Mobile shell: correct bottom-tab active state for Account and Dashboard; compute active href from the same item list used for the tab bar; action tab links to /dash/account with active styling when on account/profile.

0.3.35

  • Workout set row autosubmit: avoid patching 0 or toggling completed when only one field fails parsing; flush pending parsed values on blur and on unmount so debounced edits are not lost when navigating away before the 350ms timeout.

0.3.34

  • Dashboard app routes: /dash/account and /dash/profile pages aligned with updated shell navigation.
  • Mobile/app shell navigation refresh: top bar account trigger, sidebar, bottom tabs, nav config/active helpers, and mobile shell behavior tuned for account/profile flows.
  • Workout log mobile UX: build and queue bars plus exercise row/build view interactions updated to match the revised shell/navigation structure.
  • Workout session detail and summary flow refactor: improved session data fetching, card/list rendering, and mobile set-row/build/queue interactions for more reliable workout logging UX.

0.3.33

  • Privacy policy GDPR section: restored a valid data controller paragraph rendering and removed a broken nested component fragment from PrivacyGdprAndProcessors.

0.3.32

  • /accessibility marketing page: accessibility statement (WCAG 2.1 AA / 2.2 focus, feedback via Support, third-party caveat).
  • Privacy policy: GDPR and subprocessors section with legal bases, international transfers, EEA/UK rights, and a subprocessors table (Clerk, Appwrite, Mistral, Bunny Fonts).
  • App shell and landing footers: link to Accessibility; footer nav labeled for screen readers.

0.3.31

  • Exercise catalog: listExerciseDocumentsForUserId now pages through Appwrite (500 per request) so the picker and exercises library load every exercise when a user or the global catalog has more than 500 rows.

0.3.30

  • loadExerciseLibraryMerged: global catalog plus signed-in user exercises, deduped by slug (user wins); readOnly marks seeded catalog rows.
  • New log build step: when search/filter has no matches, “Add your own exercise” opens AddCustomExerciseDialog (native <dialog>, createExerciseAction, focus restore).
  • exercise-movement-helpers.ts: shared form/row mapping for the exercises library editor.
  • Exercises library (/dash/exercises): movement list/panel and page wired to merged catalog; mapExerciseDocumentToRow exported from user-exercises for reuse.
  • category-labels: chip helpers extended for custom-exercise category picker.
  • Build view copy: “Step 1: Stack your session” and clearer intro for the fresh-log flow.
  • App shell: bottom tab nav and mobile shell/nav config tweaks; globals.css small additions.

0.3.29

  • Workout session summary modal: loads per-exercise sets (weight, reps, RPE, notes) via server action getWorkoutSessionDetailAction and Appwrite helper getWorkoutSessionDetail; tables in the modal list sets under each exercise name.
  • Dashboard and history workout cards: extracted WorkoutSessionCardItem, useWorkoutSessionModal, and buildWorkoutSessionSummary; summary dialog shows async exercise detail with loading and error states.
  • useWorkoutSessionModal: format getWorkoutSessionDetailAction promise chain (.then / .catch) for readability.

0.3.28

  • Marketing auth: shared MarketingAuthHeader on login, sign-up, and waitlist; landing and doc shell link styling aligned with the new header pattern.
  • Clerk: ClerkProvider uses theme: dark from @clerk/ui/themes (adds @clerk/ui) with brand primary color variables; SignIn, Waitlist, and SignUp rely on defaults with layout-only scoped CSS (globals.css @layer clerk) for fluid width, hidden footers where needed, and a narrow fix for stretched primary buttons.

0.3.27

  • Exercise library (/dash/exercises): clearer spacing below the intro line before the first card; page is wrapped in WorkoutLogFontScope so headings use the workout display stack, and intro copy uses the same muted tone as section blurbs.

0.3.26

  • Dash navigation and exercises page: label reads Exercises Library instead of Exercises (route /dash/exercises unchanged).
  • Dashboard “Start New Log” CTA and marketing header “Dashboard” link no longer show trailing chevron-style icons (leading + on the CTA unchanged).

0.3.25

  • volume-periods: normalizeBarHeightsPct map callback formatted as a single expression (CodeRabbit)

0.3.24

  • Dashboard weekly volume: loads this week, last week, and this month using the request time zone (Monday–Sunday bounds), week-over-week comparison, per-day bar chart with today highlighted, and batched volume aggregation for range queries
  • listWorkoutsInStartedAtRange and aggregateVolumeKgByWorkoutIdsBatched in Appwrite workout helpers; volume-periods and zoned-calendar / calendar-arithmetic utilities for zoned date math without extra dependencies
  • Dashboard and workout history: tapping a workout card opens a centered session summary modal (native <dialog>, same styling as the log note modal) with duration, volume, optional notes, and session metadata.
  • Workout history uses the same card layout and volume/note aggregation as recent workouts on the dash; session ids appear in the modal footer instead of on each list row.

0.3.23

  • BodyPortal: set portal container in the same effect tick as appending the host node so nested <dialog> showModal runs after refs attach (avoids a race with deferred updates like startTransition)
  • Workout log: removeSet now no-ops only when the set id is missing (idx === -1); the previous idx === 0 check blocked deleting the first set
  • Discarding a log session calls clearQueue() before reset and navigation so the queue does not leak into the next flow

0.3.22

  • docs/deploy-appwrite-sites.md: expanded Appwrite Sites guide — local provision:appwrite and collection env vars, build table (./.next), full env var matrix, Clerk/platforms/PWA/security notes

0.3.21

  • Mobile dash: floating “start log” FAB, BodyPortal for fixed session chrome, and LogSessionMobileBar (volume, rest/timer, finish/discard) on small screens
  • Login route loading.tsx; use-set-row-inputs, use-start-button-gate, and shared parse-set-inputs helpers for set entry and start-button rules
  • Dashboard hero lines from pickHeroHeading(); recent workouts can show note previews when volume aggregation returns them
  • App shell: slimmer mobile top bar/sidebar/footer; marketing LandingNav updates; kinetic shell CSS tweaks
  • Workout log/build flows: log-view, build-view, queue-mobile-bar, dialogs, and workout-flow-context aligned with mobile bars and gating
  • Appwrite workout-session helpers extended for note previews alongside volume aggregation
  • Dashboard insights-banner component

0.3.20

  • listUserExercises: paginated fetch loop (500/page) replaces single Query.limit(500) call, returning the full list for users with more than 500 exercises
  • updateExerciseCategoryLabel: after renaming a category, updates muscleLabel on all user exercises in that category where muscleLabel was blank or matched the old label
  • deleteExerciseCategoryWithMigration: only preserves originalMuscleLabel when it is non-empty and differs from the deleted category's label; otherwise falls back to muscleFallback
  • provision:appwrite: exercise-categories idx_user_slug index block now mirrors the exercises repair flow — detects wrong type or attributes and upgrades/throws accordingly
  • provision:appwrite: seedOneCatalogExercise now validates ex.kind explicitly and throws a descriptive error for unrecognised values instead of silently coercing to 'compound'
  • provision:appwrite: catalog exercise seed uses deterministic document ids (SHA-256 of catalog user id + slug, hex truncated to 36 chars) and upsertDocument for race-free writes; on 409, falls back to listDocuments + updateDocument for legacy rows that still use random ids

0.3.19

  • scripts/validate-default-exercises.mjs and validate:default-exercises; npm run check validates required keys and unique slugs in default-exercises.json
  • Category delete flow: empty reassign targets blocked in UI; dialog fallback option, focus trap, Escape, and Tab cycling; panel no longer falls back to core for reassignment
  • Exercise movement form: disabled category select and submit when no category options; default category from categoryOptions[0] in the movement panel (ExerciseRowData type rename)
  • deleteExerciseCategoryWithMigration: rollback exercise updates on failure before category delete; assertCategoryAllowed accepts optional pre-fetched categories to avoid duplicate Appwrite queries
  • exercise-catalog: MUSCLE_CATEGORIES from BUILT_IN_EXERCISE_CATEGORY_SLUGS; dev catalog errors surfaced while still returning bundled defaults; unknown user category slugs resolve to core
  • provision:appwrite.mjs: comment linking duplicated GLOBAL_CATALOG_USER_ID to catalog-user-id.ts; batched parallel catalog seeding
  • exercise-categories: listExerciseCategoriesForUser and category-delete migration scan user exercises with paginated listDocuments (beyond Appwrite’s 500-row default cap)
  • Redundant auth() on /dash/exercises (rely on requireClerkForAppwrite + ClerkAuthRequiredError)
  • Category chips: neutral aria-label for dynamic categories; rename input: sr-only label + id for screen readers
  • user-exercises: createUserExercise / updateUserExercise validate kind is compound or isolation; mapExercise skips rows with invalid kind instead of coercing to compound
  • npm overrides: pin transitive serialize-javascript to 7.0.4

0.3.18

  • /dash/exercises exercise library: user-defined categories and movements with create/edit/delete, backed by Appwrite server actions
  • exercise_categories collection (unique userId + slug), helpers in exercise-categories / user-exercises, and provision:appwrite wiring plus env for the collection id
  • loadExercisePickerCatalog merges global __catalog__ exercises with the signed-in user’s categories and library; workout picker uses shared category-labels chips

0.3.17

  • Expanded default-exercises.json with ~50 additional common movements across chest, back, legs, arms, and core (still seeded via provision:appwrite into __catalog__)

0.3.16

  • loadExercisePickerCatalog(clerkUserId) merges global __catalog__ exercises with the signed-in user’s library (sparse user rows get picker defaults); development falls back to DEFAULT_EXERCISES when Appwrite returns nothing or errors
  • Exercise picker empty/error messaging on the new-log build step when the catalog fails to load or has no rows
  • /dash/log/new and /admin/log/new use loadExercisePickerCatalog with Clerk userId instead of only global catalog documents

0.3.15

  • Appwrite global exercise catalog: default-exercises.json, sentinel userId __catalog__, optional muscleLabel / category / kind on exercises; provision:appwrite seeds/updates default rows idempotently
  • listDefaultExerciseCatalog() (admin client) for the new-log picker; resolveExerciseIdForWorkout prefers user slug, then global catalog slug, then creates a user-owned exercise
  • Workout log flow loads initialCatalog from Appwrite on /dash/log/new and /admin/log/new instead of a static in-code list; catalog.ts parses JSON for DEFAULT_EXERCISES / newSet() only

0.3.14

  • Dash nav: shared getActiveHref for sidebar and mobile; /dash/log normalized like history for active state; top bar Settings shows active styling on dash and admin settings routes
  • Locale cookie sync refreshes on /dash/log when locale changes (same as history routes)
  • aggregateVolumeKgByWorkoutIds paginates set documents past Appwrite’s page limit; dashboard shows workouts even if volume totals fail (separate list vs volume error)

0.3.13

  • /dash/history workout history (shared WorkoutHistoryPage), /dash/settings placeholder, and redirect from /dash/workout-history to /dash/history
  • listRecentWorkouts and aggregateVolumeKgByWorkoutIds in Appwrite workout session helpers
  • Dash nav targets /dash/history and /dash/settings; nav-active helpers keep history tab active for log and legacy workout-history URLs
  • Non-staff users hitting /history redirect to /dash/history (staff keep admin history)
  • Dashboard recent workouts: volume per session, duration, link to full history at /dash/history

0.3.12

  • TrainingLoggerWordmark and BackToHomeLink for consistent brand and navigation on login and waitlist
  • LandingNav uses the shared wordmark; home layout moves the sticky nav outside the content width wrapper
  • DocPageShell layout simplified (nav, main landmark, footer)
  • Skip link: visually hidden until keyboard focus (@layer components); stronger focus presentation
  • Clerk login and waitlist embeds: scoped CSS (tl-login-sign-in, tl-waitlist-embed, @layer clerk) to hide footers and waitlist “already have access” row where intended

0.3.11

  • @clerk/ui dependency; ClerkProvider no longer passes ui (layering still via appearance.cssLayerName)
  • VS Code: source.fixAll.eslint on save set to "explicit" (valid codeActionsOnSave value)

0.3.10

  • SkipToMain skip link (keyboard-visible) and #main-content landmarks with scroll-mt-24 on marketing, auth, waitlist, doc shell, placeholder, and authenticated app shell
  • src/lib/focus-styles.ts — shared focusRingMarketing / focusRingKinetic classes for visible focus (WCAG 2.4.7)
  • Footer and nav links, hero/CTA buttons, changelog external links: focus rings; changelog anchors use rel="noopener noreferrer"
  • Muted text contrast: text-zinc-500 → text-zinc-400 (and related) on marketing, footers, login/sign-up/waitlist meta lines
  • SetRow delete control: larger hit target and kinetic focus ring

0.3.9

  • DocPageShell for consistent legal and changelog layouts; PrivacyPolicy and TermsOfService content components
  • src/data/changelog.ts and ChangelogList for a reader-facing /changelog page aligned with CHANGELOG.md
  • @clerk/ui and ClerkProvider appearance / ui wiring; @layer order in globals.css so Clerk styles sit in the cascade correctly
  • Marketing hero and bottom CTA link to /waitlist instead of embedding the waitlist on the home page
  • LandingNav shows brand + Login only; TopBarAccount is UserButton only

0.3.8

  • /waitlist page and ClerkWaitlistEmbed (<Waitlist /> appearance) for beta sign-up
  • ClerkProvider waitlistUrl="/waitlist"; <SignIn /> and <SignUp /> waitlistUrl so Clerk waitlist links resolve on-app
  • Marketing hero and bottom CTA use the Clerk waitlist embed instead of custom email capture
  • src/components/marketing/email-capture.tsx
  • docs/clerk-auth-setup.md: waitlist dashboard steps and app URL wiring

0.3.7

  • Appwrite provision: validate existing idx_user_slug in the exercises index createIndex path (not after workouts idx_user_startedAt)

0.3.6

  • Workouts status attribute (pending / completed); provision script creates it and only swallows true 404s when ensuring database/collection exist; validates existing idx_user_slug matches the expected unique index
  • Locale and timezone cookies plus getRequestLocaleAndTimeZone for server-rendered history timestamps; LocaleCookieSync sets cookies from the browser and refreshes /history when they first change
  • src/lib/intl/ helpers (locale-cookies, request-locale)
  • completeWorkoutSession: create workout as pending, persist sets, then set completed; on failure, delete created set documents and the workout document
  • listRecentWorkouts: return only completed workouts; query with a higher limit then slice so pending rows do not reduce the visible count
  • Authenticated shell: pass History/Settings hrefs and labels into MobileShell (e.g. staff “Activity” vs athlete “Workout History”)
  • Log view: disable Finish workout until at least one set is completed; block empty payloads with a clear error
  • SetRow: weight field uses local draft state committed on blur
  • WorkoutFlowProvider: keep rest timer ref aligned when adjusting rest seconds and when resetting the session

0.3.5

  • Mobile shell: stop remounting on every pathname change (MobileShell no longer keyed by route); drawer closes on navigation, Escape, and in-drawer link taps; bottom tabs use configured History/Settings hrefs from nav items
  • Discard confirm <dialog>: handle native cancel (Escape) via ref-stable callback; rounded container styling
  • Category filter chips: role="group" and aria-pressed instead of incorrect tablist/tab semantics
  • Log view: explicit saving lock replaces useTransition so the whole log UI (sets, nav, timer, discard/finish) stays disabled until finishWorkoutAction completes
  • finishWorkoutAction: log failures server-side; return a generic client error message
  • InsightBanner: optional fromMuscle / toMuscle / sessionCount props (defaults unchanged)
  • WorkoutFlowProvider: memoize context value so consumers avoid unnecessary rerenders
  • completeWorkoutSession: create set documents in parallel per exercise (Promise.all)
  • getAppwriteEndpoint / getAppwriteProjectId: reuse shared requiredEnv helper
  • README: setup order — npm install before provision and Clerk steps
  • provision-appwrite: composite Key index idx_workout_order on sets (workoutId, order)

0.3.4

  • Rest timer: interval tick reads latest rest seconds via a ref so countdown does not stall after setRestSeconds updates outside the interval
  • History empty state: “Start a log” links to /dash/log/new or /admin/log/new from Clerk role metadata; load failures log server-side and show a generic message
  • provision-appwrite: idempotent create paths swallow only Appwrite 409 with column_already_exists / index_already_exists (not string-matching "already exists")
  • SetRow: htmlFor / id wiring for weight and reps inputs (label association)

0.3.3

  • Exercise upsert race: getOrCreateExerciseId catches Appwrite 409 after createDocument, re-queries by userId + slug, and returns the existing document id
  • Provisioning: idx_user_slug on exercises is a unique composite index (userId, slug) so concurrent creates cannot duplicate the same exercise
  • docs/appwrite-schema.md: documents unique idx_user_slug and migration (delete old Key index, re-run provision:appwrite) for projects provisioned before this change

0.3.2

  • buildCompleteWorkoutPayload: only serialize sets marked completed; omit exercises with no completed sets so Appwrite validation still passes (avoids persisting placeholder 0×0 rows)

0.3.1

  • Dev (Turbopack): moved kinetic reveal keyframes and prefers-reduced-motion rules into kinetic-shell.css so PostCSS no longer corrupts @media after @import 'tailwindcss' in globals.css
  • scripts/provision-appwrite.mjs and npm run provision:appwrite — idempotent creation of the Appwrite database, workouts / exercises / sets collections, attributes, and indexes (reads .env.local)
  • README setup flow and scripts table; docs/appwrite-schema.md documents running the provision command after env IDs are set

0.3.0

  • Workout logging flow (build queue, log sets, rest timer, per-set notes) for /dash/log/new and /admin/log/new
  • Server action + Appwrite persistence: workouts, exercises (slug upsert), sets with optional notes
  • Workout history page lists recent sessions from Appwrite
  • Mobile shell: top bar, slide-out nav, bottom tabs; extra bottom padding on log routes
  • Env: APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_WORKOUTS / _EXERCISES / _SETS; schema doc updates for exercise slug and set notes
  • Merged main: kinetic theme CSS (DM Sans / Bebas, reveal animations), dashboard card styling, /dash/exercises and nav, PWA manifest and root viewport metadata; athlete shell remains branch MobileShell + log padding

0.2.1

  • Clerk <SignIn /> / <SignUp /> path routing: /login and /sign-up use optional catch-all segments ([[...rest]]) so nested auth URLs resolve correctly

0.2.0

  • Clerk sign-in at /login and sign-up at /sign-up; publicMetadata.labels for staff vs athlete routing
  • requireClerkForAppwrite() so server-side Appwrite calls require a Clerk session
  • Docs: docs/clerk-auth-setup.md
  • Proxy route protection for /dash, /admin, /history, /settings
  • POST /api/mistral authorizes via Clerk instead of Appwrite session
  • App shell: UserButton in top bar; sidebar sign-out uses Clerk SignOutButton
  • README, Appwrite schema docs, deploy notes, and .env.example for Clerk + narrowed Appwrite key usage
  • Appwrite Account sessions, login server actions, and session-client
  • appwrite browser package, seed:dev-users script, docs/appwrite-auth-setup.md

0.1.0

  • Next.js app with dashboard, admin flows, marketing pages, and Mistral API proxy
  • Scripts: init:env
  • ESLint, Prettier, Tailwind CSS v4