Public Access
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -285,4 +285,58 @@ User reported post-deploy: "The tour window looks great, but Clicking the X nor
|
||||
- A "Manage blocked recipes" page.
|
||||
- Bulk unblock.
|
||||
- Touch-device gesture for the card overlay (the focus state already surfaces the button on tap).
|
||||
|
||||
---
|
||||
|
||||
## Sprint 11 — Wire the dead "Generate Meal Plan" CTA (Dashboard.tsx:499-504)
|
||||
|
||||
**User direction (2026-06-05):** "Proceed." Selected from the question menu as the smallest §Future item. F1 (Sprint 9) is shipped, F8 (Spoonacular) + F9 (Ollama) are full backend proposals, and the dead `Generate Meal Plan` CTA at `Dashboard.tsx:503` is the final remaining item. The button is rendered with `onClick: () => {}` — clicking it does nothing. Wired to existing endpoints, no backend changes, no new dependencies.
|
||||
|
||||
**Root cause:** the user lands on the Dashboard with no meal plan and sees a "Generate Meal Plan" button. 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), and the `fillEmptySlots` partial-success report pattern is already in production for the existing `Plan Week` menu (`handlePlanWeek` at `Dashboard.tsx:366-392`). The wiring is a 25-line client-side glue function that calls both in sequence.
|
||||
|
||||
### S11.1 — `handleGenerateFirstPlan()` in `Dashboard.tsx`
|
||||
|
||||
- [ ] Add a new handler next to `handlePlanWeek` (line 366) that:
|
||||
1. Reads `weekStart` (already in scope).
|
||||
2. `POST /api/meals` with `{ week_start_date: weekStart, status: 'draft' }` to create an empty plan.
|
||||
3. On success, `POST /api/meals/{newId}/fill-empty-slots` with `{ meal_types: ['breakfast', 'lunch', 'dinner'] }`.
|
||||
4. Invalidate `['mealPlan', weekStart]`.
|
||||
5. Toast: reuse the same partial-success pattern as `handlePlanWeek` (`"Planned N of M meals — K failed"`).
|
||||
- [ ] Wire `onClick` of the `EmptyState.action` (line 503) to call `handleGenerateFirstPlan()`.
|
||||
- [ ] Track a `generatingFirstPlan` state for the loading spinner; swap the button label to `"Generating…"` while in-flight.
|
||||
- [ ] Handle the `"Meal plan for this week already exists"` 400 from `meals.create` (race condition with another tab) by calling `fillEmptySlots` directly with the existing plan's id — refetch the plan from `getPlanned(weekStart)` to get the id.
|
||||
|
||||
### S11.2 — Verify
|
||||
|
||||
- [ ] `npm run build` green (tsc 0 errors, vite 0 errors).
|
||||
- [ ] Browser smoke (4 steps) on `http://100.108.208.56:8082/`:
|
||||
1. Log in as a family with no meal plan for the current week. Land on `/`.
|
||||
2. Confirm `EmptyState` shows "Generate Meal Plan" button.
|
||||
3. Click the button. Confirm: button label flips to "Generating…", toast appears with "Planned N of M meals", empty state disappears, plan grid renders.
|
||||
4. Refresh the page. Confirm the plan persists.
|
||||
- [ ] Race test: open two tabs, both click "Generate Meal Plan" at the same moment. Second tab should still succeed (handled by the `meals.create` 400 → fall-through to `fillEmptySlots` path).
|
||||
- [ ] No regression in Sprints 1-10.
|
||||
|
||||
### S11.3 — Docs (all 6 running docs updated)
|
||||
|
||||
- [ ] `Review/ui-nielsen-audit.md` — Sprint 11 status block at the top.
|
||||
- [ ] `fix-ui-audit.md` — Sprint 11 plan section (T5.1-T5.3).
|
||||
- [ ] `Review/handoff-ui-audit.md` — Sprint 11 entry in the "How to take over" section + TL;DR row.
|
||||
- [ ] `docs/HANDOFF.md` — Sprint 11 section.
|
||||
- [ ] `.agent/plan.md` — this section.
|
||||
- [ ] `.agent/context.md` — Sprint 11 decisions + file:line references.
|
||||
- [ ] `Review/sprint11-verification.md` — written (4-step browser smoke + race test).
|
||||
|
||||
### Done when (Sprint 11)
|
||||
|
||||
- All boxes above ticked.
|
||||
- `npm run build` green.
|
||||
- `Review/sprint11-verification.md` exists.
|
||||
- All 6 doc files have a Sprint 11 status block.
|
||||
|
||||
### Out of scope (Sprint 11)
|
||||
|
||||
- LLM-powered generation (F8 Spoonacular, F9 Ollama) — separate backend proposals, future sprints. Sprint 11 only wires the existing recipe-library-based fill.
|
||||
- A "what would you like for dinner?" prompt before generation — the existing flow generates from the library with no user input.
|
||||
- A "regenerate" button after the plan exists — the existing `Plan Week` menu at `Dashboard.tsx:366-392` already handles this case.
|
||||
- F8 Spoonacular + F9 Ollama + dead `Generate Meal Plan` CTA — separate.
|
||||
|
||||
Reference in New Issue
Block a user