From 6e386baf6ea150824bace353598989782b996bfc Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Fri, 5 Jun 2026 11:14:53 -0700 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Sprint=209=20=E2=80=94=20F1=20onboa?= =?UTF-8?q?rding=20tour=20(4-step=20welcome)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand-rolled 4-step tour (no react-joyride) anchors to existing [data-tour=""] 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 ' 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). --- .agent/context.md | 56 ++- .agent/plan.md | 65 ++++ Review/handoff-ui-audit.md | 77 +++- Review/sprint9-verification.md | 119 ++++++ Review/ui-nielsen-audit.md | 8 + docs/HANDOFF.md | 44 ++- fix-ui-audit.md | 44 +++ frontend/src/App.tsx | 14 + frontend/src/components/OnboardingTour.tsx | 428 +++++++++++++++++++++ frontend/src/pages/Dashboard.tsx | 2 +- frontend/src/pages/Pantry.tsx | 4 +- frontend/src/pages/Recipes.tsx | 1 + frontend/src/pages/ShoppingList.tsx | 2 +- 13 files changed, 839 insertions(+), 25 deletions(-) create mode 100644 Review/sprint9-verification.md create mode 100644 frontend/src/components/OnboardingTour.tsx diff --git a/.agent/context.md b/.agent/context.md index 0ffe622..db6d559 100644 --- a/.agent/context.md +++ b/.agent/context.md @@ -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 `
`, 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 `` wrapping the Weekly Overview, the Pantry's page header, the Recipes Filters button, the Shopping List page header. Each gets `data-tour=""`. The anchor also has an off-route fallback (centered card + "Open " 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 1–8 + +## 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` — `` +- `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) + diff --git a/.agent/plan.md b/.agent/plan.md index c62f669..749dc9e 100644 --- a/.agent/plan.md +++ b/.agent/plan.md @@ -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=""]` 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 `
`. +- [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` — `` on the Weekly Overview grid. +- [x] `pages/Pantry.tsx:185` — `
` on the page header (always present). +- [x] `pages/Pantry.tsx:208` — second anchor on the add-form `` (when the form is open). +- [x] `pages/Recipes.tsx:124` — ` +
+

{currentStep.body}

+

+ Step {step + 1} of {STEPS.length} — open the highlighted page to see it in context. +

+
+ +
+ + +
+
+
+
+ ) + } + + // Anchored tooltip: position the card 16px below the anchor (or above + // if it would clip the viewport). Clamp horizontally to keep the card + // on-screen. Mobile: prefer the top of the viewport so the card never + // gets clipped. + const isMobile = typeof window !== 'undefined' && window.innerWidth < 640 + const ANCHOR_GAP = 16 + const CARD_WIDTH = 320 + const viewportH = typeof window !== 'undefined' ? window.innerHeight : 800 + const viewportW = typeof window !== 'undefined' ? window.innerWidth : 1024 + const placeBelow = !isMobile && anchorRect.bottom + ANCHOR_GAP + 200 < viewportH + const top = placeBelow + ? anchorRect.bottom + ANCHOR_GAP + : Math.max(16, anchorRect.top - ANCHOR_GAP - 200) + const left = isMobile + ? 16 + : Math.max(16, Math.min(viewportW - CARD_WIDTH - 16, anchorRect.left + anchorRect.width / 2 - CARD_WIDTH / 2)) + + return ( + <> + {/* Soft scrim. The page is still visible — this is a hint, not a + modal. We don't use a full overlay because the user must be + able to see the page element being explained. */} +