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

13 KiB
Raw Blame History

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?).