Skip to content

HEP / Plan Builder Redesign — Variant C → Final Synthesis (G) — Detailed Changes

Date: 2026-06-29 (Variant C) · Updated: 2026-07-02 (Synthesis G) · Status refreshed: 2026-07-29 Repos: OktaPT-FE Status: shipped on development, behind the "New plan builder (beta)" toggle, default off. The classic InitialPlanCreationModal / EditPlanModal remain the default path and are untouched.

History: - Variant C — merged into development (commit 003010c). - Six refinement variants HEP-EDITS-A…F — worktree explorations on top of merged C (AI integration + polish, each with a different bet). Superseded. - HEP-EDITS-G — the final synthesis: best-of-breed from A–F plus new work. Scope: 19 files, +1,320 / −246, 2 new files. Merged into development via PRs #559 and #561 (commits cbc446b, fa7d84c, 88dabf7). The split view, docked AI column, useNewBuilderPref, addInstances, fromAiPlan, and the actualDate → focus type fix are all live at HEAD. - Post-G polish (PR #563, 3f2bd7c, 2026-07-07) — newly added exercises flash and scroll into view; useInitialPlanState.getDateForDay fixed so day 1 is today rather than tomorrow, and dates are formatted locally instead of via toISOString, which had shifted them by a day in negative-UTC-offset zones.

See HEP Builder for the user-facing description.


Summary

A clinician-facing builder for creating and editing a patient's Home Exercise Program (HEP). The goal: as fast as writing exercises on paper, as structured as software — "this one a couple times a week, this one every day, twice a day" — while keeping the software powers (multi-session days, videos, library, RTM).

The builder is a full-screen Sheet with a persistent exercise library palette and three synced projections of one canonical store:

  • List view — per-exercise "prescription" rows: tap/typeable dose steppers, Daily / Weekdays / M·W·F / Custom cadence presets, a 1× / 2× / 3× per day control, inline + Note, color-coded blocks.
  • Week view — a 7-day grid with AM/PM/3rd session lanes; drag from the palette into a day/lane.
  • Split view (G) — on ≥1536px screens, List + a live Week mirror side-by-side (the original "Studio" concept): edit a row on the left, watch the week update on the right. Default at that size; falls back to the List/Week toggle below it.

An AI assistant (G) docks as a right-hand column: seeded from the AI-notes step (notes + attachments + plan summary), tenant-gated guidelines picker, and the canvas as its stage — streaming preview while generating, old→new PlanComparisonView on proposal. The create flow always starts at the AI-notes step; "Skip & create manually" opens the builder blank, so AI assists but never blocks.


Architecture (the key bet — unchanged since C)

Canonical store = the existing day-indexed Maps; cadence is a pure projection on top.

