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) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 21:33:23 +02:00
1 parent fa1c6a62ca
commit 43442276a9
2 files changed
+201 -2

No files matched your search

+83 -2
View File
@@ -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
`<I18nProvider initialLocale="de">`.
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
+118
View File
@@ -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 `<I18nProvider initialLocale="de">`.
## 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.