Skip to content

feat(workout): set UX overhaul -- checkbox confirm, swipe delete, gym picker fix, rest minimize - #109

Merged
rghamilton3 merged 16 commits into
mainfrom
feat/workout-set-ux
Apr 14, 2026
Merged

rghamilton3 merged 16 commits into
mainfrom
feat/workout-set-ux

Conversation

@rghamilton3

Copy link
Copy Markdown
Contributor

Summary

  • feat: Checkbox UX for set confirm/unconfirm with undo support
  • feat: Swipe-to-delete sets; removed set-level notes
  • fix: Active workout shows all exercises simultaneously (no hard focus until set tapped)
  • fix: B008 -- gym picker appeared on every workout start even after gym was saved; fix replaces firstWorkoutCompleted gate with readLastGymChoice() ?? openGymPicker()
  • feat: Minimize rest timer to a compact sticky banner so athletes can review the exercise log while counting down

Test plan

  • Start a workout: confirm gym picker only appears when no gym is saved in localStorage
  • Log a set, mark it done via checkbox, then undo via the undo banner
  • Swipe left on a set row to delete it
  • During rest, tap the ChevronDown to minimize -- banner should appear at bottom with countdown
  • Tap Expand from the banner to restore the full rest view
  • Tap Skip from the banner to end rest immediately
  • Complete rest naturally -- next rest should start expanded (not minimized)

Add a "Done with this exercise" affordance so athletes can skip
remaining sets on a strength block and advance focus to the next
exercise. Also dim the trailing auto-populated set row so its
unconfirmed state is visually unambiguous.

- active-workout-store: add skippedActivityIds (Set<string>) + skipActivity
  action; resets automatically on finish/discard via initialState spread
- exercise-block: add onSkipExercise prop + DONE button below set list;
  add isPending to SetRowData; pass isPending to SetRow for pending rows
- set-row: add isPending prop -- opacity-40 on row + placeholder in
  STATUS column instead of confirm button
- log.$workoutId: pull skippedActivityIds from store; activeFocusId memo
  treats skipped activities as done; skip guard on render; wire
  onSkipExercise with confirmedSets.length > 0 guard; mark trailing row
  isPending: true
Removes the hard-focus filter that prevented non-active exercises from
rendering. All exercises now display with their set rows and controls.
"Add set" is accessible on every exercise block to support ad-hoc
pre-population. "Done/Skip" remains active-only as a progression signal.

Closes B007
- SetRow gains left-swipe gesture to reveal a DEL button (64 px threshold);
  snaps open when threshold is reached, snaps closed on partial swipe or tap
- Adds deleteSet and removeActivity actions to active-workout-store
- Removes set-level notes (notes / noteTags) from the LoggedSet domain type,
  both adapters (Supabase + Tauri), and the store's setSetNote action
- Drops loggedSetId prop and NoteAffordance from SetRow; removes set-note
  display from WorkoutDetailExercises and ManualWorkoutForm
- Adds Supabase + SQLite migrations to DROP notes / note_tags from logged_sets
…backlog

- Adds Coding Guidelines section to CLAUDE.md (5 rules: think, read, simplify,
  surgical changes, goal-driven execution)
- /commit command gains Step 4: QuickPlan archive prompt after each commit
- /quick command Step 5: enforce archive move instead of suggesting it
- Backlog: adds B007 (multi-exercise display), B008 (gym selector persistence),
  and "frequently used exercises" idea
Replace the confirm checkmark + done badge/icon trio with a unified
checkbox metaphor. Empty checkbox (ember) to confirm; filled checkbox
to unconfirm. Variance coloring is preserved via the filled checkbox:
green for met, amber for under, neutral for no prescription.

Unconfirming a set now marks it completed=false (updateLoggedSet)
rather than deleting it, so actual weight and reps are preserved in
the row for re-editing.
- Require reps on all confirms (weight optional): change confirm
  button disabled condition from weight||reps to reps-only gate
- Fix reps=0 coercing to null: parse reps with >= 0 (not > 0) and
  use non-null check instead of falsy check, preventing a
  logged_sets_completed_check constraint violation for failed sets
- Suppress spurious pending row on unconfirm: guard pending row
  insertion with hasUncompletedSets so un-confirming a set doesn't
  produce a duplicate editable row alongside the existing one
Replaced the `firstWorkoutCompleted`-gated branching logic in all three
workout-start handlers with a single `readLastGymChoice() ?? openGymPicker()`
expression. The onboarding flag was the wrong gate -- it caused the picker to
appear on every start until the first workout was fully completed, regardless
of whether a gym had already been saved to localStorage.

