mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
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:
1 parent
fa1c6a62ca
commit
43442276a9
2 files changed
+201
-2
No files matched your search
+83
-2
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user