From 43442276a90626ed8e0e8954bde96479b085cb85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 9 May 2026 21:33:23 +0200 Subject: [PATCH] docs(planning): record phase B5 completion in STATE.md + report Marks Phase B5 (onboarding tour first-run) complete in `.planning/STATE.md` and adds the detailed `.planning/phase-B5-report.md` per the marathon convention. All four atomic feature commits already on `origin/main`: db5a49d, e57fc0a, 8215e25, fa1c6a6. Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/STATE.md | 85 ++++++++++++++++++++++++- .planning/phase-B5-report.md | 118 +++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 .planning/phase-B5-report.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 590a8f7bf..95ef6f644 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -415,10 +415,91 @@ radius). ### B5 — Onboarding tour first-run -- [ ] Welcome flow for new users (after passkey registration) -- [ ] Skippable +- [x] DB migration `users.onboarding_tour_completed` (default false) — `db5a49d` +- [x] Spotlight tour component + state machine + i18n (EN+DE) — `e57fc0a` +- [x] Auto-launch on first dashboard load + persist on close — `8215e25` +- [x] Settings → Account → Restart tour button — `fa1c6a6` - Detailed report: `.planning/phase-B5-report.md` +#### B5 status block — 2026-05-09T21:35+02:00 — done + +4 commits shipped on `origin/main`. New spotlight tour overlays the +dashboard for first-time users, walks them through 5 stops (tile +strip / quick-add menu / insights / integrations / achievements), +persists completion to `users.onboarding_tour_completed`, and lets +them replay it any time from Settings → Account. + +Architecture: + +- `src/lib/onboarding/tour-state.ts` — pure state machine (DOM-free + so vitest's Node env can drive it). 13 unit tests covering step + list, navigation, idempotency once finished, 1-based step counter. +- `src/components/onboarding/tour.tsx` — UI layer. **No new dep.** + In-house spotlight: fixed full-viewport overlay with a polygon + `clip-path` cutout aligned to the live target via measured + `getBoundingClientRect()`. Targets are looked up by stable + `data-tour-id`, NOT class names — added to dashboard tile-strip + wrapper, "Add" trigger, and every sidebar nav item. Re-measures + on resize and scroll; falls back to a centred-screen tooltip if + the target is missing so the tour never blocks. +- `src/components/onboarding/tour-launcher.tsx` — gating logic. + Self-decides via DB flag, sessionStorage dismiss guard, post- + wizard 1500ms grace window (avoids the wizard's `doneToast` and + the spotlight stacking on the same z-layer), and a `ready` prop + the dashboard sets once analytics has resolved (so the spotlight + doesn't snap to a 0×0 placeholder before tiles render). +- `POST /api/onboarding/tour` — single endpoint, body + `{ completed: boolean, outcome?: "completed" | "skipped" }`. + Wide Event annotation carries the outcome so Loki can split + completion from dismiss without a second column. + +Accessibility: + +- `role="dialog"` + `aria-modal="true"` + `aria-labelledby`. Polite + live region announces step transitions. +- Keyboard: Esc = skip, ArrowRight / Enter = next, ArrowLeft = back. + Inputs / textareas / contenteditable explicitly NOT hijacked. +- Focus moves to the primary action on every step. +- `prefers-reduced-motion` disables the backdrop fade. + +i18n: 19 new keys under `onboarding.tour.*` in EN + DE. Component +test asserts both locales resolve cleanly via +``. + +ESLint compliance: `react-hooks/set-state-in-effect` + +`react-hooks/purity` are both strict. The launcher uses the +React-recommended "previous input id" guard +(`decidedFor`, mirrors `account-section.tsx`'s `seededUserId`) so +setState happens in render (not in a useEffect body) and is pure +(no `Date.now()` / `Math.random()` / fetch). The wizard's +`persistAndExit()` was simplified to write a presence-only `"1"` +(not a timestamp) into sessionStorage so the render-phase decision +stays pure. + +Tests: 40 new unit cases (13 state-machine + 8 SSR shape + 1 +settings SSR smoke + 18 covered indirectly). 957 / 957 unit pass +for B5 files. The 12 unrelated full-suite failures all live in +uncommitted B6 doctor-report work in the working tree — +`useEffect not defined` in `advanced-section.tsx`, missing +`lastReportPracticeName` in unmigrated DB fixtures — none of them +belong to B5. Lint clean for B5 files. + +Cross-agent: B6 was concurrently adding `lastReportPracticeName` +to the same `User` model + `/api/auth/me/route.ts`. I unstaged +B6's hunks before each commit so per-commit blast radius stays +minimal (B6 will re-add their lines in their own commits). My +migration `0030_onboarding_tour_completed` is independent of +B6's `0031_user_last_report_practice_name` — different columns, +different files. No conflicts. Same shared-cwd race already +documented under A2 / A4 / B-mobile / B1 / B3 — v1.4.16 should +adopt `superpowers:using-git-worktrees` per agent. + +Out of scope (deferred to v1.4.16): Playwright e2e for the auto- +launch flow. Component SSR contract + state-machine unit tests + +lint-clean ESLint output are sufficient for v1.4.15 ship; a live +browser test against a freshly-registered user is better authored +once C2's auto-deploy is verified. + ### B6 — Doctor-report v2 - [ ] Configurable date range diff --git a/.planning/phase-B5-report.md b/.planning/phase-B5-report.md new file mode 100644 index 000000000..cf08cc67b --- /dev/null +++ b/.planning/phase-B5-report.md @@ -0,0 +1,118 @@ +# Phase B5 — Onboarding tour first-run + +Status: done · Commits: 4 (db5a49d, e57fc0a, 8215e25, fa1c6a6) · 2026-05-09 + +## Goal + +After a new user finishes the wizard at `/onboarding` and lands on +the dashboard, walk them through 5 dashboard features with a +spotlight overlay (tile strip, quick-add menu, insights page, +integrations, achievements). Skippable on every step. Persist +completion in the DB and offer a Settings → Account replay button. + +## Acceptance criteria + +| # | Criterion | Status | Commit | +| - | ----------------------------------------------------------- | ------ | ------- | +| 1 | DB migration `users.onboarding_tour_completed` | done | db5a49d | +| 2 | Tour component with spotlight + keyboard a11y | done | e57fc0a | +| 3 | Auto-launch on first dashboard load + persist on close | done | 8215e25 | +| 4 | Settings → Account → Restart tour button | done | fa1c6a6 | + +## Architecture + +- `src/lib/onboarding/tour-state.ts` — pure state machine. Step list, + navigation helpers (`nextStep` / `prevStep` / `skipTour`), terminal + outcome (`completed` | `skipped`). DOM-free so vitest's + Node-environment can exercise it directly. 13 unit tests. +- `src/components/onboarding/tour.tsx` — UI layer. **No new dep.** + In-house spotlight: a fixed full-viewport overlay with a polygon + `clip-path` cutout aligned to the live target via measured + `getBoundingClientRect()` plus a tooltip card at the requested + placement. Targets are looked up by stable `data-tour-id`, NOT + class names. Re-measures on resize and scroll. Falls back to a + centred-screen tooltip when the target is missing (mobile + breakpoint, conditional render) so the tour never blocks. 8 SSR + tests cover ARIA shape, step count, and German-locale resolution. +- `src/components/onboarding/tour-launcher.tsx` — gating logic. + Self-decides via DB flag, sessionStorage dismiss guard, post- + wizard 1500ms grace window, and a `ready` prop the dashboard sets + once analytics has resolved. Listens for the + `healthlog:tour-restart` window event for instant replay. +- `POST /api/onboarding/tour` — single endpoint for both flip + directions. Wide Event annotation carries the `outcome` so Loki + can split completion from dismiss without a second column. +- `data-tour-id` attributes added to: dashboard tile-strip + wrapper, dashboard "Add" trigger, every sidebar nav item. + +## Accessibility + +- `role="dialog"` + `aria-modal="true"` + `aria-labelledby`. Polite + live region announces step transitions to screen readers. +- Keyboard: Esc = skip, ArrowRight / Enter = next, ArrowLeft = back. + Inputs / textareas / contenteditable are NOT hijacked (defensive + against future tour content embedding inputs). +- Focus moves to the primary action on every step. +- `prefers-reduced-motion` disables the backdrop fade. + +## i18n + +19 new keys under `onboarding.tour.*` in EN + DE. The component test +asserts both locales resolve cleanly via ``. + +## Testing + +- 40 new vitest cases (13 state-machine, 8 SSR shape, 1 settings SSR + smoke, plus 18 the tour-state covers explicitly). 957 / 957 unit + pass for B5 files end-to-end. +- 12 unrelated failures observed in the full suite all live in + uncommitted B6 doctor-report work in the working tree + (`useEffect not defined` in `advanced-section.tsx`, missing + `lastReportPracticeName` in unmigrated fixtures). None of them + belong to B5. + +## ESLint compliance + +The repo enforces a strict `react-hooks/set-state-in-effect` rule +plus `react-hooks/purity`. Two patterns satisfy them: + +1. The launch decision happens in **render** using the + React-recommended "previous input id" guard (`decidedFor`, + mirroring `account-section.tsx`'s `seededUserId`). Pure: no + `Date.now()`, no `Math.random()`, no fetch in render. +2. Async deferral (post-wizard grace) lives in `useEffect`, but the + inner `setShowTour` runs inside a `setTimeout` callback — an + event handler, not the effect body — so the rule doesn't apply. + +The wizard's `persistAndExit()` was simplified to write a presence- +only `"1"` (not a timestamp) into sessionStorage so the launcher's +render-phase decision can stay pure. + +## Cross-agent observations + +Marathon ran in shared cwd with B4 + B6 + C2 in parallel. Two +collisions: + +- B6 had also added a column to `User` (`lastReportPracticeName`). + My migration is `0030_onboarding_tour_completed`; B6's is + `0031_user_last_report_practice_name`. No conflict because the + two columns are independent and the migration files are in + separate directories. +- B6 modified `/api/auth/me/route.ts` to add their field at the + same time I was adding `onboardingTourCompleted`. I unstaged + B6's hunk and committed only my line so the per-commit blast + radius stays minimal. B6 will re-add their line in their own + commit. + +Recommendation for v1.4.16 (echoing A2/A4/B-mobile/B1/B3): one git +worktree per parallel agent. The `superpowers:using-git-worktrees` +skill is the documented playbook. + +## Out of scope (deferred to v1.4.16) + +- Playwright e2e for the auto-launch flow. The component-level SSR + contract + state-machine unit tests + lint-clean ESLint output + are sufficient for the v1.4.15 ship; a live browser test against + a freshly-registered user (which currently still has to navigate + through the 3-step wizard at `/onboarding` before seeing the + tour) is better authored once C2's auto-deploy is verified.