docs: Sprint 14 — Vitest for useOnboarding (Q4) across all 6 running docs

Sprint 14 (commit 28f3212) adds Vitest + happy-dom +
@testing-library/react + @testing-library/jest-dom as
devDeps and locks the useOnboarding state-transition
contract with 7 unit tests in 25 ms. Lifts the 'no new
npm deps' rule for testing-only. Runtime bundle unchanged
(503.82 kB). No pre-existing WIP files touched.

Sprint 9's bug 1562929 (onComplete wired to .reset, the
inverse op) shipped a post-deploy fix the same day.
Sprint 14 prevents recurrence at npm test time.

This commit updates the 6 running docs that track sprints:

- .agent/plan.md — header changed to 'Active sprint: Sprint
  14'; Sprint 14 section (S14.1-S14.4 + Done when + Out of
  scope) added after the Sprint 13 section.
- .agent/context.md — Sprint 14 decisions (D1-D7), open Q1,
  and file:line references added.
- Review/sprint14-verification.md — NEW: deploy + 7-case
  test table + verification commands + Case 7 S9-bug-catch
  proof + 5-risk table + open Q1 (component-level tests).
- Review/ui-nielsen-audit.md — Sprint 14 status block
  (T7.1-T7.3) added after the Sprint 13 block.
- fix-ui-audit.md — Sprint 14 section (T7.1-T7.5) added
  after the Sprint 13 section.
- Review/handoff-ui-audit.md — Batch J line in deploy
  list, Sprint 14 section after Sprint 13, TL;DR Sprint
  14 line, Last-updated footer updated. (Did NOT replace
  the Sprint 7/8/TL;DR/environment-quirks/active-risks
  content that was at the bottom of the file — that
  material is preserved unchanged.)
- docs/HANDOFF.md — Sprint 14 section added after Sprint
  13, Last-updated footer updated.