Fixes B008. Removes unused `useOnboardingStore` subscription from library.tsx.
Adds a minimize affordance to the full-page rest view. Tapping the chevron
collapses the rest timer to a compact sticky strip (RestTimerBanner) at the
bottom of the screen, exposing the exercise log so athletes can review sets
or add notes while the countdown runs in the background.

- RestPanel: optional `onMinimize` prop renders a ChevronDown button top-right
- RestView: threads `onMinimize` through to RestPanel
- RestTimerBanner: new component -- ember progress bar + REST label + countdown
  + Expand + Skip buttons, sticky bottom z-40
- log.$workoutId.tsx: `restMinimized` state; minimized path renders banner
  alongside the exercise log; minimize state resets when rest timer ends
- Update .claude/commands/* to latest skill wrappers; add spec-init command
- Mark B008 resolved in Bugs.md
- Archive completed QuickPlans: undo-set-done-status, b008-gym-picker-every-start
@vercel

vercel Bot commented Apr 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ardent-forge Ready Ready Preview, Comment Apr 14, 2026 9:57pm

P18-001/002: add coordinated deleteSet and removeActivity bridge
functions in use-active-workout.ts that await DB deletion before
updating the Zustand store; wire onDeleteSet and onRemoveExercise
through them in log.$workoutId.tsx so swipe-delete and exercise
removal are now persisted

P18-003: add [workout-logs] prefixed onError handlers to
useDeleteLoggedSet, useDeleteLoggedActivity, and
useDeleteLoggedActivityGroup in use-workout-logs.ts

P18-004: update set-row.test.tsx confirmed-state assertion from
getByText('DONE') to getByLabelText('Undo set 1') to match the
checkbox icon button introduced in this PR

P18-005: remove duplicate console.error calls from unconfirmSet
catch in use-active-workout.ts and handleUnconfirmSet catch in
log.$workoutId.tsx -- mutation hook onError is the single log owner

P18-006: handleFinish and handleConfirmSet guard clauses now log
[workout-page] prefix and call setPageError instead of silently
returning

P18-007: add setIsSwiping(false) alongside setSwipeX(0) in DEL
button onClick in set-row.tsx

P18-008: move setStartingTemplateId inside try/finally in
library.tsx so the spinner always clears even if openGymPicker throws

P18-009: call storeClearUndo() in undoSet catch block so the expiry
timer does not silently clear an already-failed undo

P18-013/014: add inline comments to active-workout-store.ts
explaining the private-mode no-op and ephemeral field reset

Review: Context/Reviews/0018-pr109-workout-set-ux-2026-04-13.md
Add Wave 7 to Feature 018 Steps.md with S051-S054 capturing the four
[TASK] findings from the PR #109 review:
- S051: tests for new store actions (deleteSet, unconfirmSet, etc.)
- S052: component test for swipe-to-delete DEL button
- S053: regression test for B008 gym picker fix
- S054: tests for RestTimerBanner component

Mark review 0018-pr109-workout-set-ux-2026-04-13.md as partially
resolved with resolution summary.

Review: Context/Reviews/0018-pr109-workout-set-ux-2026-04-13.md
S051 -- active-workout-store: 14 new cases covering deleteSet,
unconfirmSet, removeActivity (undo-clearing side effect), and
skipActivity (Set reference isolation after finishWorkout reset)

S052 -- set-row: 3 new cases for swipe-to-delete DEL button
(renders with onDelete, absent without, click fires callback)

S053 -- start-workout-flow: 2 B008 regression cases (saved gym
skips picker, no saved gym opens picker); also fixes the 4
pre-existing tests which were broken by the B008 change (readLastGymChoice
was not mocked, causing failures at the new call site)

S054 -- rest-timer-banner: new test file, 11 cases covering
progress bar clamping, total=0 guard, Expand/Skip callbacks,
and countdown text rendering

127 test files, 2307 tests passing

Review: Context/Reviews/0018-pr109-workout-set-ux-2026-04-13.md
Remove three try/catch wrappers in use-active-workout.ts whose catch
blocks only re-threw -- errors now propagate naturally to callers.

Replace useEffect(() => { if (!restTimer) setRestMinimized(false) }) with
a direct setRestMinimized(false) call inside handleConfirmSet, ensuring
each new rest timer starts expanded without triggering the
setState-in-effect lint rule.
@rghamilton3
rghamilton3 merged commit 87737fd into main Apr 14, 2026
4 checks passed
@rghamilton3
rghamilton3 deleted the feat/workout-set-ux branch April 14, 2026 21:59

This branch was successfully deployed

1 active deployment
Preview — 0fa1b450 Deployed Apr 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant