Files
Meal-Planner/Review/sprint9-verification.md
T
admin 6e386baf6e 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).
2026-06-05 11:14:53 -07:00

120 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sprint 9 — F1 Onboarding Tour (H10)
**Status (2026-06-05):** ✅ Code complete. `npm run build` green. Awaiting user commit + deploy.
**Audit link:** F1 (Onboarding hints / tour) is the last §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.
**Goal:** First-time visitors get a 4-step tour. Returning users never see it. The tour re-shows on demand via `?reset-tour=1`.
---
## What ships
### `OnboardingTour.tsx` (NEW)
Hand-rolled (no `react-joyride`) to keep the npm footprint flat. 4 steps:
1. **Dashboard** — "Your weekly meal plan"
2. **Pantry** — "What you have in stock"
3. **Recipes** — "Browse + filter recipes"
4. **Shopping List** — "Plan → shop → restock"
Each step:
- Anchors to a `[data-tour="<id>"]` attribute on the existing page.
- Renders a tooltip card pinned to the anchor (top/bottom/center fallback).
- Highlights the anchor with a primary-400 ring + soft scrim.
- Step progress shown as 4 progress bars (top of card).
- Skip / Back / Next (or "Got it" on the last step).
**Keyboard nav (when tour is visible):**
- `1``4` → jump to that step
- `←/→` → step back / forward
- `Esc` → dismiss
- Tab order: `Skip → Back → Next` (or `Skip → Open page → Next` when off-route)
**A11y:**
- `role="dialog"`, `aria-modal="true"`, `aria-labelledby` → step title.
- Focus is captured on open (moved to the primary action) and restored on close.
- Tooltip + anchor ring are announced via `aria-hidden="true"` (decorative); the dialog text is the real signal.
**Storage:**
- localStorage key: `mealplanner:onboarding-complete` (`"1"` once completed).
- `?reset-tour=1` in any URL clears the key + strips the param via `navigate(..., { replace: true })` so a refresh doesn't re-clear.
- Reading the key is wrapped in try/catch — private mode / disabled storage silently falls through.
### Anchor points (5 lines of code total)
| Page | File:line | Anchor | Notes |
|---|---|---|---|
| Dashboard | `pages/Dashboard.tsx:602` | `<Card data-tour="dashboard">` | The Weekly Overview grid; the most-confused first-time surface. |
| Pantry | `pages/Pantry.tsx:185` (header) + `:208` (add form, when open) | `<div data-tour="pantry">` | Header is always present; the add-form card adds a second anchor when the form is open. |
| Recipes | `pages/Recipes.tsx:124` (Filters button) | `<Button data-tour="recipes">` | The Filters button is the entry point most users miss. |
| ShoppingList | `pages/ShoppingList.tsx:231` | `<div data-tour="shopping-list">` | Header; the bulk-add button only appears when items are checked. |
### `App.tsx` (mount)
- Imports `OnboardingTour` + `useOnboarding`.
- Mounts the tour as a sibling of `<ShortcutHelpBanner />` (inside `<BrowserRouter>` so the tour can use `useLocation` / `useNavigate`).
- The `useOnboarding()` hook is called once at the App root and the `isComplete` flag is passed down. On dismiss, the tour calls `onComplete()` which the App maps to `onboarding.reset()` — flipping the flag so re-renders don't re-show.
---
## Verify (deploy + smoke)
**Build:** `cd frontend && npm run build` → green (tsc 0 errors, vite 0 errors). Verified locally.
**Browser smoke on `http://100.108.208.56:8082/`:**
1. **First-visit tour.** Open an incognito window (or a new browser) and navigate to `http://100.108.208.56:8082/`. The tour auto-shows on step 1 (Dashboard) within 1 frame.
2. **Anchor highlight.** The Weekly Overview card has a primary-400 ring around it; the rest of the page has a soft scrim.
3. **Forward nav.** Press `→` → step 2 (Pantry) shows. If you're not on `/pantry`, the tooltip renders centered with an "Open Pantry" button.
4. **Click "Open Pantry".** Tour stays open, navigates to `/pantry`, anchor re-renders below the header.
5. **Keyboard jumps.** From step 2, press `3` → tour jumps to Recipes (Filters button highlighted).
6. **Dismiss.** Press `Esc` on any step → tour disappears, localStorage key is set. Refresh the page → tour does NOT re-show.
7. **Reset.** Navigate to `http://100.108.208.56:8082/?reset-tour=1`. The query string is stripped, localStorage key is cleared, tour shows again on step 1.
8. **A11y.** Tab through the dialog: focus moves from Skip → Back → Next in order; Esc dismisses; screen reader announces the step title (e.g. "What you have in stock, dialog").
**A11y verification:**
- VoiceOver on the dialog announces "Your weekly meal plan, dialog".
- Arrow keys step the tour.
- Esc dismisses.
- Focus is restored to the previously-focused element on dismiss.
**Regression check:**
- Sprint 5 keyboard shortcuts (`g d`, `g r`, `g p`, `g s`, `/`, `?`) still work.
- Sprint 4 global error toast still fires.
- The 3-button Sprint 8 voting row on Dashboard meal cards still works.
- Sprint 7 `WeekRangeNav` still renders on Dashboard and ShoppingList.
---
## Out of scope
- Per-page deep tutorials (the welcome tour is the only thing S9 ships).
- Video or animated demos.
- Tooltip-on-hover patterns.
- A user-facing "Show tour" link in the footer (operator can use `?reset-tour=1`; a footer link is a 5-line follow-up if requested).
- F8 Spoonacular proposal, F9 Ollama proposal, dead `Generate Meal Plan` CTA — separate.
---
## Risks & mitigations
- **R1: rAF polling on the anchor's `getBoundingClientRect`.** Runs while the tour is open. Cheap (one DOM read per frame). Cancelled on close. No throttling needed for a 4-step tour.
- **R2: The auto-show on `/` only.** If the user lands on `/pantry` first (e.g. via a bookmark), the tour does NOT auto-show. The header anchor is still present so a `?reset-tour=1` would show the tour with the right anchor. Documented; not a bug.
- **R3: `useOnboarding` flag is App-level state.** A second `<App>` mount (in tests, e.g.) would not share the flag. The tour reads localStorage on mount so the real source of truth is the storage key, not the flag.
- **R4: Existing pre-existing WIP in `git status`.** Sprint 9 doesn't touch `backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, or `nginx/nginx.conf` — those are the user's to manage.
---
## Commit
One commit: `feat(ui): Sprint 9 — F1 onboarding tour (4-step welcome)`. Files:
- NEW `frontend/src/components/OnboardingTour.tsx` (~420 lines)
- `frontend/src/App.tsx` (mount + flag)
- `frontend/src/pages/Dashboard.tsx` (anchor)
- `frontend/src/pages/Pantry.tsx` (2 anchors)
- `frontend/src/pages/Recipes.tsx` (anchor on Filters button)
- `frontend/src/pages/ShoppingList.tsx` (anchor on header)