docs: Sprint 11 — wire the dead "Generate Meal Plan" CTA across all 6 running docs

Sprint 11 (commit 41154e9) wires the previously-dead
"Generate Meal Plan" empty-state CTA on the Dashboard to two
existing endpoints (POST /api/meals + POST /api/meals/{id}/fill-
empty-slots). No backend changes; no new dependencies. The
handler lives on the client for now; future F8 (Spoonacular) +
F9 (Ollama) will swap the fillEmptySlots call for an LLM call
without changing the DOM. F8 + F9 remain in the §Future backlog.

This commit updates the 6 running docs that track the sprint:

- .agent/plan.md — Sprint 11 section (S11.1-S11.3) added.
- .agent/context.md — Sprint 11 (D1-D6, Q1-Q3) added; file:line
  references; key takeaways.
- Review/sprint11-verification.md — new file: 4-step browser
  smoke + race test + 2 API curls + a11y check + risks + future
  work section.
- Review/ui-nielsen-audit.md — Sprint 11 status block (T5.1-T5.3)
  at the top, after the Sprint 10 block.
- fix-ui-audit.md — Sprint 11 section (T5.1-T5.5) added after the
  Sprint 10 section.
- Review/handoff-ui-audit.md — Batch G added to the deploy
  instructions; Sprint 11 section added after Sprint 10; TL;DR
  table row 11 added; Last-updated footer updated.
- docs/HANDOFF.md — Sprint 11 section added after the Sprint 10
  section, with a path-forward paragraph for F8/F9.

