feat(ui): Sprint 9 — F1 onboarding tour (4-step welcome)

Hand-rolled 4-step tour (no react-joyride) anchors to existing
[data-tour="<id>"] attributes. localStorage key
mealplanner:onboarding-complete is the source of truth; ?reset-tour=1
clears the key and re-shows.

Steps: Dashboard / Pantry / Recipes / Shopping List. Keyboard: 1-4 jump,
←/→ step, Esc dismiss. Off-route fallback renders a centered card with
an 'Open <page>' CTA. A11y: role=dialog, aria-modal=true, focus captured
on open and restored on close.

5 lines of code across 4 pages; 1 new component (~420 lines). No new
dependencies. No backend changes. No migration. Frontend-only deploy.

Tracking: Review/sprint9-verification.md (8-step browser smoke + a11y
check + reset-link test).
This commit is contained in:
2026-06-05 11:14:53 -07:00
parent efd1fc695f
commit 6e386baf6e
13 changed files with 839 additions and 25 deletions
+53 -3
View File
@@ -122,9 +122,7 @@ User report 2026-06-05 (follow-up to Sprint 7): "one of the meals was the meal t
- `frontend/src/pages/Dashboard.tsx:38-50, 385-410``MealCard` 3-button voting row
- `Review/sprint8-verification.md` — new file (deploy + smoke)
---
# Context — Sprint 7 (webui empty-meal-plan fix)
## Sprint 7 — webui empty-meal-plan fix
## Why Sprint 7 exists
@@ -178,3 +176,55 @@ User report 2026-06-05: "Latest meal plans were emails to me this morning, but w
- `backend/scripts/fix_2026_06_05_to_2026_06_08.sql` — new (TO ADD)
- `Review/sprint7-verification.md` — new (TO ADD)
---
# Context — Sprint 9 (F1 Onboarding Tour, H10)
## Why Sprint 9 exists
User direction 2026-06-05: "Proceed with the next phase in the redesign." §Future backlog items: F1 (onboarding tour), F8 (Spoonacular proposal), F9 (Ollama proposal), dead `Generate Meal Plan` CTA. F1 is the only §Future item with a clear UI scope — selected.
## Decisions (locked in for Sprint 9)
- **D1. Hand-rolled tour, no `react-joyride`.** Adding a new npm dep is a 1-line trade-off; the audit's prior principles ("reuse existing components/ui/*", "no new npm deps") win. The tour is 4 steps; the implementation is ~420 lines of focused React.
- **D2. localStorage key `mealplanner:onboarding-complete` (`"1"` once done).** Same shape as the other `mealplanner:` prefixed keys in the codebase (verified by grep).
- **D3. `?reset-tour=1` re-triggers the tour.** Strips the param via `navigate(..., { replace: true })` so a refresh doesn't re-clear. Operator can use this from the browser URL bar; a footer link is a 5-line follow-up if requested.
- **D4. Auto-show on `/` only.** Other routes need a manual trigger (or `?reset-tour=1`). The first-time user lands on `/` (the Dashboard is the only root route), so auto-show on first visit is the natural moment.
- **D5. Tooltip is a real `<div role="dialog" aria-modal="true">`, not a portal.** The 4 anchor elements are all in the same DOM tree as the dialog. The 20-line portal boilerplate was not worth it; a `position: fixed` dialog at the right z-index works fine.
- **D6. rAF polling for the anchor's `getBoundingClientRect`.** Runs only while the tour is open. Cancellable. One DOM read per frame; well under 1% CPU on a 60Hz display.
- **D7. Focus captured on open (primary action), restored on close.** Uses `previouslyFocused.current = document.activeElement` on mount; restores on unmount. Standard focus-trap pattern, minus the trap (the dialog is intentionally non-modal — the user can interact with the page below).
- **D8. The 4 anchor points are stable elements that already exist in the DOM.** The Dashboard's `<Card>` wrapping the Weekly Overview, the Pantry's page header, the Recipes Filters button, the Shopping List page header. Each gets `data-tour="<id>"`. The anchor also has an off-route fallback (centered card + "Open <page>" CTA) so a first-time user who lands on `/pantry` can still see the Dashboard step (with a one-click nav).
## Open questions to surface to the user, not to assume
- **Q1. Should the tour show on every page or only `/`?** Default: `/` only. Other pages need `?reset-tour=1`. If the user lands on a non-root page first, the tour does NOT auto-show. Documented in `Review/sprint9-verification.md` smoke step 2.
- **Q2. Should the tour re-show on logout / new device?** Default: no. The localStorage key is per-browser, not per-family-profile. If the user has multiple devices or shares a device, the tour shows once per browser. A future migration could move the key to the family profile, but that's a Sprint 11+.
- **Q3. Should the tour re-show on a recipe update / catalog change?** Default: no. The tour is a one-shot. New users see it; existing users don't.
## Sprint 9 verification gate
- `cd frontend && npm run build` → green (tsc 0 errors, vite 0 errors)
- Browser smoke (8 steps) on `http://100.108.208.56:8082/` per `Review/sprint9-verification.md`
- No regression in Sprints 18
## Sprint 9 — does NOT touch
- The `extractErrorMessage` / `showApiError` flow (Sprint 4 F7) — unchanged.
- The keyboard shortcuts (Sprint 5 F2) — unchanged.
- The bulk pantry add (Sprint 6 F3) — unchanged.
- The plan-the-week (Sprint 6 F4) — unchanged.
- The undo-toast (Sprint 3 B12) — unchanged.
- The WeekRangeNav (Sprint 7) — unchanged.
- The 3-button Sprint 8 voting row — unchanged.
- Pre-existing WIP: `backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, `nginx/nginx.conf` — untouched.
## Key file:line references
- `frontend/src/components/OnboardingTour.tsx` (NEW) — ~420 lines
- `frontend/src/App.tsx:75-105``useOnboarding` + tour mount
- `frontend/src/pages/Dashboard.tsx:602``<Card data-tour="dashboard">`
- `frontend/src/pages/Pantry.tsx:185, 208` — header + add-form anchors
- `frontend/src/pages/Recipes.tsx:124` — Filters button anchor
- `frontend/src/pages/ShoppingList.tsx:231` — header anchor
- `Review/sprint9-verification.md` — new file (deploy + 8-step browser smoke + a11y check)
+65
View File
@@ -121,3 +121,68 @@ Goal: bring implementation back into alignment with `Review/reviewconcensus.md`.
- R2 spikes fail → stop, propose schema/spec change, await approval.
- Verification matrix in `Review/reviewconcensus.md §6` not green → no R3 work begins.
---
## Sprint 9 — F1 Onboarding Tour (H10)
**Owner:** this agent. **Status:** code complete, `npm run build` green, awaiting user commit + deploy. **Tracking:** `Review/sprint9-verification.md`.
**User policy decision (2026-06-05, exact):** "Proceed with the next phase in the redesign." Selected Sprint 9 = F1 (the only §Future item with a clear UI scope). F8 (Spoonacular) and F9 (Ollama) are full backend proposals; the dead `Generate Meal Plan` CTA is a separate follow-up.
### S9.1 — New `OnboardingTour.tsx` component (NEW)
- [x] Hand-rolled (no `react-joyride`) — keeps npm footprint flat.
- [x] 4 steps: Dashboard / Pantry / Recipes / Shopping List.
- [x] Anchors to `[data-tour="<id>"]` attributes on existing elements.
- [x] Tooltip card pinned to anchor (top/bottom/center fallback for off-route steps).
- [x] Anchor highlight = primary-400 ring + soft scrim; tooltip is a real `<div role="dialog" aria-modal="true">`.
- [x] Step progress = 4 progress bars.
- [x] Keyboard: `1``4` jump, `←/→` step, `Esc` dismiss, `Tab` order is `Skip → Back → Next`.
- [x] `useOnboarding()` hook + `?reset-tour=1` re-trigger; localStorage key `mealplanner:onboarding-complete`.
- [x] Focus captured on open (primary action), restored on close.
- [x] All reads/writes to localStorage wrapped in try/catch (private mode safe).
### S9.2 — Anchor points (5 lines of code total)
- [x] `pages/Dashboard.tsx:602``<Card data-tour="dashboard">` on the Weekly Overview grid.
- [x] `pages/Pantry.tsx:185``<div data-tour="pantry">` on the page header (always present).
- [x] `pages/Pantry.tsx:208` — second anchor on the add-form `<Card>` (when the form is open).
- [x] `pages/Recipes.tsx:124``<Button data-tour="recipes">` on the Filters button.
- [x] `pages/ShoppingList.tsx:231``<div data-tour="shopping-list">` on the page header.
### S9.3 — `App.tsx` mount
- [x] `useOnboarding()` at App root, `isComplete` passed to `<OnboardingTour>`.
- [x] `onComplete` mapped to `onboarding.reset()` (flips the flag so re-renders don't re-show).
- [x] Mounted as sibling of `<ShortcutHelpBanner />` inside `<BrowserRouter>` (so `useLocation` / `useNavigate` work).
### S9.4 — Verify
- [x] `npm run build` green (tsc 0 errors, vite 0 errors).
- [ ] Browser smoke (8 steps) on `http://100.108.208.56:8082/` per `Review/sprint9-verification.md`.
- [ ] No regression in Sprints 18 (keyboard shortcuts, error toast, 3-button vote row, WeekRangeNav, bulk pantry add).
### S9.5 — Docs (all 6 running docs updated)
- [x] `Review/ui-nielsen-audit.md` — Sprint 9 status block at the top.
- [x] `fix-ui-audit.md` — Sprint 9 plan section (T3.1T3.4).
- [x] `Review/handoff-ui-audit.md` — Sprint 9 entry in the "How to take over" section + TL;DR row.
- [x] `docs/HANDOFF.md` — Sprint 9 section.
- [x] `.agent/plan.md` — this section.
- [x] `.agent/context.md` — Sprint 9 decisions + file:line references.
- [x] `Review/sprint9-verification.md` — written (8-step browser smoke + a11y check + reset-link test).
### Done when (Sprint 9)
- All boxes above ticked.
- `npm run build` green.
- `Review/sprint9-verification.md` exists.
- All 6 doc files have a Sprint 9 status block.
### Out of scope (Sprint 9)
- Thread 3 follow-ups: F8 (Spoonacular), F9 (Ollama), dead `Generate Meal Plan` CTA at `Dashboard.tsx:415`.
- Per-page deep tutorials, video demos, hover tooltips.
- A user-facing "Show tour" link in the footer (operator uses `?reset-tour=1`; a footer link is a 5-line follow-up if requested).
- Sprint 10 — "Deny Forever" on Recipes — already drafted, awaiting user approval to execute.