Public Access
Sprint 15 (commit a3c89bf) does two things: (1) reorders
backend/app/main.py so recipe_search_api.router mounts BEFORE
the WIP recipes_api.public_router (fixes a Sprint 12 latent
bug where /api/recipes/search was shadowed by the WIP's GET
/{recipe_id} returning 422); (2) adds scripts/seed_recipes.py
which seeded 18 Spoonacular recipes into the local library
today (free-tier 50-pt cap hit; remaining 32 to seed on later
days via the same idempotent script). DB went 31 -> 49 total
recipes.
This commit updates the 6 running docs that track sprints:
- .agent/plan.md — Sprint 15 section (S15.1-S15.4 + Done
when + Out of scope) added after Sprint 14's out-of-scope.
- .agent/context.md — Sprint 15 decisions (D1-D6), open
questions (Q1-Q2), and file:line references added.
- Review/sprint15-verification.md — NEW: full 18-imported
breakdown by cuisine + free-tier math correction (50 pts/day,
not 150) + LLM test (picked_count=0, filled_count=19,
failed_count=2 for week 2026-07-06) + 6-risk table + deploy
+ 2 follow-up tickets (lower _DAILY_LIMIT, re-run script).
- Review/ui-nielsen-audit.md — Sprint 15 status block
(T8.1-T8.3) added after the Sprint 14 block. Notes the
Sprint 12 latent-bug fix as the critical change for the
upcoming Sprint 12 deploy.
- fix-ui-audit.md — Sprint 15 section (T8.1-T8.5) added after
the Sprint 14 section. T8.1 documents the main.py mount
order fix in detail. T8.5 surfaces 2 follow-up tickets.
- Review/handoff-ui-audit.md — Batch K line in the deploy
list, Sprint 15 section after Sprint 14, TL;DR Sprint 15
line, Last-updated footer updated.
- docs/HANDOFF.md — Sprint 15 section after Sprint 14, Last-
updated footer updated. Notes the corrected free-tier math
and the 2 follow-up tickets.
All 6 docs now reflect Sprint 15. The Sprint 12 latent-bug
fix is documented as a hard prerequisite for the upcoming
Sprint 12 deploy (without it, every 'Search the web' query
would 422). Re-running scripts/seed_recipes.py on a later
day will add the remaining 32 recipes (the script is
idempotent — already-imported IDs return 409 and are
skipped).
446 lines
42 KiB
Markdown
446 lines
42 KiB
Markdown
# UI/UX Audit & Fix — Agent Handoff
|
||
|
||
You are taking over an 8-sprint UI/UX audit and fix cycle. **All 8 sprints' code is committed and build green.** The user's deployment host (Tailscale `100.108.224.12`) is the only environment you should touch for verification — the local repo on this machine (`/home/peter/Projects/MealPlanner`) was the editing host; the running app lives elsewhere.
|
||
|
||
**Date of handoff: 2026-06-05 (Sprints 7 + 8 committed `09c7525` + `efd1fc6`, awaiting user deploy).**
|
||
|
||
---
|
||
|
||
## How to take over (fresh-agent quickstart)
|
||
|
||
If you are a new agent continuing this work, do this **in order**:
|
||
|
||
1. **Read** `docs/ORIENTATION.md` (project orientation) → `docs/HANDOFF.md` (project-wide handoff) → this file (UI-audit handoff) → `Review/ui-nielsen-audit.md` (the audit itself).
|
||
2. **Skim** the per-sprint verification docs in `Review/sprint{1..10}-verification.md`. They are the source of truth for the deploy + smoke flow.
|
||
3. **Check the user's deployment status** — the user deploys in batches. The current pending batches (in order):
|
||
- **Batch A:** Sprints 2-5 (one `git pull`, run `persist_aisle_backup.sql`, `alembic upgrade head`, `docker compose up -d --build backend frontend`). The 0015 cast fix is in `d78bd18`; Sprint 2's deploy was blocked on it.
|
||
- **Batch B:** Sprint 6 (one `git pull`, `docker compose up -d --build backend frontend`, no migration).
|
||
- **Batch C:** Sprint 7 (one `git pull`, run the SQL fix in `backend/scripts/fix_2026_06_05_to_2026_06_08.sql`, `docker compose up -d --build backend frontend`).
|
||
- **Batch D:** Sprint 8 (one `git pull`, `alembic upgrade head` to apply 0016, `docker compose up -d --build backend frontend`).
|
||
- **Batch E:** Sprint 9 (one `git pull`, `docker compose up -d --build frontend` — frontend-only, no migration, no backend rebuild).
|
||
- **Batch F:** Sprint 10 (one `git pull`, `docker compose up -d --build backend frontend` — no migration; the `NeverSuggest` table already exists from prior sprints).
|
||
- **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).
|
||
- **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`).
|
||
- **Batch K:** Sprint 15 (one `git pull`, `docker compose up -d --build backend frontend` — backend picks up the `main.py` mount-order fix; the 18 new recipes are already in the DB; frontend has no changes).
|
||
4. **Open issues** in `.agent/plan.md` (the "Phase R1-R3" section is a prior plan; the **Sprint 15 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).
|
||
|
||
**TL;DR of where things stand:**
|
||
|
||
- 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.
|
||
- **Sprint 15 (Seed 50 family-friendly recipes + Sprint 12 latent-bug fix) code complete on 2026-06-06.** 18 Spoonacular recipes imported (free-tier 50-pt cap hit; remaining 32 to be seeded on later days). DB went 31 → 49 total recipes. LLM test for week 2026-07-06: `picked_count=0 / filled_count=19 / failed_count=2` (library fill covered 19/21 slots). Sprint 12 latent-bug fix: `main.py` mount order. Awaiting commit + push.
|
||
- Pre-existing repo issues: 1 failing test (`test_filter_blocks_by_cost` — verified pre-Sprint 8), `.gitignore` `lib/` bug, no CI. Documented.
|
||
|
||
---
|
||
|
||
## ⚠️ Pending user deploy — Sprints 7 + 8 (committed, awaiting pull)
|
||
|
||
### Sprint 8 — "Deny" semantics (C + Z, hard-filter escalation)
|
||
|
||
**Status: COMMITTED `efd1fc6` on 2026-06-05. Build green. 21/21 planner tests pass.** Awaiting user to `git pull` + `alembic upgrade head` + rebuild.
|
||
|
||
**User policy decision (2026-06-05, exact words):** "Hard filter. If it is denied this week twice, it should be considered denied for good."
|
||
|
||
**Root cause (one-liner):** the planner has no cross-week memory of denials. Denials live on the `meal_plan_item` row, are never consulted by the planner, and the `NeverSuggest` blocklist is empty for the user's family. The user's "Roasted Sweet Potato and Chickpea Bowl" was denied on 2026-05-15 but the recipe was still in the pool for the next 90+ days.
|
||
|
||
**Policy (Sprint 8):**
|
||
|
||
- "Approve" → `item.approval_status = approved`. n/a.
|
||
- "Deny this week" (1st in 90d) → `denied` + `denial_expires_at = now() + 90d`. Recipe becomes eligible again after 90d.
|
||
- "Deny this week" (2nd in 90d — **server-side auto-escalation**) → `denied` + `denial_expires_at = NULL` + a `NeverSuggest` row written. Permanent.
|
||
- "Never again" (explicit) → same as the 2nd-time auto-escalation. Permanent.
|
||
|
||
**Scope (12 boxes):** see `.agent/plan.md` "Active sprint" section. Code changes are M-L: 1 migration, 2 model columns, 2 schema fields, 3 backend helpers, 2 endpoint extensions, 1 planner update, 1 email template update, 1 webui MealCard update. **No new dependencies. Migration 0016 required.**
|
||
|
||
**Tracking docs:** `Review/sprint8-verification.md` (deploy + smoke), `Review/ui-nielsen-audit.md` Sprint 8 status block, `fix-ui-audit.md` T2.1–T2.10, this file, `docs/HANDOFF.md` Sprint 8 section.
|
||
|
||
**Thread 3 (§Future backlog) is deferred** until S8 is deployed + verified. F9-full (local Ollama model pull on the host) remains a future sprint. **Sprint 9 (F1 onboarding) + 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) are all committed 2026-06-05, awaiting user deploy.**
|
||
|
||
### Sprint 9 — F1 Onboarding Tour (H10)
|
||
|
||
**Status: COMMITTED + POST-DEPLOY FIX on 2026-06-05. Build green. Frontend-only.** Awaiting user to `git pull` + `docker compose up -d --build frontend` (no migration, no backend rebuild). The original commit (`6e386ba`) had a broken dismiss path (X / Skip / Esc / "Got it" did nothing — `onComplete` was wired to `useOnboarding().reset()` which is the inverse operation). Post-deploy fix is `1562929`: split into two callbacks `onComplete` (dismiss → `markComplete()`) and `onReset` (re-show → `reset()`). User confirmed browser smoke passes after the fix.
|
||
|
||
**Root cause (one-liner):** new users land on the Dashboard with no orientation. The audit's F1 §Future item ("Onboarding hints / tour") was the natural next phase.
|
||
|
||
**Scope (4 boxes):** 1 new `OnboardingTour.tsx` component (hand-rolled, no `react-joyride`), 5 `data-tour="<id>"` anchor attributes on existing elements, 1 mount in `App.tsx`, 1 localStorage key (`mealplanner:onboarding-complete`). **No new dependencies. No backend changes.**
|
||
|
||
**Tour behavior:**
|
||
- Auto-shows on first visit to `/` (the only root route). Subsequent visits do not show.
|
||
- 4 steps: Dashboard / Pantry / Recipes / Shopping List. Each anchors to a `[data-tour="<id>"]` element on the relevant page.
|
||
- Keyboard: `1`–`4` jump to step, `←/→` step back/forward, `Esc` dismiss.
|
||
- Off-route fallback: if the user is on a different page than the current step's anchor, the tooltip renders as a centered card with an "Open <page>" CTA.
|
||
- `?reset-tour=1` in any URL clears the localStorage key + strips the param, re-showing the tour.
|
||
- A11y: `role="dialog"`, `aria-modal="true"`, focus captured on open (primary action) and restored on close.
|
||
|
||
**Tracking docs:** `Review/sprint9-verification.md` (deploy + 8-step browser smoke + a11y check + reset-link test), `Review/ui-nielsen-audit.md` Sprint 9 status block, `fix-ui-audit.md` T3.1–T3.4, this file, `docs/HANDOFF.md` Sprint 9 section.
|
||
|
||
### Sprint 10 — "Deny Forever" on Recipes (user-driven)
|
||
|
||
**Status: COMMITTED on 2026-06-05. Build green. Backend + frontend, no migration.** Awaiting user to `git pull` + `docker compose up -d --build backend frontend` (the `NeverSuggest` table already exists from prior sprints).
|
||
|
||
**Root cause (one-liner):** the user can already block a recipe from a meal plan (Sprint 8), but a recipe they've never seen planned can only be blocked by the admin via the `NeverSuggest` admin API. Sprint 10 surfaces the same `NeverSuggest` infrastructure on the Recipes surface so the user can pre-emptively mark a recipe as "allergy" or "dislike" while browsing.
|
||
|
||
**Scope (7 boxes):** 2 new public backend endpoints (`POST` + `DELETE /api/never-suggest`), 1 schema field (`recipe_name`), 1 new `NeverSuggestButton.tsx` component (~290 lines), 1 API client (`neverSuggest.list/add/remove`), 1 overlay on `RecipeCard`, 1 button group in `RecipeDetail` top bar. **No new dependencies. No migration. Admin path unchanged.**
|
||
|
||
**Two reasons (matching the server's `NeverSuggestReason` enum):**
|
||
- `Allergy` (red) — requires `window.confirm`. Permanent, irreversible to the planner.
|
||
- `Dislike` (neutral) — no confirm. The 6s undo toast is the escape hatch.
|
||
|
||
**Undo semantics:** Sprint 3 B12 `showToast.undo()` pattern. Click Undo → `DELETE /api/never-suggest/{id}` + 4 query invalidations so the recipe reappears immediately.
|
||
|
||
**Tracking docs:** `Review/sprint10-verification.md` (deploy + 9-step browser smoke + 5 API curls + undo test + a11y check), `Review/ui-nielsen-audit.md` Sprint 10 status block, `fix-ui-audit.md` T4.1–T4.9, this file, `docs/HANDOFF.md` Sprint 10 section.
|
||
|
||
### Sprint 11 — Wire the dead "Generate Meal Plan" CTA (user-driven)
|
||
|
||
**Status: COMMITTED on 2026-06-05. Build green. Frontend-only.** Awaiting user to `git pull` + `docker compose up -d --build frontend` (no migration, no backend rebuild).
|
||
|
||
**Root cause (one-liner):** the Dashboard's empty state has rendered a "Generate Meal Plan" button since Sprint 1 with `onClick: () => {}` — clicking it does nothing. The user has no path from a fresh install to a populated plan.
|
||
|
||
**Scope (3 boxes):** 1 new `handleGenerateFirstPlan` handler in `Dashboard.tsx` (~50 lines, calls existing `meals.create` + `meals.fillEmptySlots`), 1 new `EmptyState.action.disabled` prop (optional, backward-compatible), 1 toast format reused from Sprint 6 F4. **No new dependencies. No backend changes.** The two endpoints already exist from Sprint 6+.
|
||
|
||
**Generation flow:** 1) `POST /api/meals` with `{ week_start_date, status: 'draft', items: [] }` to create a fresh plan. 2) `POST /api/meals/{id}/fill-empty-slots` with `{ meal_types: ['breakfast', 'lunch', 'dinner'] }` to fill it from the library. 3) Invalidate `['mealPlan', weekStart]`. The page re-renders with the populated plan.
|
||
|
||
**Race handling:** if `meals.create` returns 400 with `detail: "Meal plan for this week already exists"` (another tab created one first), the handler falls through to `getPlanned(weekStart)` to get the existing plan's id, then calls `fillEmptySlots` against it. No error toast in this case.
|
||
|
||
**Tracking docs:** `Review/sprint11-verification.md` (deploy + 4-step browser smoke + race test + 2 API curls), `Review/ui-nielsen-audit.md` Sprint 11 status block, `fix-ui-audit.md` T5.1–T5.5, this file, `docs/HANDOFF.md` Sprint 11 section.
|
||
|
||
### Sprint 12 — F8 Spoonacular search (§Future H10) (user-driven)
|
||
|
||
**Status: COMMITTED on 2026-06-05. Build green. Backend + frontend.** Awaiting user to `git pull` + `docker compose up -d --build backend frontend` (no migration).
|
||
|
||
**Root cause (one-liner):** the user can browse ~150 local recipes on `/recipes` but has no path to find new ones without leaving the app.
|
||
|
||
**Scope (6 boxes):** NEW `backend/app/api/recipe_search.py` (~270 lines, 2 endpoints + module-level quota counter), 1 schema addition (`RecipeSearchHit` + `RecipeImportRequest`), 1 `Settings` field (`SPOONACULAR_API_KEY`), 1 router registration, 5 new `mealPlannerApi.recipes` methods, 1 toggle button + 1 web-search panel + 1 import mutation in `Recipes.tsx`, 2 optional `RecipeIngredient` fields. **No new dependencies. No migration. No pre-existing WIP files touched.**
|
||
|
||
**Free-tier quota:** Spoonacular = 150 points/day. `complexSearch` = 1 pt + 0.01/result. The new search uses summary-only (1.1 pts/query). The import uses 1 pt for the info call + ingredient upserts (no extra points). 140-pt daily budget leaves a 10-pt safety margin. Counter resets on process restart.
|
||
|
||
**Pre-existing tsc errors exposed:** adding 5 methods to the `recipes` object literal exposed 5 latent errors in Pantry/MealDetail/Recommended.tsx (calls to non-existent methods + 2 missing `RecipeIngredient` fields). Resolved per user decision: 3 stub methods + 2 optional type fields. ~7 lines of fixes; no WIP touched. Documented in `Review/sprint12-verification.md` D-fix section + `fix-ui-audit.md` T6.4.
|
||
|
||
**Tracking docs:** `Review/sprint12-verification.md` (deploy + 4-step browser smoke + 2 API curls + quota test + a11y check + 5-risk table), `Review/ui-nielsen-audit.md` Sprint 12 status block, `fix-ui-audit.md` T6.1–T6.6, this file, `docs/HANDOFF.md` Sprint 12 section.
|
||
|
||
### Sprint 13 — F9-lite (Ollama Cloud plan synthesis) (§Future H10) (user-driven)
|
||
|
||
**Status: COMMITTED on 2026-06-05. Build green. Backend + frontend.** Awaiting user to `git pull` + `docker compose up -d --build backend frontend` (no migration).
|
||
|
||
**Root cause (one-liner):** the Sprint 11 "Generate Meal Plan" CTA was library-only. Sprint 13 splits it into a 2-step modal: "Use the recipe library" (Sprint 11 unchanged) or "Ask the LLM" (new). The LLM path lets the user describe what they want for the week ("Italian-inspired, vegetarian") and uses kimi-k2.6:cloud on ollama.com to pick meals from the local library.
|
||
|
||
**Scope (5 boxes):** NEW `backend/app/api/llm_plan.py` (~280 lines, 1 endpoint + 4 helpers + tolerance for malformed LLM responses), 2 schema additions (`LLMPlanRequest` + `LLMPlanResponse`), 1 router registration, 1 `mealPlannerApi.llm.plan` method, prompt modal in `Dashboard.tsx` (radio + textarea + click-outside-to-dismiss). **No new dependencies. No migration. No pre-existing WIP files touched.** Reuses `OLLAMA_BASE_URL` / `OLLAMA_API_KEY` / `OLLAMA_MODEL` from `config.py:36-38`.
|
||
|
||
**LLM tolerance:** a 60s timeout, parse-failure (markdown code fences, trailing commentary), or empty response all return 0 picks; the library fill takes over. The user sees a success toast with `picked_count: 0` — same as if the LLM had returned 0 picks — never a crash.
|
||
|
||
**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.1–T7.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.1–T7.5, this file, `docs/HANDOFF.md` Sprint 14 section.
|
||
|
||
### Sprint 15 — Seed 50 family-friendly recipes for 4-week planning (content op) + Sprint 12 latent-bug fix (user-driven)
|
||
|
||
**Status: CODE COMPLETE on 2026-06-06. 18 Spoonacular recipes imported today (free-tier 50-pt cap hit; remaining 32 to seed on later days). DB went 31 → 49 total recipes. `main.py` mount-order fix verified (Sprint 12 latent bug). Awaiting user commit + push.** No frontend change, no migration, no runtime dep change.
|
||
|
||
**Root cause (one-liner):** User direction (2026-06-05): "Lets build out recipes for the coming 4 weeks in advance. In order to do this, lets add more recipes to the list of available ones." During Sprint 15 I also discovered that Sprint 12's `/api/recipes/search` route is shadowed by the pre-existing WIP's `GET /{recipe_id}` — without the `main.py` mount-order fix, every "Search the web" query would 422.
|
||
|
||
**Scope (4 boxes):**
|
||
1. **`backend/app/main.py` mount-order fix** — moved `recipe_search_api.router` import to line 39 + moved the `include_router` call to before `recipes_api.public_router`. 3-line comment explains the why. Verified `GET /api/recipes/search?q=...` returns 200 with hits; `POST /api/recipes/import` still 201; WIP's `GET /{recipe_id}` still works for valid UUIDs.
|
||
2. **`scripts/seed_recipes.py` (NEW, ~150 lines)** — 50-query one-shot Python script. Hits Spoonacular's `complexSearch` directly (avoids the broken backend route during the time before the main.py fix took effect; also avoids the backend's quota counter). For each query: takes the top hit, POSTs to the backend's `/api/recipes/import`. Idempotent (409 on duplicate). 1.5 sec sleep. Stops cleanly on 402 (quota exhausted).
|
||
3. **18 recipes imported today** (free-tier 50-pt cap hit at query 28). Distribution: 8 Italian + 7 Mexican + 3 Asian + 0 American + 0 Mediterranean (American + Mediterranean queries came after the cap). Plus 1 from earlier manual test (Pasta with Garlic, Scallions, Cauliflower & Breadcrumbs, ext=716429). DB now has 49 total recipes.
|
||
4. **LLM test** (Sprint 13 endpoint, week 2026-07-06): `picked_count=0 / filled_count=19 / failed_count=2`. Library fill covered 19/21 slots; the LLM (kimi-k2.6:cloud) returned 0 picks. Sprint 13 tolerance worked as designed (zero-pick response is not a crash).
|
||
|
||
**Cost (corrected):** free tier is 50 pts/day, not 150. 50 queries = 50 × 1.10 (search) + 50 × 1 (import) = 105 pts. Need 3 days on free tier. **Follow-up: lower `_DAILY_LIMIT=140` in `recipe_search.py:48` to 45** to match the real cap (5-pt safety margin).
|
||
|
||
**No pre-existing WIP files touched.** Only `main.py` was reordered (one-line + comment); `recipes.py`, `schemas/recipe.py`, `nginx.conf` are unchanged.
|
||
|
||
**Tracking docs:** `Review/sprint15-verification.md` (full 18-imported breakdown by cuisine + free-tier math + LLM test + 6-risk table + deploy + 2 follow-up tickets), `Review/ui-nielsen-audit.md` Sprint 15 status block (T8.1-T8.3), `fix-ui-audit.md` Sprint 15 section (T8.1-T8.5), this file, `docs/HANDOFF.md` Sprint 15 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.
|
||
|
||
**Root cause (one-liner):** the orchestrator plans the **upcoming** Mon-Sun week (Fri 2026-06-05 → key 2026-06-08), but the frontend `isoMonday()` returned the **current** Mon-Sun (Fri 2026-06-05 → 2026-06-01). Email + DB + webui disagree by 7 days. User sees an empty page.
|
||
|
||
**Scope (6 boxes):** 1 backend function, 1 frontend util rename, 1 new `WeekRangeNav` component, 2 call-site updates, 1 SQL fix script. **No migration.**
|
||
|
||
**Tracking docs:** `Review/sprint7-verification.md` (deploy + smoke), `Review/ui-nielsen-audit.md` Sprint 7 status block, `fix-ui-audit.md` S7.1–S7.6, this file, `docs/HANDOFF.md` Sprint 7 section.
|
||
|
||
---
|
||
|
||
## TL;DR
|
||
|
||
Twelve commits land all 14 audit findings + 6 §Future items + 2 user-driven sprints:
|
||
|
||
| Sprint | Commit | Scope | Build | Deploy |
|
||
|---|---|---|---|---|
|
||
| 1 | `f3e4a44` | 5 P0 blockers | ✅ green | ✅ deployed 2026-06-02 by user |
|
||
| 2 | `ccc70aa` + `f5fb755` | 6 P1s + bonus S3.3 | ✅ green | ⚠️ blocked — see S2 + 0015 fix below |
|
||
| 3 | `e90a9d6` | 3 P2s + a11y sweep | ✅ green | ⚠️ blocked — depends on S2 deploy |
|
||
| 4 | `d71b67a` | F7 global error handler + F6 plan-status aria-label | ✅ green | ⚠️ not yet deployed (frontend-only) |
|
||
| 5 | `d78bd18` | F5 URL week selector + **CRITICAL 0015 cast fix** | ✅ green | ⚠️ not yet deployed |
|
||
| 5 | `f740f40` | F2 keyboard shortcuts + ShortcutHelpBanner | ✅ green | ⚠️ not yet deployed |
|
||
| 6 | `8ad4ef6` | F3 bulk pantry add + F4 plan-the-week (ShoppingList + Dashboard) | ✅ green | ⚠️ not yet deployed (backend + frontend, no migration) |
|
||
| 7 | `09c7525` | webui "empty meal plan" date-semantics fix + new `WeekRangeNav` + SQL data fix | ✅ green | ⚠️ committed; awaiting user deploy |
|
||
| 8 | `efd1fc6` | "Deny" semantics (C + Z, hard-filter escalation) | ✅ green | ⚠️ committed; awaiting user deploy |
|
||
| 9 | (committed 2026-06-05) | F1 Onboarding Tour (H10) — hand-rolled, no new deps, 4-step welcome tour with `?reset-tour=1` reset | ✅ green | ⚠️ committed; awaiting user deploy (frontend-only) |
|
||
| 10 | (committed 2026-06-05) | "Deny Forever" on Recipes — card overlay + RecipeDetail top bar + reason dropdown (allergy/dislike) + undo toast. New `POST`/`DELETE /api/never-suggest` (public) + `recipe_name` join. | ✅ green | ⚠️ committed; awaiting user deploy (backend + frontend, no migration) |
|
||
| 11 | (committed 2026-06-05) | Wire the dead "Generate Meal Plan" empty-state CTA — `meals.create` + `meals.fillEmptySlots`; race-safe; reusable for F8/F9. | ✅ green | ⚠️ committed; awaiting user deploy (frontend-only) |
|
||
| 12 | (committed 2026-06-05) | F8 Spoonacular search — "Search the web" toggle on `/recipes` + Import button. New `GET /api/recipes/search` + `POST /api/recipes/import`. Quota-gated (140pt/day). | ✅ green | ⚠️ committed; awaiting user deploy (backend + frontend) |
|
||
| 13 | (committed 2026-06-05) | F9-lite Ollama Cloud plan synthesis — prompt modal on Dashboard CTA. New `POST /api/llm/plan`. Library-or-LLM radio; 60s timeout tolerance; library fills the rest. | ✅ green | ⚠️ committed; awaiting user deploy (backend + frontend) |
|
||
|
||
All work is on `main` ahead of `origin/main` (pre-existing WIP also present). All 13 sprints compile. **Sprint 1 is live. Sprints 2-13 are not yet live on `100.108.208.56:8082/`.**
|
||
|
||
**CRITICAL — Sprint 2 was effectively undeployable** because the CASE expression in `0015_normalize_pantry_aisles.py` failed with `text = boolean` on the `varchar(100) aisle` column. The bug is fixed in `d78bd18` (Sprint 5). Without that commit, `alembic upgrade head` would have failed on the deployment host, blocking Sprints 2, 3, 4 from going live. **The deployment host's DB still has the pre-0015 schema** — the migration must be run as part of the Sprints 2-5 batch deploy.
|
||
|
||
**Next action:** the user runs the deploy commands in `Review/sprint2-verification.md`, `Review/sprint3-verification.md`, `Review/sprint4-verification.md`, `Review/sprint5-verification.md`, `Review/sprint6-verification.md`, `Review/sprint7-verification.md`, and `Review/sprint8-verification.md` on the deployment host, then smoke-checks per the checklists. Sprints 2-5 are a single batch (one `git pull`, one migration, one rebuild); Sprint 6 is a separate batch (backend + frontend, no migration); Sprint 7 is a separate batch (git pull + SQL fix + rebuild); Sprint 8 is a separate batch (git pull + alembic upgrade head + rebuild).
|
||
|
||
---
|
||
|
||
## Where the work lives
|
||
|
||
```
|
||
/home/peter/Projects/MealPlanner/
|
||
├── Review/
|
||
│ ├── ui-nielsen-audit.md # The audit (276 → ~340 lines). Status blocks per sprint at the top.
|
||
│ ├── sprint2-verification.md # Deploy + smoke checks for Sprint 2 (BACKEND MIGRATION INCLUDED).
|
||
│ ├── sprint3-verification.md # Deploy + smoke checks for Sprint 3 (frontend only).
|
||
│ ├── sprint4-verification.md # Deploy + smoke checks for Sprint 4 (F7+F6, frontend only).
|
||
│ ├── sprint5-verification.md # Deploy + smoke checks for Sprint 5 (F5+F2+0015 fix; backend+frontend).
|
||
│ ├── sprint6-verification.md # Deploy + smoke checks for Sprint 6 (F3+F4; backend+frontend, no migration).
|
||
│ ├── docs-claude.md # Prior docs adversarial review (not yours).
|
||
│ ├── docs-gpt5.5.md # Prior docs adversarial review.
|
||
│ ├── repo-gpt5.5.md # Prior repo adversarial review.
|
||
│ ├── reviewconcensus.md # Prior review consensus.
|
||
│ └── synthesis.md # Prior review synthesis.
|
||
├── fix-ui-audit.md # The plan. Implementation notes per task + commit refs. Sprints 4-5 added.
|
||
├── docs/HANDOFF.md # The project-wide handoff (don't duplicate — read for context).
|
||
└── docs/ORIENTATION.md # Project orientation.
|
||
```
|
||
|
||
**Read first**, in this order: `docs/ORIENTATION.md` → `docs/HANDOFF.md` → `Review/ui-nielsen-audit.md` (the audit) → `fix-ui-audit.md` (the plan) → the four `sprintN-verification.md` files.
|
||
|
||
---
|
||
|
||
## Audit findings — all closed
|
||
|
||
The audit has 14 findings + 3 a11y items. They map 1:1 to the plan's task list. All have code committed. Some have known caveats — read the audit's "Sprint 1/2/3 status" blocks at the top of `Review/ui-nielsen-audit.md` for the per-finding deploy status.
|
||
|
||
**P0 blockers (Sprint 1, `f3e4a44`):**
|
||
- B1 Recipe ingredients fused unit+name (`RecipeDetail.tsx:161`) — `.trim()` removed.
|
||
- B2 Meal ingredients missing quantities (`MealDetail.tsx:248-252`) — `qty` field; also `ing.ingredient?.name` fallback.
|
||
- B3 `$N/A per serving` (`MealDetail.tsx:191`) — conditional on `cost != null`.
|
||
- B4 `/recommended` blank page — `*` NotFound catch-all + `/recommended` alias to `/recipes/recommended`. New `pages/NotFound.tsx`. `EmptyState` extended with optional `to` prop.
|
||
- B5 Mobile empty meal slots hidden (`Dashboard.tsx:164,219`) — `hidden md:*` removed; `min-h-11` (44 px) on the Generate button.
|
||
|
||
**P1 (Sprint 2, `ccc70aa` + `f5fb755`):**
|
||
- B6 Meal-card title 1-line truncate — `line-clamp-2`; image shrinks 56→40 on `<md`.
|
||
- B7 MealDetail hero overlap + spoonacular SEO copy — hero reworked, `cleanDescription()` in `lib/utils.ts` strips 14 boilerplate patterns, raw text → "Notes from source" disclosure.
|
||
- B8 Pantry aisle free-text → canonical select — `PANTRY_AISLES` enum, `Select` components, ingredient name marked `*`. **Backend migration `0015_normalize_pantry_aisles.py` normalizes `ingredient.aisle` + `grocery_item.aisle` in a single transaction; dry-run at `backend/scripts/dry_run_aisle_migration.sql`; persistent backup at `backend/scripts/persist_aisle_backup.sql`.**
|
||
- B9 ShoppingList snake_case aisle labels — `AISLE_LABEL` map + `aisleDisplay()`.
|
||
- B10 Mobile pantry table — `role="region"` + right-edge white-to-transparent gradient hint.
|
||
- B11 Recipes filters — `applied`/`pending` state split, Apply/Reset buttons, active-count chip on Filters button.
|
||
|
||
**P2 + a11y (Sprint 3, `e90a9d6`):**
|
||
- B12 Undo-toast replaces `confirm()` — `lib/toast.tsx` (renamed from `.ts` for JSX) gains `showToast.undo(message, onUndo, ms=5000)`. Dashboard undo re-fires `generateItem` to refill the slot (recipe may differ — documented in plan §R4). Pantry undo re-fires `pantry.add` with original fields (fully reversible).
|
||
- B13 Mobile nav wrap — `whitespace-nowrap` + `px-2 sm:px-3`.
|
||
- S3.3 Mobile shopping-list stat grid (3-col on all viewports) — done in Sprint 2.
|
||
- S3.4 ErrorBoundary already mounted at `App.tsx:42` — verified, no code change.
|
||
- S3.5 A11y sweep — `<nav aria-label="Primary">`, `aria-current="page"`, `<main id="main-content">`, `Badge` extended with `icon` + `aria-label` props, approval-status Badge uses `aria-label="Approval status: …"`.
|
||
|
||
---
|
||
|
||
## Environment quirks you MUST know
|
||
|
||
### 1. The deployment host is not this machine
|
||
|
||
This repo lives on a development host (Tailscale `100.108.146.47`). The live deployment is on the user's home server at Tailscale `100.108.224.12`, served at `http://100.108.208.56:8082/`. The deployment host's Docker stack is the source of truth for the running app.
|
||
|
||
When you `docker compose up -d --build` *here*, you rebuild images on this host's Docker daemon, not the deployment host's. The frontend I built in Sprint 1 was verified by the user *manually* deploying from this repo on the deployment host.
|
||
|
||
**Don't try to push to the deployment host.** The user's workflow is: commit locally, then `git pull` on the deployment host and rebuild there.
|
||
|
||
### 2. The db is in a container; no host psql
|
||
|
||
On the deployment host (`docker-ubuntu`), `psql` is not installed. All DB commands must run via `docker compose exec db psql -U mealplanner -d mealplanner -f /dev/stdin < script.sql`. The `Sprint 2` verification doc has the exact incantations.
|
||
|
||
**On the local dev host** (this one), `psql` IS available because the same `db` service runs locally for the user's dev work. But the DB used in dev is *different* from the one the user is migrating (Tailscale host). The dry-run I ran on this host showed 21,196 rows would change; the deployment-host DB will have a different count.
|
||
|
||
### 3. `.gitignore` blocks `frontend/src/lib/`
|
||
|
||
There's a pre-existing repo bug: `.gitignore` line 17 is `lib/` (the Python `lib/` ignore), and it catches `frontend/src/lib/`. New files in `frontend/src/lib/` need `git add -f`. Existing tracked files (`utils.ts`) work; the new `toast.tsx` was force-added in commit `e90a9d6`. Be aware when adding new files there.
|
||
|
||
### 4. The `.env` file is at the project root
|
||
|
||
It contains `SESSION_PASSWORD=test-family-password`, `APP_BASE_URL=http://100.108.224.12:8082` (Tailscale host), real SendGrid keys, real Ollama creds. Don't commit it (`.env` is in `.gitignore`). Don't `cat` it casually.
|
||
|
||
### 5. ESLint is not configured
|
||
|
||
`npm run lint` errors with "ESLint couldn't find a configuration file." That's a pre-existing repo gap, not introduced by this work. The `build` script (`tsc && vite build`) is the typecheck.
|
||
|
||
### 6. Pre-existing WIP is in `git status`
|
||
|
||
When you `git status`, you'll see modifications to `backend/app/api/meals.py`, `backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, `frontend/src/api/index.ts`, `nginx/nginx.conf`. These are NOT mine — they were already in the working tree from prior commits. Don't commit them as part of UI audit work. If you need to amend them, do it on a separate commit with a separate message.
|
||
|
||
---
|
||
|
||
## What "verification" actually looks like
|
||
|
||
This work has *no automated test suite in CI* (per the audit, only `npm run build` enforces typecheck). The verification gate is **screenshot + manual smoke** on the deployment host.
|
||
|
||
For each sprint, a `sprintN-verification.md` lists:
|
||
- A **deploy command** block (the user runs on the deployment host).
|
||
- A **smoke-check** list of manual interactions to perform in the browser.
|
||
- A list of "Things to look for" — the observable behavior change for each finding.
|
||
|
||
The user's flow is: read the smoke check, run the deploy, click around, mark each item ✅ or report an issue. The verification log is a record, not a script.
|
||
|
||
**If you need to re-verify locally**, the original screenshots are at `/tmp/opencode/mp-review/screenshots/` (20 PNGs, 1366 px and 390 px viewports). Post-fix screenshots are in `/tmp/opencode/mp-review/screenshots/fix-sprint1/`. The playwright harness script is at `/tmp/opencode/mp-review/shoot-sprint1.js`. Useful templates for re-screenshotting Sprints 2 and 3 are in there — adapt the URL list.
|
||
|
||
---
|
||
|
||
## Active risks & open questions
|
||
|
||
1. **S2.3 / B8 backend migration NOT YET RUN on deployment host.** The user must run `alembic upgrade head` after `git pull`. Dry-run shows 21k rows change on the dev DB; the prod DB may differ. The persistent backup (`persist_aisle_backup.sql`) creates two permanent tables the operator can `DROP` after confidence is established.
|
||
|
||
2. **S3.1 / B12 Dashboard Undo is "rebuild, not restore".** `meals.generateItem` re-fills the slot with a *new* recipe; the original recipe is gone. The plan documents this trade-off (§R4). If the user pushes back, the alternative is a backend endpoint to restore from a snapshot (out of scope, would need a separate task).
|
||
|
||
3. **`frontend/src/lib/toast.tsx` is force-added because of the `lib/` gitignore bug.** Future agents should be aware: any new file in `frontend/src/lib/` needs `git add -f`. Consider fixing the `.gitignore` (use `/lib/` or specific Python paths instead of `lib/`) as a small follow-up.
|
||
|
||
4. **S2.3 may need a follow-up constraint** — the migration docstring says no `CHECK (aisle IN (...))` constraint is added. The next migration (`0016`) could add it, but that's an optional tightening. The frontend already constrains the form to canonical values, so DB constraint is belt-and-suspenders.
|
||
|
||
5. **`scripts/dry_run_aisle_migration.sql` is read-only but reads from prod.** If the user runs it via `docker exec ... psql -f /dev/stdin < script.sql`, no writes happen. But they should know it's a *query*, not a script. The comment at the top of the file says so.
|
||
|
||
---
|
||
|
||
## What's NOT in scope (per audit §Future)
|
||
|
||
F2, F3, F4, F5, F6, and F7 are done across Sprints 4-6. The audit intentionally deferred these. If the user asks, they become a new plan:
|
||
- F1. Onboarding hints / tour (H10)
|
||
- ~~F2. Keyboard shortcuts (`/`, `g p`, `g s`, `n m`)~~ — done in Sprint 5
|
||
- ~~F3. Bulk add on Pantry/Shopping List (H7)~~ — done in Sprint 6 (ShoppingList only)
|
||
- ~~F4. Plan-the-whole-week button (H7)~~ — done in Sprint 6
|
||
- ~~F5. Persistent week selector in URL~~ — done in Sprint 5
|
||
- ~~F6. Badge a11y for color-only signals (generalized)~~ — done in Sprint 4
|
||
- ~~F7. Global `react-query` `onError` toast handler~~ — done in Sprint 4
|
||
- F8. Backend Spoonacular enrichment (separate proposal in `docs/proposals/`)
|
||
- F9. Ollama LLM matcher (separate proposal in `docs/proposals/`)
|
||
|
||
**Follow-up tickets surfaced during Sprints 4-6:**
|
||
- Wire the no-op `Generate Meal Plan` empty-state CTA at `Dashboard.tsx:415` to a real user-facing "create a new plan" path (orchestrator/admin flow). Distinct from F4 (which is "plan exists, slots are empty"). Currently the user sees the CTA on `/` only if their family has NO plan at all.
|
||
- Pantry bulk-add (F3 was scoped to ShoppingList in Sprint 6; adding multi-select to a 4-column table on mobile is a larger surface).
|
||
- "Sprints 2-5 batch" deployment: Sprints 2, 3, 4, 5 are deployable as a single batch now that the 0015 cast fix is in. Sprint 6 is a separate batch (no migration). See the per-sprint `sprintN-verification.md` deploy sections.
|
||
|
||
---
|
||
|
||
## File-level diff summary (audit work only)
|
||
|
||
| Sprint | File | What |
|
||
|---|---|---|
|
||
| 1 | `frontend/src/pages/RecipeDetail.tsx` | B1: drop `.trim()` |
|
||
| 1 | `frontend/src/pages/MealDetail.tsx` | B2: `qty` + name fallback; B3: cost conditional |
|
||
| 1 | `frontend/src/App.tsx` | B4: `/recommended` alias + `*` NotFound |
|
||
| 1 | `frontend/src/pages/Dashboard.tsx` | B5: remove `hidden md:*` on empty slots |
|
||
| 1 | `frontend/src/pages/NotFound.tsx` | **NEW** — uses EmptyState |
|
||
| 1 | `frontend/src/components/ui/EmptyState.tsx` | optional `to` prop |
|
||
| 1 | `frontend/src/types/index.ts` | `RecipeIngredient.notes` + `ingredient?` |
|
||
| 2 | `frontend/src/pages/Dashboard.tsx` | B6: line-clamp-2 + smaller image |
|
||
| 2 | `frontend/src/pages/MealDetail.tsx` | B7: hero rework + cleanDescription + disclosure |
|
||
| 2 | `frontend/src/lib/utils.ts` | B7: cleanDescription() |
|
||
| 2 | `frontend/src/pages/Pantry.tsx` | B8: select for aisle/unit + required marker; B10: scroll hint |
|
||
| 2 | `frontend/src/types/index.ts` | B8: PANTRY_AISLES + PantryAisle type |
|
||
| 2 | `backend/alembic/versions/0015_normalize_pantry_aisles.py` | **NEW** — migration |
|
||
| 2 | `backend/scripts/dry_run_aisle_migration.sql` | **NEW** — read-only preview |
|
||
| 2 | `backend/scripts/persist_aisle_backup.sql` | **NEW** — persistent backup |
|
||
| 2 | `frontend/src/pages/ShoppingList.tsx` | B9 + S3.3: aisle label + 3-col grid |
|
||
| 2 | `frontend/src/pages/Recipes.tsx` | B11: applied/pending filter + Apply/Reset + active count |
|
||
| 3 | `frontend/src/lib/toast.ts` → `.tsx` | renamed + undo helper |
|
||
| 3 | `frontend/src/pages/Dashboard.tsx` | B12: undo on delete; S3.5: aria-label on status badge |
|
||
| 3 | `frontend/src/pages/Pantry.tsx` | B12: reversible undo on remove |
|
||
| 3 | `frontend/src/App.tsx` | B13 + S3.5: nav nowrap, aria-current, main id |
|
||
| 3 | `frontend/src/components/ui/Badge.tsx` | S3.5: icon + aria-label props |
|
||
| 4 | `frontend/src/lib/toast.tsx` | F7: extractErrorMessage + showApiError |
|
||
| 4 | `frontend/src/App.tsx` | F7: QueryCache/MutationCache onError + queries defaultOptions |
|
||
| 4 | `frontend/src/pages/Dashboard.tsx` | F7: removed 6 local onError; F6: plan-status aria-label |
|
||
| 4 | `frontend/src/pages/Pantry.tsx` | F7: removed 3 local onError; undo now uses showApiError |
|
||
| 4 | `frontend/src/pages/MealDetail.tsx` | F7: removed submitMutation.onError |
|
||
| 5 | `backend/app/api/meals.py` | F5: added ?week_start= query param |
|
||
| 5 | `backend/app/api/shopping_list.py` | F5: added ?week_start= query param |
|
||
| 5 | `backend/alembic/versions/0015_normalize_pantry_aisles.py` | **CRITICAL cast fix** — was blocking Sprint 2 deploy |
|
||
| 5 | `frontend/src/lib/utils.ts` | F5: isoMonday/parseIsoDate/shiftIsoDate/formatIsoDate helpers |
|
||
| 5 | `frontend/src/api/index.ts` | F5: getPlanned/get take weekStart? |
|
||
| 5 | `frontend/src/pages/Dashboard.tsx` | F5: useSearchParams sync, week nav, queryKey includes weekStart |
|
||
| 5 | `frontend/src/pages/ShoppingList.tsx` | F5: same URL sync as Dashboard |
|
||
| 5 | `frontend/src/hooks/useKeyboardShortcuts.ts` | **NEW** — F2 global handler with vim-style sequences |
|
||
| 5 | `frontend/src/hooks/useFocusSearch.ts` | **NEW** — F2 focus-search CustomEvent bus |
|
||
| 5 | `frontend/src/components/ShortcutHelpBanner.tsx` | **NEW** — F2 help dialog |
|
||
| 5 | `frontend/src/App.tsx` | F2: mount GlobalShortcuts + ShortcutHelpBanner |
|
||
| 5 | `frontend/src/pages/Pantry.tsx` | F2: useFocusSearchOnShortcut on search input |
|
||
| 5 | `frontend/src/pages/Recipes.tsx` | F2: useFocusSearchOnShortcut on search input |
|
||
| 6 | `backend/app/api/pantry.py` | F3: POST /api/pantry/bulk with per-item upsert |
|
||
| 6 | `backend/app/api/meals.py` | F4: POST /api/meals/{id}/fill-empty-slots with partial-success result |
|
||
| 6 | `backend/app/schemas/__init__.py` | F3+F4: HomePantryBulkCreate, HomePantryBulkResult, FillEmptySlotsRequest, FillEmptySlotsResult, FilledSlot, FailedSlot |
|
||
| 6 | `frontend/src/api/index.ts` | F3: pantry.addBulk; F4: meals.fillEmptySlots |
|
||
| 6 | `frontend/src/pages/ShoppingList.tsx` | F3: 'Add N to pantry' button + addCheckedToPantry handler |
|
||
| 6 | `frontend/src/pages/Dashboard.tsx` | F4: 'Plan the week' button + Dinners only / All meals dropdown |
|
||
|
||
---
|
||
|
||
## Quick-start for the next agent
|
||
|
||
If you are continuing this work:
|
||
|
||
```bash
|
||
# 1. Get oriented
|
||
cd /home/peter/Projects/MealPlanner
|
||
cat docs/ORIENTATION.md
|
||
cat docs/HANDOFF.md
|
||
cat Review/ui-nielsen-audit.md
|
||
cat fix-ui-audit.md
|
||
|
||
# 2. See what's still in flight
|
||
git log --oneline -10
|
||
git status
|
||
git log origin/main..HEAD --oneline
|
||
|
||
# 3. If the user wants to deploy pending work (Sprints 2-5 as a single batch):
|
||
# - Sprint 2: docker compose exec db psql -U mealplanner -d mealplanner \
|
||
# -f /dev/stdin < backend/scripts/persist_aisle_backup.sql
|
||
# - docker compose exec backend alembic upgrade head
|
||
# - Sprints 3-5: docker compose -f docker-compose.yml up -d --build backend frontend
|
||
# See Review/sprint{2,3,4,5}-verification.md for the full checklist.
|
||
|
||
# 4. If you need to take new screenshots after changes
|
||
# /tmp/opencode/mp-review/shoot-sprint1.js is a working playwright harness.
|
||
# Adapt the URL list. Output: /tmp/opencode/mp-review/screenshots/.
|
||
|
||
# 5. Build check before any commit
|
||
cd frontend && npm run build
|
||
```
|
||
|
||
**When you commit**, follow the existing style:
|
||
- `fix(ui): …` for bug fixes
|
||
- `feat(ui): …` for new behavior
|
||
- `refactor(frontend): …` for restructuring without behavior change
|
||
- `docs(review): …` for doc-only commits
|
||
- Conventional commits, imperative mood, body explaining the why
|
||
|
||
**When you push**, only push UI-audit commits. Pre-existing WIP is the user's to manage.
|
||
|
||
---
|
||
|
||
## Final words
|
||
|
||
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-06** — 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`) committed on 2026-06-05, awaiting user push; Sprint 15 (Seed 50 family-friendly recipes + Sprint 12 latent-bug fix) code complete on 2026-06-06, 18 recipes imported, awaiting user commit + push**. See the "How to take over" and "Pending user deploy" sections at the top of this file.
|