All 6 docs now reflect Sprint 11. §Future backlog remaining: F8
(Spoonacular) + F9 (Ollama) proposals, both full backend work.
This commit is contained in:
2026-06-05 15:36:26 -07:00
parent 41154e934a
commit dac1364c29
7 changed files with 307 additions and 6 deletions
+54
View File
@@ -288,3 +288,57 @@ User direction 2026-06-05: "Proceed with the next phase in the redesign. Also ad
- `frontend/src/pages/RecipeDetail.tsx:73-78` — top bar (Deny forever button group)
- `Review/sprint10-verification.md` — new file (deploy + 9-step browser smoke + 5 API curls + a11y check)
---
# Context — Sprint 11 (Wire the dead "Generate Meal Plan" CTA)
## Why Sprint 11 exists
User direction 2026-06-05: "Proceed." Selected from the question menu as the smallest remaining §Future item. F1 (Sprint 9) shipped, F8 (Spoonacular) + F9 (Ollama) are full backend proposals, and the dead `Generate Meal Plan` CTA at `Dashboard.tsx:503` was the last remaining piece. The button renders with `onClick: () => {}` — clicking it does nothing. The backend already has the two endpoints needed (`POST /api/meals` to create a plan + `POST /api/meals/{id}/fill-empty-slots` to fill it from the recipe library), so the wiring is a 25-line client-side glue function. No backend changes. No new dependencies. F8/F9 remain future sprints that will swap the recipe-library-based fill for an LLM/Spoonacular-based generation.
## Decisions (locked in for Sprint 11)
- **D1. Wire to existing endpoints, no new backend route.** `POST /api/meals` (creates an empty plan) + `POST /api/meals/{id}/fill-empty-slots` (fills with library recipes). The `fillEmptySlots` partial-success report pattern is already in production for the existing `Plan Week` menu at `Dashboard.tsx:366-392`. Reusing the same toast messaging keeps the UX consistent.
- **D2. Client-side orchestration, not a new server endpoint.** A combined `POST /api/meals/generate` endpoint would be cleaner long-term (atomic, single source of truth for "this is how a meal plan is generated"), but it would duplicate `fillEmptySlots` logic and lock in a generation strategy before F8/F9 are decided. Keeping the orchestration on the client means F8/F9 only need to swap the `fillEmptySlots` call for a future `LLMGenerate` call.
- **D3. Handle the "already exists" race.** Two tabs clicking "Generate Meal Plan" at the same moment: the second `meals.create` returns 400 with `detail: "Meal plan for this week already exists"`. Fall through to `getPlanned(weekStart)` to get the existing plan id, then call `fillEmptySlots` against it. Same end result, no error toast.
- **D4. Reuse the partial-success toast format from `handlePlanWeek`.** `"Planned N of M meals"` on full success, `"Planned N of M — K failed (e.g. <reason>)"` on partial, `"No empty meals to fill"` on 0/0. The user already knows this toast shape.
- **D5. Track `generatingFirstPlan` state.** Swap the button label to `"Generating…"` and disable it while in-flight, matching the existing `planningWeek` state pattern at `Dashboard.tsx:363`.
- **D6. Path forward to F8/F9: the `EmptyState.action.onClick` is the single seam.** Future F8 (Spoonacular) or F9 (Ollama) work only needs to swap the function called by `onClick`. No DOM, copy, or component structure changes needed.
## Open questions to surface to the user, not to assume
- **Q1. Should the empty state show a meal-type picker ("Breakfast / Lunch / Dinner" toggles) before generating, or always generate all three?** Default: always generate all three (matching the existing `Plan Week` menu default). Surfacing a picker adds 3 checkboxes and a "Generate N meals" button; small but a separate UI decision. If you want it, it's a 5-line addition to `handleGenerateFirstPlan`.
- **Q2. Should the CTA be hidden entirely if the recipe library is empty?** Default: show it, and let it fail gracefully. The backend's `fillEmptySlots` returns `failed=[]` for every slot with reason "No recipes available" when the library is empty. The UI toast surfaces this. A library-empty case is rare in practice (admin seeds the library), and hiding the button would leave the user with no path forward.
- **Q3. Should the path forward to F8/F9 add a `source: 'library' | 'spoonacular' | 'ollama'` field to the meal plan to record which strategy was used?** Default: no. The current `MealPlan` table has no such field. Adding it is a Sprint 12+ change if F8/F9 ship.
## Sprint 11 verification gate
- `cd frontend && npm run build` → green (tsc 0 errors, vite 0 errors)
- Browser smoke (4 steps) on `http://100.108.208.56:8082/` per `Review/sprint11-verification.md`
- Race test: two tabs clicking "Generate Meal Plan" simultaneously — both succeed
- No regression in Sprints 1-10
## Sprint 11 — does NOT touch
- The OnboardingTour (Sprint 9) — unchanged. The tour's first step is the Dashboard's Weekly Overview card (`Dashboard.tsx:602`); the empty state with the CTA renders above the card and is a different element. No tour interaction needed.
- The NeverSuggestButton (Sprint 10) — unchanged.
- The 3-button Sprint 8 voting row — unchanged.
- The WeekRangeNav (Sprint 7) — unchanged.
- The bulk pantry add (Sprint 6 F3) — unchanged.
- The existing `handlePlanWeek` (Sprint 6 F4) — unchanged. That fills empty slots in an *existing* plan. Sprint 11 is the create-then-fill path.
- The keyboard shortcuts (Sprint 5 F2) — unchanged.
- The error toast / `showApiError` flow (Sprint 4 F7) — used for the error path; unchanged.
- Pre-existing WIP: `backend/app/api/recipes.py`, `backend/app/schemas/recipe.py`, `nginx/nginx.conf` — untouched.
- Backend code: no changes. The two endpoints already exist and are well-tested.
## Key file:line references (Sprint 11)
- `frontend/src/pages/Dashboard.tsx:366-392` — existing `handlePlanWeek` (model for the new handler)
- `frontend/src/pages/Dashboard.tsx:499-504``EmptyState` with the dead CTA (target)
- `frontend/src/pages/Dashboard.tsx:393-396``useQuery` for `['mealPlan', weekStart]` (invalidation target)
- `frontend/src/api/index.ts:38-65``meals` API client (already has `create` + `fillEmptySlots`)
- `backend/app/api/meals.py:159-195``POST /api/meals` (create)
- `backend/app/api/meals.py:693+``POST /api/meals/{id}/fill-empty-slots`
- `backend/app/schemas/__init__.py:227-247``MealPlanBase` + `MealPlanCreate` schemas
- `Review/sprint11-verification.md` — new file (deploy + 4-step browser smoke + race test)