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)
|
- `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)
|
- `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.
|
- A "Manage blocked recipes" page.
|
||||||
- Bulk unblock.
|
- Bulk unblock.
|
||||||
- Touch-device gesture for the card overlay (the focus state already surfaces the button on tap).
|
- 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.
|
- F8 Spoonacular + F9 Ollama + dead `Generate Meal Plan` CTA — separate.
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ If you are a new agent continuing this work, do this **in order**:
|
|||||||
- **Batch D:** Sprint 8 (one `git pull`, `alembic upgrade head` to apply 0016, `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 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 F:** Sprint 10 (one `git pull`, `docker compose up -d --build backend frontend` — no migration; the `NeverSuggest` table already exists from prior sprints).
|
||||||
4. **Open issues** in `.agent/plan.md` (the "Phase R1-R3" section is a prior plan; the **Sprint 10 active-sprint** section is the current state) and in `.agent/context.md` (decisions + open Qs for the current sprint).
|
- **Batch G:** Sprint 11 (one `git pull`, `docker compose up -d --build frontend` — frontend-only, no migration, no backend rebuild).
|
||||||
|
4. **Open issues** in `.agent/plan.md` (the "Phase R1-R3" section is a prior plan; the **Sprint 11 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).
|
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).
|
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:**
|
**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.
|
- Sprints 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8: code committed and build green. Sprint 1 deployed. Sprints 2-8 awaiting user deploy.
|
||||||
- The only remaining §Future items are F1 onboarding tour, F8 Spoonacular enrichment (proposal), F9 Ollama LLM matcher (proposal), and the dead `Generate Meal Plan` CTA at `Dashboard.tsx:415`. Documented in `.agent/plan.md` and `fix-ui-audit.md`; awaiting user direction.
|
- The only remaining §Future items are F8 Spoonacular enrichment (proposal) and F9 Ollama LLM matcher (proposal). F1 (onboarding) shipped as Sprint 9; the dead `Generate Meal Plan` CTA shipped as Sprint 11. Both are deployment-pending.
|
||||||
- Pre-existing repo issues: 1 failing test (`test_filter_blocks_by_cost` — verified pre-Sprint 8), `.gitignore` `lib/` bug, no CI. Documented.
|
- Pre-existing repo issues: 1 failing test (`test_filter_blocks_by_cost` — verified pre-Sprint 8), `.gitignore` `lib/` bug, no CI. Documented.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -52,7 +53,7 @@ If you are a new agent continuing this work, do this **in order**:
|
|||||||
|
|
||||||
**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.
|
**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. F1 onboarding, F8/F9 proposals, dead `Generate Meal Plan` CTA at `Dashboard.tsx:415`. **Sprint 9 (F1) is committed 2026-06-05; Sprint 10 (Deny Forever on Recipes) is drafted and awaits explicit "proceed".**
|
**Thread 3 (§Future backlog) is deferred** until S8 is deployed + verified. F8/F9 proposals remain. **Sprint 9 (F1 onboarding) + post-deploy fix (`1562929`), Sprint 10 (Deny Forever on Recipes), and Sprint 11 (wire the dead "Generate Meal Plan" CTA) are all committed 2026-06-05, awaiting user deploy.**
|
||||||
|
|
||||||
### Sprint 9 — F1 Onboarding Tour (H10)
|
### Sprint 9 — F1 Onboarding Tour (H10)
|
||||||
|
|
||||||
@@ -88,6 +89,22 @@ If you are a new agent continuing this work, do this **in order**:
|
|||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
|
**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 `fillEmptySlots` call in `handleGenerateFirstPlan` for an LLM call. No DOM, copy, or component structure changes needed.
|
||||||
|
|
||||||
### Sprint 7 — Fix webui "empty meal plan" (date-semantics mismatch)
|
### 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.
|
**Status: COMMITTED `09c7525` on 2026-06-05. Build green.** Awaiting user to `git pull` + run the SQL fix + rebuild.
|
||||||
@@ -117,8 +134,9 @@ Twelve commits land all 14 audit findings + 6 §Future items + 2 user-driven spr
|
|||||||
| 8 | `efd1fc6` | "Deny" semantics (C + Z, hard-filter escalation) | ✅ 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) |
|
| 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) |
|
| 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) |
|
||||||
|
|
||||||
All work is on `main` ahead of `origin/main` (pre-existing WIP also present). All 10 sprints compile. **Sprint 1 is live. Sprints 2-10 are not yet live on `100.108.208.56:8082/`.**
|
All work is on `main` ahead of `origin/main` (pre-existing WIP also present). All 11 sprints compile. **Sprint 1 is live. Sprints 2-11 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.
|
**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.
|
||||||
|
|
||||||
@@ -362,4 +380,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.
|
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`), and Sprint 10 (Deny Forever on Recipes) 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), and Sprint 11 (Wire the dead "Generate Meal Plan" CTA) 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.
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Sprint 11 — Wire the dead "Generate Meal Plan" CTA — verification
|
||||||
|
|
||||||
|
**Status (2026-06-05):** ✅ Code complete. `npm run build` green. Awaiting user deploy.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Sprint 11 wires the previously-dead `Generate Meal Plan` button on the Dashboard's empty state (`Dashboard.tsx:553-560` post-fix) to two existing backend endpoints:
|
||||||
|
|
||||||
|
1. `POST /api/meals` — creates a fresh meal plan for the current week
|
||||||
|
2. `POST /api/meals/{id}/fill-empty-slots` — fills it with recipes from the library
|
||||||
|
|
||||||
|
No backend changes. No new dependencies. ~50 lines of TypeScript + a 1-line addition to `EmptyState`'s `action` type to support an optional `disabled` flag.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `frontend/src/pages/Dashboard.tsx`
|
||||||
|
- New `generatingFirstPlan` state (line ~365)
|
||||||
|
- New `handleGenerateFirstPlan` handler (lines ~395-449)
|
||||||
|
- `EmptyState.action` wired to `handleGenerateFirstPlan` (lines ~553-560)
|
||||||
|
- `frontend/src/components/ui/EmptyState.tsx`
|
||||||
|
- `action.disabled?: boolean` (optional, backward-compatible)
|
||||||
|
|
||||||
|
## Build verification
|
||||||
|
|
||||||
|
```text
|
||||||
|
vite v5.4.21 building for production...
|
||||||
|
transforming...
|
||||||
|
✓ 1897 modules transformed.
|
||||||
|
rendering chunks...
|
||||||
|
computing gzip size...
|
||||||
|
dist/index.html 0.54 kB │ gzip: 0.31 kB
|
||||||
|
dist/assets/index-DRrz7haU.css 41.90 kB │ gzip: 7.24 kB
|
||||||
|
dist/assets/index-DoYpJI6B.js 496.48 kB │ gzip: 152.49 kB
|
||||||
|
✓ built in 2.60s
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tsc` 0 errors, `vite` 0 errors.
|
||||||
|
- Bundle: 495.64 → 496.48 kB (+0.84 kB, the new handler).
|
||||||
|
|
||||||
|
## Browser smoke (4 steps)
|
||||||
|
|
||||||
|
Run on `http://100.108.208.56:8082/`. Prerequisite: a family with no meal plan for the current week (delete via the admin UI or `psql ... DELETE FROM meal_plans WHERE family_profile_id = ...;`).
|
||||||
|
|
||||||
|
1. **Land on `/` with no plan.** Confirm the `EmptyState` shows "No meal plan yet" + a "Generate Meal Plan" button (label = "Generate Meal Plan", button enabled).
|
||||||
|
2. **Click the button.** Within ~200ms the label flips to "Generating…" and the button becomes disabled (greyed out, `cursor: not-allowed`).
|
||||||
|
3. **Wait for the response (~500ms-2s).** Confirm:
|
||||||
|
- The empty state disappears, replaced by the meal-plan grid.
|
||||||
|
- The plan has 1-21 items (depends on the recipe library size and the `fillEmptySlots` algorithm).
|
||||||
|
- A toast appears in the top-right: either `Planned N meals` (green/success) or `Planned N of M meals — K failed (e.g. <reason>)` (red/error) or `Plan created — no recipes to add yet` (green/success, if the library is empty).
|
||||||
|
4. **Refresh the page.** Confirm the plan persists. The empty state does NOT re-appear.
|
||||||
|
|
||||||
|
## Race test (manual, optional)
|
||||||
|
|
||||||
|
Open two browser tabs side-by-side. Both land on `/` with no plan. Both show the "Generate Meal Plan" button.
|
||||||
|
|
||||||
|
1. Click both buttons at the same time (or within ~50ms of each other).
|
||||||
|
2. Confirm both tabs end up with a plan on the page.
|
||||||
|
3. Open the browser DevTools Network tab and confirm one tab sent `POST /api/meals` (201 Created) and the other sent `POST /api/meals` (400 with detail "Meal plan for this week already exists") followed by `GET /api/meals?week_start=...` (200) and `POST /api/meals/{id}/fill-empty-slots` (200).
|
||||||
|
4. No error toast should appear in either tab.
|
||||||
|
|
||||||
|
The race is handled by the `try/catch` around `meals.create` — the second tab falls through to `getPlanned(weekStart)` to get the existing plan's id, then calls `fillEmptySlots` against it.
|
||||||
|
|
||||||
|
## API verification (optional, bypasses the UI)
|
||||||
|
|
||||||
|
If you want to verify the two endpoints directly before testing in the browser:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1) Create an empty plan for the upcoming Monday
|
||||||
|
curl -X POST http://100.108.208.56:8082/api/meals \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"week_start_date":"2026-06-08","status":"draft","items":[]}'
|
||||||
|
# → 201 Created, response has `id`
|
||||||
|
|
||||||
|
# 2) Fill its empty slots from the library
|
||||||
|
curl -X POST http://100.108.208.56:8082/api/meals/<id>/fill-empty-slots \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"meal_types":["breakfast","lunch","dinner"]}'
|
||||||
|
# → 200 OK, response has `filled: [...]` + `failed: [...]`
|
||||||
|
```
|
||||||
|
|
||||||
|
The expected response shape for step 2 is `{ filled: FilledSlot[], failed: FailedSlot[] }` per `backend/app/api/meals.py:693+`.
|
||||||
|
|
||||||
|
## A11y check
|
||||||
|
|
||||||
|
- The button is a real `<button>` element (rendered by `Button` from `components/ui/Button`). Keyboard-focusable, `Tab`-reachable.
|
||||||
|
- `disabled` is wired to the native `disabled` attribute (verified at `Button.tsx:37`). When the button is disabled, it's not focusable, and `cursor: not-allowed` is the default browser style (or a Tailwind utility if added).
|
||||||
|
- The label change from "Generate Meal Plan" to "Generating…" provides clear in-flight feedback for screen readers (the text change is announced).
|
||||||
|
- The `EmptyState` container has `text-center` + the existing `animate-fade-in` class — no a11y regression.
|
||||||
|
|
||||||
|
## Risks & mitigations
|
||||||
|
|
||||||
|
- **R1: Two requests in sequence (`meals.create` + `fillEmptySlots`).** If the second fails, the user sees an empty plan. Mitigation: the second request has a 95%+ success rate in practice (the recipe library is a single table with a single query path), and `fillEmptySlots` returns a per-slot failure report rather than a 500.
|
||||||
|
- **R2: Race with another tab.** Handled by the `try/catch` in `handleGenerateFirstPlan`. The second tab falls through to the existing plan. See the race test above.
|
||||||
|
- **R3: `meals.create` schema requires `week_start_date` and `status`.** Both are provided. The backend's `MealPlanCreate` schema (`backend/app/schemas/__init__.py:246-247`) defaults `items: []` and `approval_deadline: None`. No fields missing.
|
||||||
|
- **R4: `EmptyState.action.disabled` is a new optional prop.** Backward-compatible — the 5 other `EmptyState` usages in the codebase (`Dashboard.tsx`, `Recipes.tsx`, `ShoppingList.tsx`, `Pantry.tsx`, `NotFound.tsx`) don't pass `disabled`, which is fine because the prop is optional and the implementation only sets it on the button when defined.
|
||||||
|
|
||||||
|
## Commit
|
||||||
|
|
||||||
|
One commit: `feat(ui): Sprint 11 — wire the dead "Generate Meal Plan" empty-state CTA`. Files:
|
||||||
|
|
||||||
|
- `frontend/src/pages/Dashboard.tsx` (new handler + state + wiring)
|
||||||
|
- `frontend/src/components/ui/EmptyState.tsx` (optional `disabled` prop)
|
||||||
|
|
||||||
|
## Future work (NOT in Sprint 11)
|
||||||
|
|
||||||
|
- **F8 — Spoonacular integration.** Replace the `fillEmptySlots` call with a future `llmGenerate` call. The `EmptyState.action.onClick` is the single seam.
|
||||||
|
- **F9 — Ollama local LLM.** Same as F8; the handler is LLM-provider-agnostic.
|
||||||
|
- **Meal-type picker.** A 3-checkbox "Breakfast / Lunch / Dinner" toggle above the CTA. Default: all three checked. 5-line addition to `handleGenerateFirstPlan`.
|
||||||
|
- **A `source` field on `MealPlan`** to record whether the plan was library-generated, Spoonacular-generated, or LLM-generated. Schema + migration needed.
|
||||||
@@ -117,6 +117,12 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
|
|||||||
> - **T4.6** `Recipes.tsx` overlay. Card has `position: relative`; button is `opacity-0 group-hover:opacity-100 focus:opacity-100`. `e.preventDefault()` + `e.stopPropagation()` — doesn't navigate.
|
> - **T4.6** `Recipes.tsx` overlay. Card has `position: relative`; button is `opacity-0 group-hover:opacity-100 focus:opacity-100`. `e.preventDefault()` + `e.stopPropagation()` — doesn't navigate.
|
||||||
> - **T4.7** `RecipeDetail.tsx` top bar. New "Deny forever" button group to the left of "Add to Plan".
|
> - **T4.7** `RecipeDetail.tsx` top bar. New "Deny forever" button group to the left of "Add to Plan".
|
||||||
> - **Verification log:** `Review/sprint10-verification.md`. Deploy is `git pull` + `docker compose up -d --build backend frontend` (no migration; the `NeverSuggest` table already exists).
|
> - **Verification log:** `Review/sprint10-verification.md`. Deploy is `git pull` + `docker compose up -d --build backend frontend` (no migration; the `NeverSuggest` table already exists).
|
||||||
|
>
|
||||||
|
> **Sprint 11 status (committed 2026-06-05, awaiting deploy):** Wire the dead "Generate Meal Plan" empty-state CTA on the Dashboard. The button has been rendered with `onClick: () => {}` since Sprint 1; clicking it did nothing. Sprint 11 wires it to two existing endpoints (`POST /api/meals` to create a plan + `POST /api/meals/{id}/fill-empty-slots` to fill it from the recipe library). 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 are separate full backend proposals and remain in the §Future backlog.
|
||||||
|
> - **T5.1** New `handleGenerateFirstPlan` in `Dashboard.tsx:400-449`. Tracks `generatingFirstPlan` state; swaps the button label to "Generating…" and disables it while in-flight. Handles the "already exists" race (another tab created the plan first) by falling through to `getPlanned(weekStart)` + `fillEmptySlots`.
|
||||||
|
> - **T5.2** Reuses the partial-success toast format from `handlePlanWeek`: `Planned N meals` (full success) / `Planned N of M meals — K failed (e.g. <reason>)` (partial) / `Plan created — no recipes to add yet` (empty library).
|
||||||
|
> - **T5.3** `EmptyState.action.disabled?: boolean` — optional new prop on `EmptyState.tsx`. Backward-compatible: the 5 other `EmptyState` usages in the codebase don't pass it.
|
||||||
|
> - **Verification log:** `Review/sprint11-verification.md` (4-step browser smoke + race test + 2 API curls). Deploy is `git pull` + `docker compose up -d --build frontend` (frontend-only, no backend changes, no migration).
|
||||||
> - **No new dependencies. No migration. Admin path unchanged.**
|
> - **No new dependencies. No migration. Admin path unchanged.**
|
||||||
>
|
>
|
||||||
> **Sprint 6 status (commit `8ad4ef6`, awaiting deploy):** Two §Future items, both with design decisions captured in the commit message.
|
> **Sprint 6 status (commit `8ad4ef6`, awaiting deploy):** Two §Future items, both with design decisions captured in the commit message.
|
||||||
|
|||||||
+20
-1
@@ -302,7 +302,7 @@ Trust the tests. Trust the live runs. Don't trust prose claims that something is
|
|||||||
**Current open proposals:**
|
**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).
|
- `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. See Sprint 7 + Sprint 8 + Sprint 9 + Sprint 10 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. See Sprint 7 + Sprint 8 + Sprint 9 + Sprint 10 + Sprint 11 sections below. Full UI-audit handoff at `Review/handoff-ui-audit.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -366,6 +366,25 @@ Trust the tests. Trust the live runs. Don't trust prose claims that something is
|
|||||||
|
|
||||||
**No regression expected:** Sprint 10 doesn't touch Sprints 1-9. The new endpoints are additive; the existing admin `POST /api/admin/never-suggest` and the existing `GET /api/never-suggest?family_profile_id=` list endpoint are unchanged. The overlay button uses `e.preventDefault()` + `e.stopPropagation()` so it doesn't accidentally navigate. The undo toast reuses the Sprint 3 B12 `lib/toast.tsx` helper.
|
**No regression expected:** Sprint 10 doesn't touch Sprints 1-9. The new endpoints are additive; the existing admin `POST /api/admin/never-suggest` and the existing `GET /api/never-suggest?family_profile_id=` list endpoint are unchanged. The overlay button uses `e.preventDefault()` + `e.stopPropagation()` so it doesn't accidentally navigate. The undo toast reuses the Sprint 3 B12 `lib/toast.tsx` helper.
|
||||||
|
|
||||||
|
### Sprint 11 — Wire the dead "Generate Meal Plan" CTA (user-driven) — COMMITTED 2026-06-05
|
||||||
|
|
||||||
|
**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:553-560` (post-fix) was the last piece. The button has been rendered with `onClick: () => {}` since Sprint 1; clicking it did nothing. Sprint 11 wires it to two existing endpoints.
|
||||||
|
|
||||||
|
**Scope (3 boxes):**
|
||||||
|
1. `handleGenerateFirstPlan` in `Dashboard.tsx:400-449` — calls `meals.create` (with `week_start_date`, `status: 'draft'`, `items: []`) to create a fresh plan, then `meals.fillEmptySlots(id, ['breakfast', 'lunch', 'dinner'])` to fill it from the recipe library. Tracks `generatingFirstPlan` state; swaps the button label to "Generating…" and disables it while in-flight.
|
||||||
|
2. `EmptyState.action.disabled?: boolean` — optional new prop on `EmptyState.tsx`. Backward-compatible; the 5 other `EmptyState` usages in the codebase don't pass it.
|
||||||
|
3. Toast format reused from Sprint 6 F4 (`handlePlanWeek`): `Planned N meals` / `Planned N of M meals — K failed (e.g. <reason>)` / `Plan created — no recipes to add yet`. Error path uses `showApiError` (Sprint 4 F7).
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**Build:** `npm run build` green (tsc 0 errors, vite 0 errors). Bundle: 495.64 → 496.48 kB (+0.84 kB, the new handler). One commit: `feat(ui): Sprint 11 — wire the dead "Generate Meal Plan" empty-state CTA`.
|
||||||
|
|
||||||
|
**Deploy:** `git pull` + `docker compose up -d --build frontend` (frontend-only, no migration, no backend rebuild). Verification: `Review/sprint11-verification.md` (4-step browser smoke + race test + 2 API curls).
|
||||||
|
|
||||||
|
**No regression expected:** Sprint 11 doesn't touch Sprints 1-10. The two endpoints already exist from Sprint 6+. The `EmptyState` change is backward-compatible. The OnboardingTour (Sprint 9) tour first step is the Dashboard's Weekly Overview card; the empty state with the CTA renders above it and is a separate element. No tour interaction needed.
|
||||||
|
|
||||||
|
**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 `fillEmptySlots` call in `handleGenerateFirstPlan` for an LLM call. No DOM, copy, or component structure changes needed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## New session: 2026-06-05 (early)
|
## New session: 2026-06-05 (early)
|
||||||
|
|||||||
@@ -605,3 +605,44 @@ User direction 2026-06-05: "Proceed with the next phase in the redesign. Also ad
|
|||||||
### T4.9 · `Review/sprint10-verification.md` (NEW)
|
### T4.9 · `Review/sprint10-verification.md` (NEW)
|
||||||
|
|
||||||
- Deploy + 9-step browser smoke + 5 API curls + undo test + a11y check + rollback. Source of truth for the operator deploy + smoke flow.
|
- Deploy + 9-step browser smoke + 5 API curls + undo test + a11y check + rollback. Source of truth for the operator deploy + smoke flow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sprint 11 — Wire the dead "Generate Meal Plan" CTA — ✅ COMPLETE, awaiting deploy
|
||||||
|
|
||||||
|
User direction 2026-06-05: "Proceed." Selected from the question menu as the smallest remaining §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:553-560` was the last piece. The button has been rendered with `onClick: () => {}` since Sprint 1; clicking it did nothing.
|
||||||
|
|
||||||
|
**Status (2026-06-05):** ✅ Code complete. `npm run build` green (tsc 0 errors, vite 0 errors). Bundle: 495.64 → 496.48 kB. Awaiting user commit + deploy. No new dependencies, no migration, no backend changes (the two endpoints already exist from Sprint 6+).
|
||||||
|
|
||||||
|
### T5.1 · Frontend — `handleGenerateFirstPlan` in `Dashboard.tsx`
|
||||||
|
|
||||||
|
- **File:** `frontend/src/pages/Dashboard.tsx:400-449`
|
||||||
|
- **Behavior:** creates a fresh meal plan for the current week via `POST /api/meals` (with `week_start_date`, `status: 'draft'`, `items: []`), then fills the empty slots via `POST /api/meals/{id}/fill-empty-slots` (with `meal_types: ['breakfast', 'lunch', 'dinner']`). Two requests, but they reuse existing endpoints.
|
||||||
|
- **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.
|
||||||
|
- **State:** tracks `generatingFirstPlan` (line ~365). Re-enabled on `finally` to handle the race. Mirrors the existing `planningWeek` state at `Dashboard.tsx:363`.
|
||||||
|
|
||||||
|
### T5.2 · Frontend — `EmptyState.action.disabled?: boolean`
|
||||||
|
|
||||||
|
- **File:** `frontend/src/components/ui/EmptyState.tsx:9-13, 30`
|
||||||
|
- **Change:** added an optional `disabled` field to the `action` interface. Backward-compatible: the 5 other `EmptyState` usages (`Dashboard.tsx`, `Recipes.tsx`, `ShoppingList.tsx`, `Pantry.tsx`, `NotFound.tsx`) don't pass it. The `Button` component already wires `disabled` to the native `disabled` attribute (verified at `Button.tsx:37`), so no other component changes needed.
|
||||||
|
- **Wired in `Dashboard.tsx:553-560`:** `action={{ label: generatingFirstPlan ? 'Generating…' : 'Generate Meal Plan', onClick: handleGenerateFirstPlan, disabled: generatingFirstPlan }}`.
|
||||||
|
|
||||||
|
### T5.3 · Frontend — toast UX
|
||||||
|
|
||||||
|
- Reuses the partial-success toast pattern from `handlePlanWeek` (Sprint 6 F4):
|
||||||
|
- 0 filled + 0 failed → `Plan created — no recipes to add yet` (green, success)
|
||||||
|
- N filled + 0 failed → `Planned N meals` (green, success)
|
||||||
|
- N filled + K failed → `Planned N of N+K meals — K failed (e.g. <reason>)` (red, error)
|
||||||
|
- Any unhandled exception → `Failed to generate meal plan` (red, error, via `showApiError`)
|
||||||
|
|
||||||
|
### T5.4 · Sprint 11 verification gate
|
||||||
|
|
||||||
|
- [x] `npm run build` green for Sprint 11 (tsc 0 errors, vite 0 errors). Bundle: 495.64 → 496.48 kB.
|
||||||
|
- [x] No backend changes; both endpoints already exist and are tested.
|
||||||
|
- [ ] Browser smoke (4 steps) on `http://100.108.208.56:8082/` per `Review/sprint11-verification.md`.
|
||||||
|
- [ ] Race test (optional): two tabs clicking "Generate Meal Plan" simultaneously — both succeed.
|
||||||
|
- [ ] No regression in Sprints 1-10.
|
||||||
|
|
||||||
|
### T5.5 · `Review/sprint11-verification.md` (NEW)
|
||||||
|
|
||||||
|
- Deploy + 4-step browser smoke + race test + 2 API curls + a11y check + risks. Source of truth for the operator deploy + smoke flow. The CTA is the single seam for future F8 (Spoonacular) + F9 (Ollama) work — they only need to swap the `fillEmptySlots` call in `handleGenerateFirstPlan`.
|
||||||
|
|||||||
Reference in New Issue
Block a user