useEditPlanState / useInitialPlanState store the plan as workouts: Map<dayNumber, DayWorkout> + extraSessions; deriveRows groups instances by identical editable fields into Prescription Rows with a derived schedule; useCadenceRows translates row edits back into Map mutations. Create/edit/save/load/dirty-tracking/started-day locking all come from the existing hooks; cadence is frontend-only — no new DB columns. (Details: lib/utils/cadence/*, lib/hooks/useCadenceRows.ts.)

G adds three additive projection layers, none of which touch the canonical store:

  • Media hydration — AI/template exercises arrive as id+name only; useExerciseDetails + enrichExerciseWithMedia project enriched copies into the surface (create mode, or once the assistant has been used) so thumbnails render; media is not part of groupKey and save strips it.
  • AI assistant — reuses useAIPlanEdit (same hook as the classic modals); accept writes proposed days via plan.addDayWorkout, skipping locked days, preserving the AI's per-day assignment.
  • RTM time tracking — useRtmTimeTracking records the same billing-relevant sessions the classic modals do.

How G was decided (the variant program)

Six parallel variants were built on merged C and reviewed side-by-side. What each contributed / where each lost:

Variant Adopted into G Rejected
A Custom as an explicit 4th cadence segment; auto-open assistant when AI-seeded Always-on media hydration; dead onRetryAI; chip-click bubbling
B useNewBuilderPref concept; RequestConnection (onboarding) entry Palette Library/AI mode toggle (hid scope/tags); "apply to every day" accept checkbox; no-clone seed; unseeded chat; no hydration
C Library-faithful palette AI search (in-grid AiSearchButtonCard + ring + banner); "2×/day" label; chip stopPropagation Palette hidden whenever AI open; no pref persistence
D Slide-in panel's clean chip handling Overlay embodiment (blocks the plan while chatting); summary-only chat seed
E Docked AI column + canvas-as-stage; mobile close-on-send + AIMessageBanner; actualDate→focus type fix at the source Flatten-to-Daily AI seeding (discarded the AI's periodization); chip-click bubbling
F Foundation: conditional media hydration, RTM tracking, useSyncExternalStore pref, fullest onboarding wiring, per-day-preserving fromAiPlan, Custom segment, palette-hosted Templates Overlay panel (superseded by E's column)

All six agreed on (adopted): AI-notes-first create flow, clickable week chips → detail dialog, inline custom day-dots, "2×/day" wording, palette AI search, useCadenceRows.addInstances bulk op for templates.


G changes by file

File Δ What changed
hep-builder/HepBuilder.tsx +832/−~120 AI column (guidelines picker, notes-seeded chat, auto-open on seed); canvas stage (streaming / comparison takeover, palette steps aside); split view (viewMode: list\|week\|split, live matchMedia 1536px gate, default split at 2xl); media hydration; RTM; armed add-target; destination toasts for click and drag adds; templates fetch guard + error toast
hep-builder/ExercisePalette.tsx +178 AI search (shared library components, ring on AI matches, banner); Templates button (mobile-sheet-reachable); search autofocus (desktop); Enter adds top match (falls through to AI search on no match); armed-target context chip ("Adding to Wed · PM ✕"); feedback moved to the builder (no double toasts); 36px add overlay on touch
hep-builder/PrescriptionRowCard.tsx +141/−~90 Explicit Custom cadence segment + inline day dots (shared renderDaysBlock); name no longer hard-capped at 128px (flex-1 line-clamp-2); drag handle aligned to thumbnail height (64px grab target); 2×/day enabled on mixed rows (no active chip + tooltip; tap unifies); remove moved to the card footer as a trash icon (was top-right, crowding the Reps/Time toggle)
hep-builder/SessionLane.tsx +51 Chip remove visible on touch (md:-only hover reveal) with a 32px hit area; chip click → details with stopPropagation; empty/"+ session" hints are now buttons that arm the add target
hep-builder/WeekView.tsx / DayColumn.tsx +24/+8 onAddHere threading; compact mirror wraps the week to two rows (4+3) so days are ~2× wider in split view; day pills py-2
hep-builder/controls/* (5 files) +40 Touch-target pass: Stepper buttons w-10 md:w-8, Segmented xs/sm grow on mobile, DayDots 40px, ScalePicker 36px; Segmented gains title; AmPmToggle gains mixed; "AM·PM" → "2×/day" (ru "2×/день")
lib/hooks/useCadenceRows.ts +38 addInstances(templates, schedule?) bulk insert (single write per session — safe where an addRow loop would clobber itself); day defaults use shared PLAN_DAYS
lib/hooks/useNewBuilderPref.ts new (49) Persisted beta preference — localStorage key oktapt.useNewBuilder, default false, SSR-safe (starts false, hydrates on mount). Synced by a custom oktapt:useNewBuilder window event (same-tab, multiple consumers) plus the native storage event (cross-tab). Implemented with useState + useEffect, not useSyncExternalStore as the variant-F row above describes
lib/utils/cadence/fromAiPlan.ts new (32) AI plan → canonical Maps, preserving per-day assignment (clones instances)
lib/hooks/useAIInitialPlanGeneration.ts 2 Type fix at the source: workoutsMap value actualDate → focus (was wrong at runtime)
pages/doctor/dashboard.tsx +23 Persistent pref; create always starts at the notes step; remount key={create-…-ai\|blank}; passes aiGeneratedPlan/userPromptNotes/userAttachments; skip-to-manual clears notes (stale-notes leak fix)
components/RequestConnection.tsx +110 Patient-onboarding entry: new builder behind the same pref, notes restored via initialNotes, notes/attachments forwarded to both builder branches; hook-order fix (early initialData return moved below hooks)
public/locales/{en,ru}/common.json +19 each view.split ("Split"/"Оба"), toast.addedDaily/addedTo, palette.addingTo/templates, templateAdded, templatesError, perDay.mixedHint; RU keeps fitness wording

UX rough edges fixed (present in merged C, fixed in G)

  • Week-chip remove was hover-only → unreachable on touch.
  • "+ session" looked like a button but was drop-only → now clickable (arms the add target, opens the palette sheet on mobile).
  • Click-add destination differed by view with no signposting; drag-adds were silent → unified destination toasts ("Added Squats — daily" / "Added Squats → Wed PM").
  • "Custom" cadence had no selected state → explicit segment.
  • 2×/day silently disabled on non-uniform rows → enabled with mixed state + unify-on-tap.
  • Sub-40px tap targets throughout (steppers 32px, scale dots 28px, xs segments) → mobile-first size pass.
  • Exercise names hard-truncated at 128px; magic mt-5 handle offset → proper flex layout.
  • Remove (X) crowded the Reps/Time toggle → trash icon in the card footer.

Out of scope (deferred) / known simplifications

  • No missed-session roll-forward; cadence criteria still not persisted (compiled to sessions); no new DB columns.
  • Week within-lane reorder doesn't enforce block order; cross-lane chip move still remove + re-add.
  • Keyboard activation of week-chip details deferred (Enter/Space are dnd-kit's drag-lift keys on that element; needs a dedicated focusable control). Details remain keyboard-reachable via List view.
  • Mobile dictation entry — future.

Open items

  • ~~Adopt G: merge HEP-EDITS-G → development~~ — done (PRs #559, #561); worktrees A–G still to clean up.
  • Flip the beta toggle default (or remove the classic modals) now that G has soaked.
  • ~~Consider persisting cadence criteria + missed-session auto-roll as the next iteration.~~ — done 2026-09-29: Plan Scheduling Redesign.

Verification

  • tsc --noEmit: 0 errors; locale JSONs valid; EN/RU hepBuilder.* key parity.
  • External code review (2026-07-02): 6 findings fixed (templates error surfacing + in-flight guard, RequestConnection hook order, stale skip-to-manual notes, PLAN_DAYS constant, richer List-view detail payload, DayDots dedup); 2 rejected with rationale (RU "Оба" over "Сплит" — fitness-jargon collision; naive chip onKeyDown would break dnd-kit keyboard drag).
  • Run: now on development in the main checkout — npm run dev, then tick New plan builder (beta) on the doctor dashboard. Suggested checks: create via notes→AI (column auto-opens, per-day seed) and via skip (blank); split-view sync + drag into a day; "+ session" arming (desktop + mobile); Enter-to-add; mixed 2×/day unify; template inject (thumbnails via hydration); chip remove on a phone viewport; RU locale.