Files
workouts/UX-REDESIGN.md
T
rzen 6e440317c4 Restructure into a three-tab app with Progress, goals, and Meditation
The UX redesign's first landing (spec in UX-REDESIGN.md): ContentView
becomes a Today / Progress / Settings TabView, "Routine" replaces
"Split" in every user-facing string and view name (code-level types
keep their names), and workout starting moves to shared
WorkoutStarter / StartedWorkoutNavigator plumbing.

- New Progress tab: weekly goal streaks, workout trends, per-exercise
  weight progression, achievements, and the full history list
  (WorkoutLogsView -> WorkoutHistoryView).
- Goals: stable categories workouts roll up to, managed from Settings.
- New Meditation exercise + starter routine; timed sits record to
  Apple Health as Mind & Body sessions.

Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4
2026-07-11 07:53:01 -04:00

241 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UX Redesign — Goals, Schedules & the Today Board
Design direction agreed 2026-07-09/10. This restructures the app from a
single-screen history list into a four-tab app built on three independent
layers — routines, schedules, goals — with a **Today board** as the entry
surface, and rides a full visual refresh along with it. The sync architecture
is untouched; the data-model additions are two small new document aggregates
(Goals, Schedules) on the existing machinery.
## The three layers
```
ROUTINES the content — what "Lower Body" or "Evening Meditation" consists of
SCHEDULES the plan — "Lower Body · every Mon & Thu · → Strength"
GOALS the meaning — stable categories everything rolls up to
```
**You plan with schedules, act on routines, observe by goals.** The three are
deliberately independent:
- **Goals outlive plans.** Switching from a 3-day to a 5-day program rewrites
schedules, but the Strength *track* in Progress continues unbroken — history
rolls up to the goal, not to the plan that produced it. The goal is the
ledger category; a schedule is how you're currently trying to fill it.
- **Goals are optional.** A schedule with no goal tag still appears on the
board and logs normally; it rolls up as uncategorized. Pure-scheduling use
works; goals can be adopted later.
- **Scheduling never touches routine files.** The schedule is the join object
(it references `routineID` and `goalID` from its own document), so
scheduling an immutable fixed-ULID starter routine never forks the seed.
### Terminology: "Routine" replaces "Split" (user-facing)
"Split" is lifting jargon; the entity is now general — a meditation session, a
mobility flow, a run. UI strings say **Routine** everywhere; starter splits
become starter routines. Code-level type names (`Split`, `SplitDocument`, …)
stay as-is for now — a rename is churn with no user value; revisit only if it
starts to confuse.
### Goals
Just **name + color/icon + order** — no cadence, no machinery. Examples:
Strength, Endurance, Morning Movement, Mindfulness. Persisted as
`Goals/<ULID>.json` (standard three-place data model). Workouts additionally
**stamp the goal at completion** (denormalized `goalID`/`goalName`, same
pattern as the existing `splitID`/`splitName`), so history keeps the
attribution it had when logged even if schedules are re-tagged later.
### Schedules
A schedule = **one routine + a recurrence + an optional goal tag**, persisted
as `Schedules/<ULID>.json`. Recurrence flavors:
1. **Fixed days** — "every Mon & Thu". Deterministic; the board just shows it.
2. **Frequency** — "2×/week, whenever". The planner picks the days.
3. **Daily** — e.g. the morning routine or evening meditation.
Plus an optional **time window** (e.g. morning, default cutoff ~11 am,
tunable) with a per-schedule setting for whether the window gates the streak:
on = only in-window starts feed the streak, though the row stays startable and
completable all day; off = any-time completion counts.
**Pool rotation is emergent, not a feature**: three 1×/week frequency
schedules (Legs, Upper, Core) under Strength rotate naturally via the
planner's recency pressure. Schedules stay minimal.
**Missed fixed days get a catch-up nudge**: the schedule reappears next day as
due-with-a-reason ("missed Mon · catch up?"), planner-placed on the best
make-up day before the next fixed slot (recovery-aware in Phase 2), with an
explicit skip. Skipping just waits for the next fixed slot.
Example of the model's reach: *Goal: Mindfulness ← Schedule: "Meditation ·
daily · evening window" ← Routine: one duration-based Meditation exercise*
(`LoadType.duration` already exists; add a `mindAndBody` case to
`WorkoutActivityType` — optional-int wire field, no schema bump — mapping to
HealthKit's mind-and-body session on the watch).
## Information architecture
Four tabs: **Home · Progress · Library · Settings**.
| Tab | Contents | Provenance |
|---|---|---|
| Home | The Today board (new) | New surface |
| Progress | Per-goal tracks + trends + full history + achievements | Absorbs `WorkoutLogsView`, promotes `WeightProgressionChartView` / `ExerciseProgressView` |
| Library | Exercise library | Promotes `ExerciseLibraryView` out of Settings |
| Settings | Slimmed settings + goal/schedule editing + About | Existing, minus Library |
Routine management stays reachable from Home — setup, not a daily
destination. The `SplitDetailView`/`ExerciseListView` duplication is
consolidated during this restructure (ExerciseListView is a pure duplicate).
The watch app is untouched: it remains a thin remote; nothing changes the wire
format until Phase 2's optional location field.
## Home — the Today board
Home is **the today-projection of the user's schedules**: a flat, stable,
time-ordered checklist where each row wears a small colored goal tag. It is
the jumping board for the day, a guide, and a motivational aid — in that
order. Boxes fill up as the day goes on.
```
Thursday, July 10
┌──────────────────────────────────┐
│ ✓ Mobility ·7:04· [Morning] 🔥12│ done — stays in place, streak ticked
│ ● Lower Body [Strength] │ fixed schedule, due today
│ Mon & Thu · ▶ Start │
│ · Zone-2 Ride [Endurance] │ flexible — ambient status row
│ 1 of 2 this week · 3 days left │
└──────────────────────────────────┘
This week ▪▪▪▫▫▫▫ + one encouraging line
```
- **Row states**: *due* (start button, streak at stake — fixed schedules on
their day, flexible ones when the planner says so, catch-ups after a miss) →
*in progress* (resume affordance, always wins attention) → *done* (✓, time,
bumped streak). Done rows stay in place — the agenda's shape is stable and
the day visibly fills up. Flexible schedules not due today render as
**ambient** compact rows (week progress, on-pace, next suggested day),
tappable to start anyway. Never blocked, just not shouting.
- **Due rows carry reason chips** from the planner ("missed Mon", "5 days
since legs · you're usually here Thursdays") — never a black box. An
override is a non-event and becomes signal.
- **Rest is declarable per row**: "make it a rest day" checks a due row off
guilt-free; adherence treats a declared rest as met, and the board stays
completable. (The planner can also propose rest when everything scores low.)
- **Sprinkles**: per-row streak flames, the week strip vs. usual pace, a
clean-sweep moment when the last due row completes, then a small tomorrow
preview.
- **Degrades gracefully**: no schedules → bootstrap proposal card; one
schedule → a one-row board, i.e. a classic single-suggestion dashboard.
## The planner — `NextSplitPlanner`
A **pure, unit-testable scorer** (precedent: `SeedReconcilePlanner`). With
explicit schedules, its scope narrows to where inference is genuinely needed:
- **Placing flexible (frequency) schedules** — which of the remaining days
this week, driven by recency pressure vs. days left.
- **Catch-up placement** — the best make-up day for a missed fixed slot
before its next occurrence.
- **Ordering emergent rotations** — several flexible schedules under one goal
rotate via recency pressure.
- **(Phase 2) Recovery guard** — overlap between a routine's target muscles
and muscles trained in the last ~48 h downweights it, using the bundled
library's `Targets:` metadata (already parsed by `ExerciseInfo`). Because it
looks across *all* recent workouts it is also the cross-goal coordinator
("no heavy legs the morning after a long run").
- **(Phase 2) Location affinity** — boost routines historically done at the
current place; missing/denied location contributes nothing.
Weekday affinity remains a minor learned signal for flexible schedules.
**Cold start:** with little history, flexible placement falls back to even
spacing and says so. Deterministic, hand-tuned weights, fixture-history unit
tests.
### Location capture (Phase 2)
- First workout start after the update requests **when-in-use,
reduced-accuracy** location (place-level is all we need). Denial is
respected silently forever.
- A coarse fix is stamped on `WorkoutDocument` as an **optional field** — no
schema bump; older decoders ignore it (same rationale as `activityType`).
- Places are **derived at read time** by proximity clustering (~150 m);
clusters are never persisted — consistent with the rebuildable-cache
philosophy.
## Streaks & adherence
- **Computed per schedule**: did "Lower Body" hit Mon & Thu this week; did
"Meditation" happen daily. Declared rests count as met. Streak state is
always **derived** from workout documents — never persisted.
- **Rolled up per goal**: a goal's streak = consecutive weeks in which *all*
its member schedules hit their recurrence. Precise diagnostics ("Upper is
what's slipping") with an honest rollup.
- Week-based schedules are forgiving by construction: a missed day breaks
nothing until the week itself is missed.
## Progress
- **Per-goal tracks first**: each goal's adherence over time, fed by
per-schedule adherence; then aggregate trends — weekly volume, workout
frequency; muscle-group balance (Phase 2). Time ranges per the
trend-visualization pattern (7D/30D/90D/1Y/All).
- **Per-exercise drill-down**: the existing weight-progression and exercise
progress views, promoted and restyled; later pinnable to a goal as an
optional **outcome metric** (observed, never promised — goals themselves
stay practice-anchored).
- **History**: the current `WorkoutLogsView` list lives here.
- **Achievements section**: a badge grid within Progress. All achievements are
**pure derivations** (per-goal streak milestones, PR counts, volume/session
milestones) — recomputable after any cache rebuild. Persisting earned-dates
is deferred until we want one-time celebration moments.
## Visual redesign
Rides Phase 1 rather than following it: a shared component set — card, stat
tile, board row (due / ambient / done variants), goal tag, badge, reason chip
— with the iOS 26 glass treatment, applied as each surface is built. Existing
screens (run flow, routine editing) are restyled to the same system as they're
touched. Theming continues through `Color+Extensions`.
## Phasing
- **Phase 1 — goals + schedules + shell + design system**: Goal and Schedule
document aggregates (documents + cache entities + mappers + editing UI +
activity-type **bootstrap** that proposes initial goals/schedules from
existing routines); 4-tab `TabView`; Today board v1 (due/ambient/done rows,
catch-up nudges, resume, declared rest); planner v1 (recency + even-spacing
placement, weekday affinity); Progress tab (per-goal tracks + history +
promoted charts + achievements grid); Library promoted; Settings slimmed;
"Routine" terminology pass; view-duplication consolidation; component set.
- **Phase 2 — the senses**: location capture + place affinity, recovery guard
(cross-goal coordination), muscle-balance chart, outcome-metric attachments,
`mindAndBody` activity type + watch session mapping, richer achievements.
- **Phase 3 — the library grows**: external exercise ingestion — exercises
become user documents (new aggregate alongside the bundled library; real
schema work). Library UI is designed now to accommodate this.
## Open questions
- Tab icons/names final pass (Home/Progress/Library/Settings working titles).
- **How declared rest / explicit skip persists.** Purely local UI state won't
survive device switches or cache rebuilds. Candidate: a `WorkoutDocument`
with `status: .skipped` for that schedule's routine (the status already
exists) — cross-device and derivation-friendly. Decide before Phase 1 build.
Related: distinguish *abandoned* (never started / walked away) from an
*explicit skip*, possibly carrying an "excuse" ("sick day") that adherence
treats as met. The board's past-day rows currently show an undifferentiated
"Skipped" tag as the placeholder for this.
- Where goal/schedule editing lives: Settings vs. from the board (long-press
a row / "edit plan" affordance) vs. both.
- Clean-sweep celebration + tomorrow-preview treatment (how loud, how playful).
- Default morning cutoff: fixed ~11 am vs. learned from the user's own
distribution.
- Whether the watch should eventually show the Today board / row states
(would need the computed projection pushed in the application context —
cheap; revisit after Phase 1).
- How far the "practice tracker" identity goes (app is named *Workouts*;
mindfulness fits the model today — does the branding follow eventually?).