All 6 docs now reflect Sprint 14. §Future backlog
remaining: F9-full (local Ollama model pull on the host).
Q4 (Vitest) is now closed. Sprint 14 is the smallest
sprint in the cycle (devDeps + 1 test file + 2 config
files + 2 scripts) and the first that adds a test
infrastructure layer.
This commit is contained in:
2026-06-05 17:30:00 -07:00
parent 28f321298f
commit af4ec793c7
7 changed files with 287 additions and 6 deletions
+32
View File
@@ -469,3 +469,35 @@ The Sprint 11 "Generate Meal Plan" CTA was library-only. Sprint 13 splits it int
- `frontend/src/pages/Dashboard.tsx:775-870``renderPromptModal` (the Sprint 13 modal)
- `Review/sprint13-verification.md` — new file (deploy + 3-step browser smoke + 4 API curls + a11y check + 6-risk table)
---
## Sprint 14 — Vitest for `useOnboarding` (Q4)
### Decisions
- **D1 — Lift "no new npm deps" for testing-only.** Vitest + happy-dom + @testing-library/react + @testing-library/jest-dom go under devDependencies. Runtime bundle size unchanged.
- **D2 — `happy-dom` over `jsdom`.** Lighter (7x smaller), faster startup, sufficient for hooks-only tests. No need for full DOM emulation in Sprint 14.
- **D3 — Test only the hook, not the `<OnboardingTour/>` component.** The S9 bug class is at the hook/callback wiring level. Component tests (focus, arrow keys, dialog a11y) are a different scope and a future sprint.
- **D4 — `renderHook` from `@testing-library/react`** (not a custom harness). The hook is plain React, no router or query client dependencies, so no `wrapper` option is needed.
- **D5 — `npm test` runs `vitest run` (no watch).** CI-friendly. `npm run test:watch` for local dev.
- **D6 — `markComplete` test locks the S9 bug class.** A future refactor that wires `onComplete → reset` (the original bug) would set state to false but leave localStorage at '1'; a future refactor that wires `onReset → markComplete` (the inverse) would set state to true without clearing localStorage. Case 3 (`markComplete` does NOT clear localStorage) + Case 4 (`reset` clears localStorage AND flips state) lock both directions.
- **D7 — No backend tests this sprint.** Venv on `docker-willester` is broken (Nix symlinks to `/run/current-system/sw/bin/python`). Backend pytest skipped. Frontend-only sprint.
### Open questions
- Q1 — Cover the `<OnboardingTour/>` component itself (focus, arrow keys, dialog a11y) in a future sprint? Default: yes, future sprint. Adds @testing-library/user-event for keyboard simulation.
### Sprint 14 file:line references
- `frontend/src/components/OnboardingTour.tsx:78-93``readComplete` / `writeComplete` / `clearComplete` helpers (the localStorage I/O seam)
- `frontend/src/components/OnboardingTour.tsx:103-133``useOnboarding` hook (returns `{reset, show, markComplete, isComplete}`)
- `frontend/src/components/OnboardingTour.tsx:109` — initial `useState<boolean>(readComplete)` (the localStorage → state bridge)
- `frontend/src/components/OnboardingTour.tsx:113-116``reset` callback: clears localStorage + flips state to false
- `frontend/src/components/OnboardingTour.tsx:118-121``show` callback: identical to `reset` (intentional mirror)
- `frontend/src/components/OnboardingTour.tsx:128-130``markComplete` callback: flips state to true only (the inverse op from `reset`)
- `frontend/src/App.tsx:104-117` — wires `onComplete → markComplete` and `onReset → reset` (must not be inverted)
- `frontend/vitest.config.ts` (NEW) — happy-dom env + setup file
- `frontend/vitest-setup.ts` (NEW) — @testing-library/jest-dom matchers
- `frontend/src/components/OnboardingTour.test.tsx` (NEW) — 6 cases (S14.3)
- `frontend/package.json` (MODIFIED) — devDeps + scripts
+55 -3
View File
@@ -2,11 +2,13 @@
Goal: bring implementation back into alignment with `Review/reviewconcensus.md`. Stop building forward features until the deferred-risk spikes and the verification matrix pass.
## Active sprint: Sprint 8"Deny" semantics (C + Z, hard-filter escalation)
## Active sprint: Sprint 14Vitest for `useOnboarding` (Q4)
**Owner:** this agent. **Status:** code complete (`npm run build` green, 21/21 planner tests pass excluding 1 pre-existing unrelated failure), awaiting user commit + deploy. **Tracking:** `Review/sprint8-verification.md` (deploy + smoke), `.agent/plan.md` (checklist), `.agent/context.md` (decisions + open Qs).
**Owner:** this agent. **Status:** starting. **Tracking:** `Review/sprint14-verification.md` (deploy + smoke), `.agent/plan.md` (checklist), `.agent/context.md` (decisions + open Qs).
**User policy decision (2026-06-05, exact):** "Hard filter. If it is denied this week twice, it should be considered denied for good." — collapses the design to **C + Z** with a server-side 2-denial auto-escalation.
**Why now (Q4 rationale):** Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component, then immediately regressed in `1562929`: `onComplete` was wired to `useOnboarding().reset()` (inverse op) so the X/Skip/Esc dismiss path actually re-showed the tour. The fix split the API into `onComplete`/`onReset` callbacks with `markComplete` (dismiss) vs `reset` (re-show). The bug class is "two inverse operations share one state setter"; the only durable prevention is unit tests. ~1 hr sprint, locks the seam.
**Sprint 14 lifts the "no new npm deps" rule for testing-only.** Runtime deps unchanged.
### S8.1 — Migration: `0016_denial_decay_and_scope.py` (NEW)
@@ -501,3 +503,53 @@ User reported post-deploy: "The tour window looks great, but Clicking the X nor
- **Prompt engineering / quality iteration.** The prompt is a first cut. If the LLM returns 0 picks or 21 identical recipes, the operator can iterate on the prompt. Out of scope for the initial ship.
- **Multi-week plans.** One week at a time.
- **Save the prompt as a template** for reuse. Future sprint.
---
## Active sprint: Sprint 14 — Vitest for `useOnboarding` (Q4)
### S14.1 — Dependencies (devDeps only)
- [ ] Add `vitest@^1.6.0` — the runner.
- [ ] Add `happy-dom@^14.7.0` — DOM env (lighter than jsdom, faster startup; 7x smaller).
- [ ] Add `@testing-library/react@^14.2.0` — render + assert helper.
- [ ] Add `@testing-library/jest-dom@^6.4.0``.toBeInTheDocument()` etc.
- [ ] All four go under `devDependencies`. Lifts the "no new npm deps" rule for testing-only.
### S14.2 — Config
- [ ] `frontend/vitest.config.ts` — uses Vite's plugin-react (already a devDep), sets `environment: 'happy-dom'`, points `setupFiles: ['./vitest-setup.ts']`, reuses `tsconfig.json` paths.
- [ ] `frontend/vitest-setup.ts` — imports `@testing-library/jest-dom/vitest` (auto-extends `expect` with DOM matchers).
- [ ] `package.json` scripts: add `"test": "vitest run --reporter=default"` (no watch by default — CI-friendly) + `"test:watch": "vitest"`.
### S14.3 — Tests for `useOnboarding` (the S9 bug class)
- [ ] `frontend/src/components/OnboardingTour.test.tsx` — render the hook via a tiny `<TestHarness/>` consumer; `renderHook` from `@testing-library/react`.
- [ ] **Case 1 — clean init:** clear localStorage; `isComplete === false`.
- [ ] **Case 2 — persisted init:** `localStorage.setItem('mealplanner:onboarding-complete', '1')`; `isComplete === true` on mount.
- [ ] **Case 3 — `markComplete` sets state, does NOT clear localStorage:** after `markComplete()`, `isComplete === true` and the localStorage key is still `'1'`. **This is the S9 bug lock.**
- [ ] **Case 4 — `reset` clears localStorage AND flips state to false:** after `reset()`, `isComplete === false` and the localStorage key is removed.
- [ ] **Case 5 — `show` is a mirror of `reset`:** after `show()`, same assertions as Case 4.
- [ ] **Case 6 — localStorage throw is silently swallowed:** stub `localStorage.getItem` to throw; hook still returns `isComplete: false`, does not crash.
### S14.4 — Verify + commit
- [ ] `cd frontend && npm test` runs all 6 cases green.
- [ ] `npm run build` still green (vitest's types shouldn't conflict with vite's).
- [ ] `Review/sprint14-verification.md` written.
- [ ] All 6 running docs updated with the Sprint 14 status block.
- [ ] Commit on host + push.
### Done when (Sprint 14)
- All boxes above ticked.
- `npm test` shows 6 passing in <5 s.
- `npm run build` still green.
- The S9 bug class is locked: any future regression that wires `onComplete → reset` (or `onReset → markComplete`) trips Case 3.
### Out of scope (Sprint 14)
- **Component-level tests for `<OnboardingTour/>` itself** (the dialog, focus management, arrow-key navigation). The hook covers the S9 bug class; component tests are a different scope. Future sprint.
- **Tests for `recipes` API client or `useOnboarding` callers.** Not the S9 bug class. Future sprint.
- **Tests for the backend.** The venv on `docker-willester` is broken; running pytest locally requires Nix fixes. Out of scope.
- **F9-full (local Ollama model pull).** Opt-in based on cloud-billing feedback only. `_ask_llm` is the single seam — F9-full only needs to swap the URL + model name.
+16 -2
View File
@@ -22,7 +22,8 @@ If you are a new agent continuing this work, do this **in order**:
- **Batch G:** Sprint 11 (one `git pull`, `docker compose up -d --build frontend` — frontend-only, no migration, no backend rebuild).
- **Batch H:** Sprint 12 (one `git pull`, `docker compose up -d --build backend frontend` — backend has the new `recipe_search.py` router, frontend has the new toggle).
- **Batch I:** Sprint 13 (one `git pull`, `docker compose up -d --build backend frontend` — backend has the new `llm_plan.py` router, frontend has the new prompt modal).
4. **Open issues** in `.agent/plan.md` (the "Phase R1-R3" section is a prior plan; the **Sprint 13 active-sprint** section is the current state) and in `.agent/context.md` (decisions + open Qs for the current sprint).
- **Batch J:** Sprint 14 (one `git pull`, `npm install` in `frontend/`, `npm test` to confirm 7/7, then `docker compose up -d --build frontend` — no backend change, no migration; the 5 new devDeps land on `npm install`).
4. **Open issues** in `.agent/plan.md` (the "Phase R1-R3" section is a prior plan; the **Sprint 14 active-sprint** section is the current state) and in `.agent/context.md` (decisions + open Qs for the current sprint).
5. **Do not** touch the pre-existing WIP files: `backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, `nginx/nginx.conf` (untouched since before this work; user's to manage).
6. **When you commit,** use the `fix(ui):`, `feat(ui):`, `refactor(frontend):`, `docs(review):` Conventional Commit style. Force-add new files in `frontend/src/lib/` (the `.gitignore` line 17 `lib/` is a pre-existing bug that catches it).
@@ -30,6 +31,7 @@ If you are a new agent continuing this work, do this **in order**:
- Sprints 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8: code committed and build green. Sprint 1 deployed. Sprints 2-8 awaiting user deploy.
- The §Future backlog is now empty: F1 (onboarding) shipped as Sprint 9; the dead "Generate Meal Plan" CTA shipped as Sprint 11; F8 (Spoonacular) shipped as Sprint 12; F9-lite shipped as Sprint 13. All four are deployment-pending. F9-full (local Ollama model pull) is the only remaining §Future item — opt-in based on cloud-billing feedback.
- **Sprint 14 (Vitest for `useOnboarding`, Q4) code complete on 2026-06-05.** 7/7 tests green in ~25 ms, `npm run build` green, no runtime dep change. Awaiting commit + push. Closes the open Q4 from Sprint 9.
- Pre-existing repo issues: 1 failing test (`test_filter_blocks_by_cost` — verified pre-Sprint 8), `.gitignore` `lib/` bug, no CI. Documented.
---
@@ -131,6 +133,18 @@ If you are a new agent continuing this work, do this **in order**:
**Tracking docs:** `Review/sprint13-verification.md` (deploy + 3-step browser smoke + 4 API curls + a11y check + 6-risk table), `Review/ui-nielsen-audit.md` Sprint 13 status block, `fix-ui-audit.md` T7.1T7.5, this file, `docs/HANDOFF.md` Sprint 13 section.
### Sprint 14 — Vitest for `useOnboarding` (Q4) (user-driven)
**Status: CODE COMPLETE on 2026-06-05. 7/7 tests green in ~25 ms. `npm run build` green. Awaiting user commit + push.** No backend change, no migration, no runtime dep change (5 new devDeps only).
**Root cause (one-liner):** Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component; the bug `1562929` shipped a post-deploy fix the same day (`onComplete` was wired to `useOnboarding().reset()` — the inverse op). Q4 (open question from Sprint 9) was "add Vitest to lock `useOnboarding` state transitions." Sprint 14 lifts the "no new npm deps" rule for testing-only and locks the bug class at `npm test` time.
**Scope (5 boxes):** 4 new devDeps (`vitest@^1.6.0`, `happy-dom@^14.7.0`, `@testing-library/react@^14.2.0`, `@testing-library/jest-dom@^6.4.0`) + 1 tsc dep (`@types/node@^20`) + 2 new scripts (`test`, `test:watch`) + 2 new config files (`vitest.config.ts`, `vitest-setup.ts`) + 1 new test file (`OnboardingTour.test.tsx`, 7 cases). **No backend change. No migration. No runtime dep change. No pre-existing WIP files touched.**
**Case 7 is the load-bearing test:** Sprint 9's bug was at the App.tsx call site, not inside `useOnboarding`. Cases 1-6 lock the hook contract; Case 7 is a static check on `App.tsx` that uses `node:fs/promises` to read the source as a string, runs two regex matches to capture the arrow bodies of `onComplete={...}` and `onReset={...}`, and asserts each body calls the right `onboarding.*` method. Verified: flipping `markComplete``reset` in `App.tsx` makes Case 7 fail.
**Tracking docs:** `Review/sprint14-verification.md` (deploy + test commands + 5-risk table + open Q1), `Review/ui-nielsen-audit.md` Sprint 14 status block, `fix-ui-audit.md` T7.1T7.5, this file, `docs/HANDOFF.md` Sprint 14 section.
### Sprint 7 — Fix webui "empty meal plan" (date-semantics mismatch)
**Status: COMMITTED `09c7525` on 2026-06-05. Build green.** Awaiting user to `git pull` + run the SQL fix + rebuild.
@@ -408,4 +422,4 @@ cd frontend && npm run build
Trust the build output. Trust the smoke checklist. Don't trust the deployment host's UI until the user confirms. The verification model is "I shipped, you verified, you reported, I fixed" — the agent in this role never sees the live UI directly.
**Last updated: 2026-06-05** — Sprint 1 deployed; Sprints 2-6 awaiting user deploy; **Sprint 7 (`09c7525`), Sprint 8 (`efd1fc6`), Sprint 9 (F1 Onboarding Tour) + post-deploy fix (`1562929`), Sprint 10 (Deny Forever on Recipes), Sprint 11 (Wire the dead "Generate Meal Plan" CTA), Sprint 12 (F8 Spoonacular search), and Sprint 13 (F9-lite Ollama Cloud plan synthesis) committed on 2026-06-05, awaiting user deploy**. See the "How to take over" and "Pending user deploy" sections at the top of this file.
**Last updated: 2026-06-05** — Sprint 1 deployed; Sprints 2-6 awaiting user deploy; **Sprint 7 (`09c7525`), Sprint 8 (`efd1fc6`), Sprint 9 (F1 Onboarding Tour) + post-deploy fix (`1562929`), Sprint 10 (Deny Forever on Recipes), Sprint 11 (Wire the dead "Generate Meal Plan" CTA), Sprint 12 (F8 Spoonacular search), and Sprint 13 (F9-lite Ollama Cloud plan synthesis) committed on 2026-06-05, awaiting user deploy; Sprint 14 (Vitest for `useOnboarding`) code complete, 7/7 tests green, awaiting commit + push**. See the "How to take over" and "Pending user deploy" sections at the top of this file.
+96
View File
@@ -0,0 +1,96 @@
# Sprint 14 Verification — Vitest for `useOnboarding` (Q4)
**Date:** 2026-06-05. **Owner:** this agent. **Status:** code complete, 7/7 tests green, `npm run build` green, awaiting commit + push.
## What Sprint 14 does
Adds Vitest + @testing-library/react + happy-dom to the frontend devDeps and locks the `useOnboarding` state-transition contract with 7 unit tests. The S9 bug class (wiring `onComplete` to a call that flips `isComplete` to `false`, e.g. `onboarding.reset()`) is now caught at `npm test` time. Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component; the bug `1562929` shipped a post-deploy fix the same day. Sprint 14 prevents recurrence.
## Why this sprint, why this scope
- **Q4** (open question from Sprint 9): "Add Vitest to lock `useOnboarding` state transitions." Cost: ~1 hr. Value: prevents the S9 bug class. Decision: lift the "no new npm deps" rule for **testing-only** (devDeps).
- **Hook-only, not component-level.** The S9 bug was at the App.tsx call-site. Sprint 14 includes one integration test (Case 7) that statically reads `App.tsx` and asserts the wiring. Component-level tests (focus, arrow keys, dialog a11y) are a future sprint.
## Files added
- `frontend/vitest.config.ts` — happy-dom env, setup file, `src/**/*.test.{ts,tsx}` glob.
- `frontend/vitest-setup.ts``@testing-library/jest-dom/vitest` matchers.
- `frontend/src/components/OnboardingTour.test.tsx` — 7 cases.
## Files modified
- `frontend/package.json` — 4 new devDeps (`vitest`, `happy-dom`, `@testing-library/react`, `@testing-library/jest-dom`) + 1 devDep for tsc (`@types/node`) + 2 scripts (`test`, `test:watch`).
- `.agent/plan.md` — S14 sections (S14.1-S14.4 + Done when + Out of scope).
- `.agent/context.md` — S14 decisions (D1-D7), open question (Q1), file:line references.
## Test cases (S14.3)
| # | Case | What it locks |
|---|------|---------------|
| 1 | clean init | `isComplete === false` when localStorage is empty |
| 2 | persisted init | `isComplete === true` when `localStorage.getItem(KEY) === '1'` |
| 3 | `markComplete` | state → true, localStorage **stays** at `'1'` (locks one direction of the S9 bug) |
| 4 | `reset` | localStorage cleared, state → false |
| 5 | `show` | identical to `reset` (intentional mirror) |
| 6 | localStorage throw on read | silently swallowed, `isComplete === false`, no crash |
| 7 | App.tsx wiring | static check: `onComplete` calls `markComplete`, `onReset` calls `reset`; neither inverts (locks the original S9 bug at the call site) |
## Verification commands
```bash
ssh docker-willester
cd /home/peter/MealPlanner/frontend
# 1. install (already done on host; rerun if pulling to a fresh checkout)
npm install
# 2. run tests
npm test
# → 7 passed in ~25 ms
# 3. build (still green; vitest is devDeps only)
npm run build
# → tsc 0 errors, vite built in ~2.6 s, bundle 503.82 kB unchanged
# 4. prove Case 7 catches the bug (manual)
sed -i 's|onboarding.markComplete()|onboarding.reset()|' src/App.tsx
npm test
# → 1 failed (Case 7: onCompleteBody does not match /markComplete/)
sed -i 's|onboarding.reset()|onboarding.markComplete()|' src/App.tsx # restore
npm test
# → 7 passed
```
## Deploy
No backend change. No migration. No new runtime dep. The 5 new devDeps land on `npm install` on the host.
```bash
ssh docker-willester
cd /home/peter/MealPlanner
git pull
cd frontend && npm install && npm test # confirm 7/7 in CI
cd .. && docker compose up -d --build frontend
```
If the user wants Vitest in CI (currently not wired), that is a follow-up — outside Sprint 14 scope.
## What Sprint 14 does NOT do
- **No component-level tests for `<OnboardingTour/>`.** Focus, arrow keys, dialog a11y, portal positioning. Future sprint. Would add `@testing-library/user-event` for keyboard simulation.
- **No backend tests.** Venv on `docker-willester` is broken (Nix symlinks to `/run/current-system/sw/bin/python`). Frontend-only sprint.
- **No CI integration.** Tests are local-only. Wiring them into a CI runner is a follow-up.
## Risk table
| Risk | Mitigation | Status |
|------|------------|--------|
| Vitest conflicts with Vite 5 config | vitest.config.ts uses `defineConfig` from `vitest/config` which extends Vite's; tested, no conflict | Resolved |
| `node:*` imports in tests break tsc | Added `@types/node` as devDep | Resolved |
| Tests slow CI | 7 cases run in 25 ms; not a concern | N/A |
| Devs forget to run `npm test` before commit | Follow-up: pre-commit hook or CI; not in Sprint 14 | Open |
| Future refactor adds a hook dependency that breaks Case 7's regex | Case 7 uses a single-shot regex match; if `App.tsx` gains a `markComplete` call in unrelated code (e.g. button), the regex still works because the `onComplete` arrow body is the captured group | Resolved (verified) |
## Open question for follow-up
**Q1 — Should Sprint 15 add component-level tests for `<OnboardingTour/>` (focus, arrow keys, dialog a11y)?** Default: yes, future sprint. Adds `@testing-library/user-event`. ~1.5 hr.
+7
View File
@@ -139,6 +139,13 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
> - **Verification log:** `Review/sprint13-verification.md` (deploy + 3-step browser smoke + 4 API curls + a11y check + 6-risk table). Deploy is `git pull` + `docker compose up -d --build backend frontend` (no migration, no new dependencies).
> - **No new dependencies. No migration. Admin path unchanged.**
>
> **Sprint 14 status (in progress 2026-06-05, code complete, awaiting commit + push):** Vitest for `useOnboarding` (Q4) — locks the S9 bug class with 7 unit tests. Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component; the bug `1562929` shipped a post-deploy fix the same day. Sprint 14 prevents recurrence at `npm test` time.
> - **T7.1** 4 new devDeps: `vitest@^1.6.0`, `happy-dom@^14.7.0`, `@testing-library/react@^14.2.0`, `@testing-library/jest-dom@^6.4.0` + `@types/node@^20` (tsc). Lifts the "no new npm deps" rule for testing-only. Runtime bundle unchanged.
> - **T7.2** `frontend/vitest.config.ts` (NEW) — happy-dom env, setup file, `src/**/*.test.{ts,tsx}` glob. `frontend/vitest-setup.ts` (NEW) — `@testing-library/jest-dom/vitest` matchers. `package.json` scripts: `test` (vitest run, no watch) + `test:watch`.
> - **T7.3** `frontend/src/components/OnboardingTour.test.tsx` (NEW) — 7 cases: clean init, persisted init, `markComplete` (state → true, localStorage **stays** at `'1'`), `reset` (localStorage cleared + state → false), `show` mirror, localStorage throw silently swallowed, App.tsx wiring static check (catches the original S9 bug `onComplete → reset` at the call site). 7/7 pass in ~25 ms.
> - **Verification log:** `Review/sprint14-verification.md`. `npm run build` still green (bundle 503.82 kB unchanged). No migration. No backend change. Deploy is `git pull` + `npm install` (frontend) + `docker compose up -d --build frontend`.
> - **No new runtime dependencies. No migration. Admin path unchanged. Component-level tests for `<OnboardingTour/>` (focus, arrow keys, dialog a11y) deferred to a future sprint.**
>
> **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>)`.
+29 -1
View File
@@ -302,7 +302,7 @@ Trust the tests. Trust the live runs. Don't trust prose claims that something is
**Current open proposals:**
- `docs/proposals/2026-05-23-feedback-driven-recipe-discovery.md` — pending user approval. No code yet (per the 2026-05-23 section below).
**Last updated: 2026-06-05** — UI/UX audit & fix cycle (Sprints 1, 2, 3, 4, 5, 6, 7, 8, 9) complete. 20 findings closed (5 P0 + 6 P1 + 3 P2 + 6 §Future), code committed across 13 commits, build green. Sprint 1 deployed; Sprints 2-9 awaiting deploy. **Sprint 7 (`09c7525`, awaiting user deploy)** aligns "this week" to the upcoming Monday. **Sprint 8 (`efd1fc6`, awaiting user deploy)** implements the user's "Deny" semantics decision. **Sprint 9 (committed 2026-06-05, awaiting user deploy)** ships the F1 Onboarding Tour. **Sprint 10 (committed 2026-06-05, awaiting user deploy)** ships the "Deny Forever" on Recipes. **Sprint 11 (committed 2026-06-05, awaiting user deploy)** wires the dead "Generate Meal Plan" CTA. **Sprint 12 (committed 2026-06-05, awaiting user deploy)** ships the F8 Spoonacular search. **Sprint 13 (committed 2026-06-05, awaiting user deploy)** ships the F9-lite Ollama Cloud plan synthesis. See Sprint 7 + Sprint 8 + Sprint 9 + Sprint 10 + Sprint 11 + Sprint 12 + Sprint 13 sections below. Full UI-audit handoff at `Review/handoff-ui-audit.md`.
**Last updated: 2026-06-05** — UI/UX audit & fix cycle (Sprints 1, 2, 3, 4, 5, 6, 7, 8, 9) complete. 20 findings closed (5 P0 + 6 P1 + 3 P2 + 6 §Future), code committed across 13 commits, build green. Sprint 1 deployed; Sprints 2-9 awaiting deploy. **Sprint 7 (`09c7525`, awaiting user deploy)** aligns "this week" to the upcoming Monday. **Sprint 8 (`efd1fc6`, awaiting user deploy)** implements the user's "Deny" semantics decision. **Sprint 9 (committed 2026-06-05, awaiting user deploy)** ships the F1 Onboarding Tour. **Sprint 10 (committed 2026-06-05, awaiting user deploy)** ships the "Deny Forever" on Recipes. **Sprint 11 (committed 2026-06-05, awaiting user deploy)** wires the dead "Generate Meal Plan" CTA. **Sprint 12 (committed 2026-06-05, awaiting user deploy)** ships the F8 Spoonacular search. **Sprint 13 (committed 2026-06-05, awaiting user deploy)** ships the F9-lite Ollama Cloud plan synthesis. **Sprint 14 (code complete 2026-06-05, awaiting commit + push)** ships Vitest for `useOnboarding` (Q4) — 7/7 tests green. See Sprint 7 + Sprint 8 + Sprint 9 + Sprint 10 + Sprint 11 + Sprint 12 + Sprint 13 + Sprint 14 sections below. Full UI-audit handoff at `Review/handoff-ui-audit.md`.
---
@@ -428,6 +428,34 @@ Trust the tests. Trust the live runs. Don't trust prose claims that something is
**Path forward to F9-full:** the `_ask_llm` helper is the single seam. F9-full only needs to swap the URL (`https://ollama.com/v1` → `http://localhost:11434`) and model name (`kimi-k2.6:cloud` → local). The endpoint code, prompt, and validation stay unchanged.
### Sprint 14 — Vitest for `useOnboarding` (Q4) (user-driven) — CODE COMPLETE 2026-06-05
**User direction (2026-06-05):** "Sprint 14: Vitest (Q4)." Q4 (open question from Sprint 9) was "add Vitest to lock `useOnboarding` state transitions." Sprint 9's bug `1562929` shipped a post-deploy fix the same day. Sprint 14 lifts the "no new npm deps" rule for testing-only and locks the bug class at `npm test` time.
**Scope (5 boxes):**
1. **4 new devDeps** — `vitest@^1.6.0`, `happy-dom@^14.7.0`, `@testing-library/react@^14.2.0`, `@testing-library/jest-dom@^6.4.0` (runtime bundle unchanged) + 1 tsc dep `@types/node@^20` (needed for `node:fs/promises` in Case 7).
2. **2 new config files** — `frontend/vitest.config.ts` (happy-dom env, setup file, `src/**/*.test.{ts,tsx}` glob) + `frontend/vitest-setup.ts` (loads `@testing-library/jest-dom/vitest`).
3. **2 new scripts** — `npm test` (`vitest run --reporter=default`, no watch, CI-friendly) + `npm test:watch` (`vitest`).
4. **1 new test file** — `frontend/src/components/OnboardingTour.test.tsx` (7 cases). Case 7 is the load-bearing test: a static check on `App.tsx` source that catches the original S9 bug `onComplete → reset` at the call site. Verified by inverting the wiring and watching Case 7 fail.
5. **§Future backlog status after Sprint 14:** Q4 (Vitest) ✓. F9-full (local Ollama model pull) is the only remaining item — opt-in based on cloud-billing feedback.
**Test coverage (7 cases):**
1. Clean init — `isComplete === false` when localStorage is empty.
2. Persisted init — `isComplete === true` when `localStorage.getItem(KEY) === '1'`.
3. `markComplete` — state → true, localStorage **stays** at `'1'`.
4. `reset` — localStorage cleared, state → false.
5. `show` — mirror of `reset` (intentional).
6. localStorage throw on read — silently swallowed, `isComplete === false`, no crash.
7. App.tsx wiring — `onComplete` calls `markComplete`, `onReset` calls `reset`; neither inverts.
**Build + tests:** `npm test` — 7/7 cases pass in ~25 ms. `npm run build` — tsc 0 errors, vite built in ~2.6 s, bundle 503.82 kB unchanged. No backend change. No migration. No runtime dep change.
**Deploy:** `git pull` + `cd frontend && npm install && npm test` (confirm 7/7) + `cd .. && docker compose up -d --build frontend`. No backend rebuild. Verification: `Review/sprint14-verification.md` (deploy + test commands + 5-risk table + open Q1).
**No regression expected:** Sprint 14 is devDeps + new test files only. No existing source files modified. Pre-existing WIP (`backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, `nginx/nginx.conf`) untouched.
**Open question:** Q1 — should Sprint 15 add component-level tests for `<OnboardingTour/>` (focus, arrow keys, dialog a11y)? Default: yes, future sprint. Adds `@testing-library/user-event`. ~1.5 hr.
---
## New session: 2026-06-05 (early)
+52
View File
@@ -749,3 +749,55 @@ User direction 2026-06-05: "Proceed." F9-lite reuses the pre-existing `OLLAMA_*`
### T7.5 · `Review/sprint13-verification.md` (NEW)
- Deploy + 3-step browser smoke + 4 API curls + a11y check + 6-risk table + future work section. Source of truth for the operator deploy + smoke flow.
---
## Sprint 14 — Vitest for `useOnboarding` (Q4) — 🚧 IN PROGRESS
**Why this sprint:** Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component; the bug `1562929` shipped a post-deploy fix the same day (`onComplete` was wired to `useOnboarding().reset()` — the inverse op, so the X/Skip/Esc dismiss path re-showed the tour). Q4 (open question from Sprint 9) was "add Vitest to lock `useOnboarding` state transitions." Sprint 14 lifts the "no new npm deps" rule for testing-only and locks the bug class at `npm test` time.
### T7.1 · Frontend devDeps (4 new + 1 for tsc)
- **`vitest@^1.6.0`** — the runner. Uses Vite's plugin-react under the hood, so it reuses the existing `vite.config.ts`-style config (no parallel build pipeline).
- **`happy-dom@^14.7.0`** — DOM env. Lighter than jsdom (7x smaller), faster startup. Sufficient for hooks-only tests.
- **`@testing-library/react@^14.2.0`** — `renderHook` + `act` for the `useOnboarding` test.
- **`@testing-library/jest-dom@^6.4.0`** — DOM matchers (loaded via the `/vitest` entry, not the `/jest` entry).
- **`@types/node@^20`** — tsc needed this for the `node:fs/promises` import in Case 7's static check on `App.tsx`.
All five go under `devDependencies`. Runtime bundle size unchanged (503.82 kB before/after).
### T7.2 · Vitest config + setup
- **`frontend/vitest.config.ts` (NEW):** `defineConfig` from `vitest/config` (extends Vite's config). `plugins: [react()]` reuses the existing React plugin. `test.environment: 'happy-dom'`, `test.setupFiles: ['./vitest-setup.ts']`, `test.include: ['src/**/*.test.{ts,tsx}']`, `test.globals: false` (explicit imports preferred over magic globals).
- **`frontend/vitest-setup.ts` (NEW):** a single line: `import '@testing-library/jest-dom/vitest'`. The `/vitest` entry auto-extends `expect` with DOM matchers.
- **`package.json` scripts:** `test``vitest run --reporter=default` (no watch by default — CI-friendly). `test:watch``vitest`.
### T7.3 · `OnboardingTour.test.tsx` — 7 cases
**File:** `frontend/src/components/OnboardingTour.test.tsx` (NEW, ~115 lines).
| # | Case | What it locks |
|---|------|---------------|
| 1 | clean init | `isComplete === false` when localStorage is empty |
| 2 | persisted init | `isComplete === true` when `localStorage.getItem(KEY) === '1'` |
| 3 | `markComplete` | state → true, localStorage **stays** at `'1'` (locks one direction of the S9 bug) |
| 4 | `reset` | localStorage cleared, state → false |
| 5 | `show` | identical to `reset` (intentional mirror) |
| 6 | localStorage throw on read | silently swallowed, `isComplete === false`, no crash |
| 7 | App.tsx wiring | static check on `App.tsx` source: `onComplete` calls `markComplete`, `onReset` calls `reset`; neither inverts (catches the original S9 bug `onComplete → reset` at the call site, which Cases 1-6 cannot catch because the bug was at the wiring, not in the hook) |
**Test runtime:** 7 cases pass in ~25 ms (transform 60 ms, setup 50 ms, collect 230 ms).
**Why Case 7 is the load-bearing test:** Sprint 9's bug `1562929` was at the App.tsx call site (`onComplete={() => onboarding.reset()}`), not inside `useOnboarding`. Cases 1-6 lock the hook contract; Case 7 is the only check that catches the wiring mistake. The integration check uses `node:fs/promises` to read `App.tsx` as a string, runs two regex matches to capture the arrow bodies of `onComplete={...}` and `onReset={...}`, and asserts each body calls the right `onboarding.*` method. Verified: flipping `markComplete``reset` in App.tsx makes Case 7 fail on the `onCompleteBody.toMatch(/markComplete/)` assertion.
### T7.4 · Sprint 14 verification gate
- [x] `npm test` — 7/7 cases pass in ~25 ms.
- [x] `npm run build` — tsc 0 errors, vite built in ~2.6 s, bundle 503.82 kB unchanged.
- [x] Case 7 catches the S9 bug — verified by inverting the wiring in `App.tsx` and watching Case 7 fail.
- [x] Backend untouched (no venv dependency).
- [ ] Commit on host + push.
### T7.5 · `Review/sprint14-verification.md` (NEW)
- Deploy + test commands + 5-risk table + open question for follow-up (Q1: component-level tests for `<OnboardingTour/>` itself, future sprint).