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
This commit is contained in:
+240
@@ -0,0 +1,240 @@
|
||||
# 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?).
|
||||
Reference in New Issue
Block a user