Canonical source: docs/claude/app-redesign-activity-execution-build-plan.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.
Activity Execution — Implementation Map
Build plan for the redesign per-type activity execution surfaces + states (Figma file 2Vs1e9IZVQh8KpMxc0W3PT, frames Activity · Medication/Supplement/Meal/Strength + Meal · Logged, Workout · Auto-completed, Timed · Active/Complete). Grounded against real basishybrid code. Legend: ✅ exists/reuse · 🔶 net-new · ⚠️ data-integrity prerequisite.
0. Architecture decision
Keep the data + completion layer, replace the presentation. The current detail modal (showBasisModalSummary → WidgetModalSummaryV2, lib/view/routes/summary/route_summary.dart) already:
- loads all four assigned presets onto the event (
_loadRecurringEventPresets, ~:1032), - delegates to per-type sections that carry the real completion writes (
~:1642-1682), - renders the HR time-series graph + zones + hypnogram (
WidgetModalSummaryGraph,:532).
So we do not rebuild logic. Two viable shells:
- (A) Restyle-in-place — reskin
WidgetModalSummaryV2sections underkFeatureRedesignedHome, fix the modal theme (it currently renders light — see below). Lowest new-code, but the file is 2.4k lines with a duplicated client/coach loader (~:1050-1350and~:1750-1975). - (B) New redesign route
rp_activity_detail.dart— reuse the preset-loader (extract_loadRecurringEventPresetsinto a shared helper) + call the same event-write handlers, rendered withAppTokens. Cleaner separation, matches therp_*pattern.
Recommendation: (B), with the loader extracted to a shared ActivityPresetResolver so both the legacy modal and the redesign route call one code path (also fixes the client/coach duplication). Entry point stays rpOpenEventSummary(context, event) (already wired in rp_live.dart / rp_plan_live.dart) — repoint it from showBasisModalSummary to the new route.
Theme note: the reused legacy modal renders light because its content has no
Material/Scaffoldto turn the theme'stextThemeinto aDefaultTextStyle. A new redesign route sidesteps this entirely (it's a themedScaffold).
1. Shared components (build once, used by every type + state)
| Component | Data source | Status |
|---|---|---|
RpActivityShell — status bar + header (back, title, •••) + protocol-progress bar + bottom CTA slot | — | 🔶 (chrome; trivial) |
RpProtocolProgressBar — "Day N of M" + bar, shown only when protocol has an end date | protocol doc assignedAt/scheduledFrom → scheduledUntil/endDate (already read in widget_protocol_detailed.dart _scheduleRange/_computeWeekProgress :157) | ✅ compute exists · 🔶 UI |
RpCadenceStrip — weekday circles (done/today/due/off) + streak text | scheduled weekdays = BasisRecurringEventV1.rule.byWeekDays (event_recurring.dart); streak = ServiceStreak RecurringStreakInfo (service_streak.dart:180); "N of M this week" = completed scheduled events this week ÷ scheduled-count | ✅ rule + streak exist · 🔶 UI + this-week aggregation |
RpActivityCadence model — {scheduledDays[], todayIndex, weekStates[], streakCount, doneThisWeek, dueThisWeek} | derived from rule + ServiceRecurringEvents.determineCompletionStats (service_recurring_events.dart:1420) | 🔶 thin derive layer |
The cadence strip is the answer to "streak isn't only daily": it reads the activity's own recurrence (byWeekDays), so peptide Mon/Thu/Sun, supplement daily, and workout 4×/wk each render their real schedule. Put it on every activity + every state.
2. Data foundations / prerequisites (do FIRST — the UI lies without them)
| # | Prerequisite | Where | Status |
|---|---|---|---|
| P0-a ⚠️ | Medication onto the event model. Today per-dose "Taken" writes to an ad-hoc events/{id}.medicationCompletions map (widget_section_medication.dart:253-298), invisible to determineCompletionStats + ServiceStreak + instance history. Add a structured event.medications: List<BasisMedicationV1> (mirror event.supplements) with per-med completionState; migrate the toggle to write it; keep back-compat read of the old map. | basiscore event model + functions writer + widget_section_medication.dart | 🔶 model+backend+client |
| P0-b 🔶 | Meal target macros on the assigned preset. AssignedMealPreset (widget_section_medication.dart:6) is name/imageUrl/description only. Add targetNutrients {calories,protein,carbs,fat} so "logged vs target" is real. | AssignedMealPreset model + protocol assignment writer (basisflow-web/functions) | 🔶 model+backend+assign UI |
| P0-c 🔶 | Medication video field. Add videoUrl/videoRef to the assigned medication (mirror BasisExerciseV1.videoUrl, exercise.dart:89) + a place to set it in protocol assignment. | AssignedMedication model + assign UI | 🔶 model+assign UI |
| P0-d ✅ | Protocol progress needs no new data — assignedAt/scheduledUntil/endDate already on protocol docs. | — | ✅ |
3. Per-type detail — element → code map
💊 Medication (Activity · Medication)
- Assigned data ✅
AssignedMedication{name,dosage,unit,schedule,timing,brandName,notes}(widget_section_medication.dart:27), loaded via_loadRecurringEventPresets. - Dose-prominent + route/timing chips ✅ all fields present.
- "Why" line ✅
notes(or protocol rationale). Skip · reason 🔶 new sheet → write a skip+reason onto the med completion (needs P0-a model). - Per-dose video 🔶 needs P0-c; player reuse ✅ (same as exercise video modal).
- Taken toggle ⚠️ rewrite to P0-a structured write (not the off-model map).
- CTA "Mark all as taken" → sets each med
completed+ rollsevent.completionStatus=completed(pattern exists:287), firesServiceStreak.updateStreaks.
🧪 Supplement (Activity · Supplement)
- Flat list, timing-in-name ✅ just render
event.supplements/ assigned preset flat (drop AM/PM grouping + count stats). - Per-item provider note ✅
BasisSupplementV1.units[].annotation+suggestedUse(supplement.dart) already carry it. - Per-item check ✅
_onCompleteSupplement(widget_section_supplement.dart:202) togglescompletionStateonevent.supplements; auto-rolls event complete. Reuse as-is.
🍽️ Meal (Activity · Meal)
- Recommended-meal hero ✅
AssignedMealPresetname/image/description (real assignment). - TARGET macros 🔶 needs P0-b.
- Scan/search log CTAs ✅ route to
RouteMealScan(Open Food Facts +analyze_meal); actual writes toevent.meals(BasisMealV1w/ fullnutrients) viaWidgetSummarySectionFood(widget_section_food.dart:740).
🏋️ Exercise (Activity · Strength) — mostly reuse
- Per-set weight×reps logging ✅
WidgetInlineExerciseEditor(widget_section_exercise.dart:50-256) →event.exercises. - Per-exercise complete + auto-roll-up ✅
_onCompleteExercise(:537),_onCompletePresetExercise(:588). - Video ✅ field exists (
BasisExerciseV1.videoUrl); populate URLs 🔶. - Rest timer / last-session reference 🔶 net-new UI (data: prior
event.exercises). - Work here is reskin only (+ the two 🔶 additions).
4. States — element → code map
Meal · Logged (post-scan)
- Scanned photo + detected items ✅ from
RouteMealScanresult (event.meals[].images,.items). - Logged-vs-target macro bars ✅ logged
nutrients(SummaryObjectMealNutrientsV1, real) vs target (needs P0-b). 🔶 bar UI. - Atlas on-target note 🔶 consumer-Atlas call (optional; can be a simple rule first).
Workout · Auto-completed
- Auto-complete ✅ wearable-detected summary events already flow in (Terra/HealthKit →
BasisEventV1summary); auto-complete when matched to a scheduled activity 🔶 (matching logic). - Session HR graph + zones ✅ reuse
WidgetModalSummaryGraph(route_summary.dart:532) — it already rendersWidgetGraphData(HR time-series) +WidgetZoneSection(zones). Just skin. - Stats (dur/dist/HR/kcal) ✅
keyStatson the summary. - "Counts toward plan · N of M this week" 🔶 = plan-match + cadence (shares
RpCadenceStrip).
Timed · Active / Complete (fasting / sauna)
- Ring timer ✅ timer state from
ServiceTimer/BasisDurationTimer(TimerType.event; used inwidget_section_events.dart:154). 🔶 ring UI. - Start/pause/end ✅ existing timer controls (
showBasisModalTimer,time(event)inSummaryActions). - Complete summary + streak ✅
event.completionStatus=completed+ServiceStreak; 🔶 ring/summary UI.
5. Sequencing (phased, dependency-ordered)
Phase 0 — data foundations (backend + model; unblocks honest UI).
P0-a medication-on-model (highest value), P0-b meal target macros, P0-c med video field. Each = model field + functions writer + (b,c) assignment UI. Deploy via ship.sh.
Phase 1 — shared redesign shell + components.
RpActivityShell, RpProtocolProgressBar, RpCadenceStrip (+ RpActivityCadence derive). Extract ActivityPresetResolver from _loadRecurringEventPresets (kills the client/coach dup). Repoint rpOpenEventSummary → new rp_activity_detail.dart route.
Phase 2 — per-type detail (reuse handlers). Supplement (pure reuse + reskin) → Meal (hero + target, needs P0-b) → Medication (reskin + video + P0-a taken-write + skip-reason) → Strength (reskin + rest timer/last-session).
Phase 3 — states.
Workout · Auto-completed (reuse WidgetModalSummaryGraph + plan-match) → Meal · Logged (reuse scan + macro compare) → Timed Active/Complete (reuse ServiceTimer).
Phase 4 — verify on device. Real account (axuk-khwf-prkr, uid GoOU3d2QcyRZMD83IhbQeIclpwh2), each type end-to-end (assign in basisflow-web → execute in app → confirm completion + streak + adherence reflect it). Vision/video best tested on a physical device.
6. What's genuinely new vs reuse (headline)
- Reuse (already coded): per-set exercise logging, supplement completion, meal scan + nutrients, HR graph + zones + hypnogram, timer service, streak service, week/adherence compute, protocol progress compute, biomarker baseline→current.
- Net-new (build): medication-on-model (⚠️ + unblocks adherence), meal target macros, med video field + player wiring,
RpCadenceStrip(schedule-aware) + protocol-progress UI, the redesign presentation layer, plan-match for auto-complete, rest-timer/last-session reference. - The one trap: medication adherence/streak is untruthful until P0-a lands — don't ship the med streak before it.
6b. Workout planned↔wearable merge — VERIFIED model (build on this)
Link-and-union, NOT a physical merge. User-logged data is safe by construction.
- Planned strength habit is seeded from
workoutPreset→event.exercisesat schedule time (service_recurring_events.dart:843-855). Target and actual share ONE list; the user edits rows in place viaupdateEventForUser(read-modify-write; writes only touched fields) —widget_section_exercise.dart:617-653. - Wearable session = separate
BasisSummaryV1(HR/cal/duration) in a different collection. On landing, client-side auto-complete (service_daymemory.dart:1062-1071) matches nearestunknown-status habit of same activity-family within 10h window (service_recurring_events.dart:692-736) and stampscompletionStatus=autoCompleted+autocompletedBy=summaryId+ wearable start/end/deviceInfo — never touchesexercises. - Detail renders the union: fetch summary via
autocompletedByfor the HR graph (route_summary.dart:147-177) while the event keeps logged sets. - Safety (3 guarantees): RMW write only mutates time/status/link/device; auto-complete gated to
unknown-status only (won't re-process completed/edited); summaries are a separate table with noset-without-merge on the event doc. - Caveats: auto-complete overwrites the event's TIME WINDOW (not sets); no per-set notes/RPE field; no edit-preservation flags (safety is structural). The "canonical record + per-field provenance" in
app-redesign-data-contracts.md §1is aspirational, not built — and not needed for this build. - Build implication: Strength + Workout·Auto detail just render
event.exercises+ fetchautocompletedBysummary. No new merge engine.
6c. Added activity types/states (Figma)
- Activity · Measurement (121:825) — value-entry rows (weight/waist/hip/body-fat) + last value + trend link; feeds metric graphs + biomarker progress. NEW type for body measurements in protocols.
- Activity · Meditation (122:825) — session-DB picker (recommended + options; net-new meditation DB, same shape as meal templates) + Start-session timer + mark-done-without-timer.
- Timed · Active now offers mark-done-without-timer (exists in current code) alongside the ring timer.
- Meal · Logged detected items are editable rows (name/qty/calories + Add item) — manual edit already exists (
widget_section_food.dart _editQuantity/_editNutrient), preserve alongside AI edit.
7. Key file anchors
- Entry/modal:
lib/view/routes/summary/route_summary.dart(WidgetModalSummaryV2,_loadRecurringEventPresets,WidgetModalSummaryGraph:532) - Sections:
widget_section_exercise.dart(WidgetInlineExerciseEditor:50,_onCompleteExercise:537),widget_section_supplement.dart(_onCompleteSupplement:202),widget_section_medication.dart(AssignedMedication:27,AssignedMealPreset:6,_onCompleteMedication:253),widget_section_food.dart(:740) - Completion mixin:
route_summary_actions.dart(complete/skip/undo, firesServiceStreak) - Streak:
services/service_streak.dart:180· Adherence/week:service_recurring_events.dart:1420,widget_protocol_detailed.dart:157 - Biomarker:
widget_biomarker_progress.dart(fromhealthSummaries) - Models:
basiscore/.../event/exercise_preset.dart,exercise.dart:89(videoUrl),supplement.dart,event/event_recurring.dart(rule.byWeekDays),event.dart(exercises/supplements/meals/*Presetmaps) - Redesign entry already wired:
rp_live.dartrpOpenEventSummary,rp_plan_live.dart_eventRow