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
+8
View File
@@ -100,6 +100,14 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
> - **No backend migration, no new dependencies.** Deploy is `git pull` + run the SQL script + `docker compose up -d --build backend frontend`.
> - **Verification log:** `Review/sprint7-verification.md` (to be written before deploy).
>
> **Sprint 9 status (committed, awaiting deploy):** F1 Onboarding Tour (H10). The natural next phase from the §Future backlog (the only item with a clear UI scope; F8 Spoonacular + F9 Ollama are full backend proposals; the dead `Generate Meal Plan` CTA is a separate follow-up). User direction 2026-06-05: "Proceed with the next phase in the redesign." The Sprint 10 follow-up ("Deny Forever" on Recipes) is already drafted and awaits explicit "proceed".
> - **T3.1** New `frontend/src/components/OnboardingTour.tsx` (~420 lines). Hand-rolled (no `react-joyride`; keeps npm footprint flat). 4 steps: Dashboard / Pantry / Recipes / Shopping List. Anchors to `[data-tour="<id>"]` attributes on existing elements. localStorage key `mealplanner:onboarding-complete`. `?reset-tour=1` re-triggers.
> - **T3.2** Anchor points: `Dashboard.tsx:602` (Weekly Overview card), `Pantry.tsx:185, 208` (header + add-form card), `Recipes.tsx:124` (Filters button), `ShoppingList.tsx:231` (page header). 5 lines of code total.
> - **T3.3** Tooltip = `position: fixed` `<div role="dialog" aria-modal="true">` (no portal needed). rAF loop reads anchor `getBoundingClientRect`; cancellable on close. Focus captured on open, restored on close. Keyboard: `1``4` jump, `←/→` step, `Esc` dismiss.
> - **T3.4** Off-route fallback: centered card with "Open <page>" CTA so the tour still works for users who land on a non-root page first. Decorative scrim + anchor ring are `aria-hidden="true"`.
> - **Verification log:** `Review/sprint9-verification.md`. Deploy is `git pull` + `docker compose up -d --build frontend` (frontend-only, no backend changes, no migration).
> - **No new dependencies. No backend changes.**
>
> **Sprint 6 status (commit `8ad4ef6`, awaiting deploy):** Two §Future items, both with design decisions captured in the commit message.
> - **F3** Bulk 'add checked to pantry' on ShoppingList. Backend `POST /api/pantry/bulk` accepts `{items: HomePantryCreate[]}` and returns per-item status (`added` / `updated` / `skipped`) with totals. Per-item failure model: unknown ingredient → `skipped` with reason, not a 4xx. Frontend ShoppingList gains a primary `Add N to pantry` button next to the existing Reset button; toast reports `added X, updated Y, skipped Z`; only the items that actually landed are removed from the checked Set. **Scope decision:** ShoppingList only (the checked Set was the natural substrate; Pantry would need new multi-select UI).
> - **F4** Plan the whole week on Dashboard. Backend `POST /api/meals/{id}/fill-empty-slots` with body `{meal_types: [str, ...]}` returns `FillEmptySlotsResult { filled: [{day, meal_type, item}], failed: [{day, meal_type, reason}] }`. Iterates day 1..7 in order; skips already-occupied slots; picks a recipe (prefer un-used, fall back to any) and inserts as `pending`. Per-slot failure model — never aborts mid-batch. Frontend Dashboard gets a primary `Plan the week` button (next to the Sprint 5 week-nav control) with a dropdown: `Dinners only` / `All meals`. Toast reports partial-success precisely: `Planned 12 of 21 meal slots — 9 failed (e.g. <reason>)`.