Public Access
docs(review): Sprint 6 verification log + plan/handoff/audit updates
Sprint 6 (F3 + F4) is now documented across the project: - Review/sprint6-verification.md: new deploy + smoke-check doc. Backend + frontend deploy (no migration). 5 smoke-check sections: A) ShoppingList bulk-add end-to-end, B) F3 partial- failure edge case, C) F4 'Plan the week' button + dropdown, D) F4 edge cases (no empty slots, all recipes used, invalid meal_types), E) Sprints 1-5 regression spot-check. Rollback section covers revert (no migration to undo). - fix-ui-audit.md: new Sprint 6 section (S6.1 F3, S6.2 F4, S6.3 verification gate). 'Done when' block updated to 6 sprints / 10 commits / 20 findings closed. - Review/handoff-ui-audit.md: updated to a 6-sprint cycle. TL;DR table includes the8ad4ef6row. File list includes sprint6- verification.md. File-level diff summary gains 8 new rows for Sprint 6 (F3 backend + F4 backend + 3 new schemas + 2 api bindings + 2 page changes). §Future list now strikethroughs F3 and F4. Follow-up tickets section added: the no-op 'Generate Meal Plan' empty-state CTA, the (now-narrower) Pantry bulk-add ticket, and the 'Sprints 2-5 + Sprint 6 separate batch' deploy note. - Review/ui-nielsen-audit.md: new Sprint 6 status block at the top. F3 + F4 documented with the design-decision context (ShoppingList-only scope; dropdown for All/Dinners; partial- success with detailed report). - docs/HANDOFF.md: Last-updated line bumped to 6 sprints / 10 commits / 20 findings / 6 §Future items. Header commit list gains the8ad4ef6row. New 'Sprint 6' subsection in the 2026-06-04 session block. Commit table gained the8ad4ef6row. Files-modified + Files-added lists updated. No code changes; the 3 pre-existing WIP files (backend/app/api/ recipes.py, schemas/recipe.py, nginx/nginx.conf) are deliberately not staged.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# UI/UX Audit & Fix — Agent Handoff
|
# UI/UX Audit & Fix — Agent Handoff
|
||||||
|
|
||||||
You are taking over a 5-sprint UI/UX audit and fix cycle. All code changes are 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.
|
You are taking over a 6-sprint UI/UX audit and fix cycle. All code changes are 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-04.**
|
**Date of handoff: 2026-06-04.**
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ You are taking over a 5-sprint UI/UX audit and fix cycle. All code changes are c
|
|||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
Nine commits land all 14 audit findings + 4 of the §Future items:
|
Ten commits land all 14 audit findings + 6 of the §Future items:
|
||||||
|
|
||||||
| Sprint | Commit | Scope | Build | Deploy |
|
| Sprint | Commit | Scope | Build | Deploy |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
@@ -18,12 +18,13 @@ Nine commits land all 14 audit findings + 4 of the §Future items:
|
|||||||
| 4 | `d71b67a` | F7 global error handler + F6 plan-status aria-label | ✅ green | ⚠️ not yet deployed (frontend-only) |
|
| 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 | `d78bd18` | F5 URL week selector + **CRITICAL 0015 cast fix** | ✅ green | ⚠️ not yet deployed |
|
||||||
| 5 | `f740f40` | F2 keyboard shortcuts + ShortcutHelpBanner | ✅ 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) |
|
||||||
|
|
||||||
All work is on `main` ahead of `origin/main` (pre-existing WIP also present). All five sprints compile. **Sprint 1 is live. Sprints 2, 3, 4, 5 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 six sprints compile. **Sprint 1 is live. Sprints 2, 3, 4, 5, 6 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.
|
||||||
|
|
||||||
**Next action:** the user runs the deploy commands in `Review/sprint2-verification.md`, `Review/sprint3-verification.md`, `Review/sprint4-verification.md`, and `Review/sprint5-verification.md` on the deployment host, then smoke-checks per the checklists. The Sprints 2-5 deploy is a single batch (one `git pull`, one migration, one rebuild). After verification, any remaining items move to the §Future backlog in `fix-ui-audit.md` (F1 onboarding, F3 bulk add, F4 plan-whole-week, F8 Spoonacular enrichment, F9 Ollama matcher).
|
**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`, and `Review/sprint6-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). After verification, any remaining items move to the §Future backlog in `fix-ui-audit.md` (F1 onboarding tour, F8 Spoonacular enrichment, F9 Ollama matcher — F3, F4, F5, F6, F7 now done across Sprints 4-6).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ All work is on `main` ahead of `origin/main` (pre-existing WIP also present). Al
|
|||||||
│ ├── sprint3-verification.md # Deploy + smoke checks for Sprint 3 (frontend only).
|
│ ├── sprint3-verification.md # Deploy + smoke checks for Sprint 3 (frontend only).
|
||||||
│ ├── sprint4-verification.md # Deploy + smoke checks for Sprint 4 (F7+F6, 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).
|
│ ├── 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-claude.md # Prior docs adversarial review (not yours).
|
||||||
│ ├── docs-gpt5.5.md # Prior docs adversarial review.
|
│ ├── docs-gpt5.5.md # Prior docs adversarial review.
|
||||||
│ ├── repo-gpt5.5.md # Prior repo adversarial review.
|
│ ├── repo-gpt5.5.md # Prior repo adversarial review.
|
||||||
@@ -144,17 +146,22 @@ The user's flow is: read the smoke check, run the deploy, click around, mark eac
|
|||||||
|
|
||||||
## What's NOT in scope (per audit §Future)
|
## What's NOT in scope (per audit §Future)
|
||||||
|
|
||||||
F2, F5, F6, and F7 are done in Sprints 4-5. The audit intentionally deferred these. If the user asks, they become a new plan:
|
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)
|
- F1. Onboarding hints / tour (H10)
|
||||||
- ~~F2. Keyboard shortcuts (`/`, `g p`, `g s`, `n m`)~~ — done in Sprint 5
|
- ~~F2. Keyboard shortcuts (`/`, `g p`, `g s`, `n m`)~~ — done in Sprint 5
|
||||||
- F3. Bulk add on Pantry/Shopping List (H7)
|
- ~~F3. Bulk add on Pantry/Shopping List (H7)~~ — done in Sprint 6 (ShoppingList only)
|
||||||
- F4. Plan-the-whole-week button (H7)
|
- ~~F4. Plan-the-whole-week button (H7)~~ — done in Sprint 6
|
||||||
- ~~F5. Persistent week selector in URL~~ — done in Sprint 5
|
- ~~F5. Persistent week selector in URL~~ — done in Sprint 5
|
||||||
- ~~F6. Badge a11y for color-only signals (generalized)~~ — done in Sprint 4
|
- ~~F6. Badge a11y for color-only signals (generalized)~~ — done in Sprint 4
|
||||||
- ~~F7. Global `react-query` `onError` toast handler~~ — done in Sprint 4
|
- ~~F7. Global `react-query` `onError` toast handler~~ — done in Sprint 4
|
||||||
- F8. Backend Spoonacular enrichment (separate proposal in `docs/proposals/`)
|
- F8. Backend Spoonacular enrichment (separate proposal in `docs/proposals/`)
|
||||||
- F9. Ollama LLM matcher (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)
|
## File-level diff summary (audit work only)
|
||||||
@@ -201,6 +208,12 @@ F2, F5, F6, and F7 are done in Sprints 4-5. The audit intentionally deferred the
|
|||||||
| 5 | `frontend/src/App.tsx` | F2: mount GlobalShortcuts + ShortcutHelpBanner |
|
| 5 | `frontend/src/App.tsx` | F2: mount GlobalShortcuts + ShortcutHelpBanner |
|
||||||
| 5 | `frontend/src/pages/Pantry.tsx` | F2: useFocusSearchOnShortcut on search input |
|
| 5 | `frontend/src/pages/Pantry.tsx` | F2: useFocusSearchOnShortcut on search input |
|
||||||
| 5 | `frontend/src/pages/Recipes.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 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Sprint 6 — Deploy & smoke-check (F3 + F4)
|
||||||
|
|
||||||
|
**Goal:** verify the bulk pantry add (F3) and the plan-the-week button (F4) work end-to-end on `http://100.108.208.56:8082/`.
|
||||||
|
|
||||||
|
**Commit:** `8ad4ef6` (`feat(ui): bulk pantry add + plan-the-week button (Sprint 6 F3+F4)`)
|
||||||
|
**Sprint scope:** F3 (ShoppingList bulk add) + F4 (Dashboard plan-the-week button)
|
||||||
|
**Backend changes:** `pantry.py` (new bulk endpoint), `meals.py` (new fill-empty-slots endpoint), `schemas/__init__.py` (3 new schemas)
|
||||||
|
**Frontend changes:** `api/index.ts` (2 new bindings), `Dashboard.tsx` (Plan the week button + dropdown), `ShoppingList.tsx` (Add to pantry button)
|
||||||
|
|
||||||
|
## 1. Deploy
|
||||||
|
|
||||||
|
Sprint 6 has both backend and frontend changes. No migrations needed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On deployment host (100.108.224.12)
|
||||||
|
cd /path/to/MealPlanner
|
||||||
|
git pull
|
||||||
|
docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Build verification (already green locally)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd frontend && npm run build
|
||||||
|
> tsc && vite build
|
||||||
|
✓ 1894 modules transformed.
|
||||||
|
dist/index.html 0.54 kB │ gzip: 0.31 kB
|
||||||
|
dist/assets/index-DBX6fwsX.css 38.10 kB │ gzip: 6.82 kB
|
||||||
|
dist/assets/index-CTd7xPlC.js 478.22 kB │ gzip: 147.53 kB
|
||||||
|
✓ built in 7.02s
|
||||||
|
```
|
||||||
|
|
||||||
|
tsc 0 errors, vite 0 errors.
|
||||||
|
|
||||||
|
## 3. Backend smoke (local dev, already verified)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# F3 — bulk pantry add with an unknown ingredient
|
||||||
|
curl -s -X POST http://100.108.208.56:8082/api/pantry/bulk \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"items":[{"ingredient_id":"00000000-0000-0000-0000-000000000001"}]}'
|
||||||
|
# Expected: {"added":0,"updated":0,"skipped":1,"results":[{"ingredient_id":"...","status":"skipped","id":null,"reason":"Unknown ingredient"}]}
|
||||||
|
|
||||||
|
# F4 — fill empty slots for dinners only
|
||||||
|
PLAN_ID=$(curl -s http://100.108.208.56:8082/api/meals | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id','') if d else '')")
|
||||||
|
curl -s -X POST "http://100.108.208.56:8082/api/meals/$PLAN_ID/fill-empty-slots" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"meal_types":["dinner"]}'
|
||||||
|
# Expected: {"filled":[...],"failed":[]} (filled has the newly-inserted dinner slots; failed is empty if there were empty dinner slots and recipes available)
|
||||||
|
|
||||||
|
# F4 — invalid meal_type
|
||||||
|
curl -s -X POST "http://100.108.208.56:8082/api/meals/$PLAN_ID/fill-empty-slots" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"meal_types":["brunch"]}'
|
||||||
|
# Expected: {"filled":[],"failed":[{"day_of_week":0,"meal_type":"brunch","reason":"Unknown meal_type: brunch"}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. UI smoke checks
|
||||||
|
|
||||||
|
### S6.A — F3 ShoppingList "Add checked to pantry"
|
||||||
|
|
||||||
|
1. Open `http://100.108.208.56:8082/shopping-list`.
|
||||||
|
2. Confirm a shopping list is displayed (current week has a plan).
|
||||||
|
3. Tick 3-5 checkboxes in different aisles. The header should show a primary `Add 5 to pantry` button next to the Reset button.
|
||||||
|
4. Click the `Add 5 to pantry` button.
|
||||||
|
5. Toast should appear: `Pantry: added 3, updated 1, skipped 1` (counts will vary).
|
||||||
|
6. Open `http://100.108.208.56:8082/pantry` in a new tab. The 4 successfully added items should be there.
|
||||||
|
7. Return to the ShoppingList. The 4 items should now be unchecked; the 1 skipped item (if any) should still be checked.
|
||||||
|
|
||||||
|
### S6.B — F3 partial-failure edge case
|
||||||
|
|
||||||
|
To force a "skipped" count, you'd need a shopping list item without an `ingredient_id`. The seed data may not include any. If your DB does have such an item:
|
||||||
|
- Tick one such item + one normal item.
|
||||||
|
- Click `Add 2 to pantry`.
|
||||||
|
- Toast should show `skipped 1` and the un-skipped item should be added.
|
||||||
|
|
||||||
|
If your DB has no such items, the toast will show only `added` / `updated` and that's correct.
|
||||||
|
|
||||||
|
### S6.C — F4 Dashboard "Plan the week"
|
||||||
|
|
||||||
|
1. Open `http://100.108.208.56:8082/`.
|
||||||
|
2. The header should show a primary `Plan the week` button (with a Sparkles icon and a chevron-down caret) next to the week-nav control.
|
||||||
|
3. Click the button. A dropdown should appear with two options:
|
||||||
|
- `Dinners only` (subtitle: "Fill every empty dinner slot this week")
|
||||||
|
- `All meals` (subtitle: "Fill every empty slot (breakfast, lunch, dinner) this week")
|
||||||
|
4. Click `Dinners only`. The button should show a spinner + `Planning…` and be disabled.
|
||||||
|
5. After 1-2 seconds, a toast should appear: `Planned N meal slots` (or `Planned N of M meal slots — X failed (e.g. <reason>)`).
|
||||||
|
6. The weekly grid should refresh and the previously-empty dinner slots should now show generated meals.
|
||||||
|
7. Click `Plan the week` again, choose `All meals`. The grid should fill in any remaining empty breakfast/lunch slots too.
|
||||||
|
|
||||||
|
### S6.D — F4 edge cases
|
||||||
|
|
||||||
|
- **No empty slots:** if every slot is already filled, the toast should say `No empty meal slots to fill`.
|
||||||
|
- **All recipes used:** the backend reuses recipes when the un-used pool is empty. If your family has very few recipes, the toast might say `Planned 21 of 21 meal slots` (with recipes repeated across days). That's expected.
|
||||||
|
- **Invalid meal_types:** the frontend only sends `dinner` or `breakfast`+`lunch`+`dinner`, so the backend's `Unknown meal_type` failure mode is not triggerable from the UI. (Documented in the backend test for completeness.)
|
||||||
|
|
||||||
|
### S6.E — Regression check: Sprints 1-5 still work
|
||||||
|
|
||||||
|
Quick spot-check:
|
||||||
|
|
||||||
|
- [ ] `/recommended` redirects to `/recipes/recommended` (Sprint 1 B4)
|
||||||
|
- [ ] Recipe detail shows ingredients with proper spacing (Sprint 1 B1)
|
||||||
|
- [ ] Meal detail shows `$X.XX per serving` (Sprint 1 B3)
|
||||||
|
- [ ] Pantry aisle `<select>` has all 9 canonical options (Sprint 2 B8)
|
||||||
|
- [ ] Shopping list section headers are sentence-case (Sprint 2 B9)
|
||||||
|
- [ ] Recipes filter panel has Apply/Reset and active count badge (Sprint 2 B11)
|
||||||
|
- [ ] Dashboard mobile viewport shows empty meal slots (Sprint 1 B5)
|
||||||
|
- [ ] Dashboard delete shows the Undo toast (Sprint 3 B12)
|
||||||
|
- [ ] Failed mutations show a toast with the FastAPI detail (Sprint 4 F7)
|
||||||
|
- [ ] URL week selector: `?week=2026-05-15` works (Sprint 5 F5)
|
||||||
|
- [ ] Keyboard shortcuts: `g d` / `g p` / `?` (Sprint 5 F2)
|
||||||
|
|
||||||
|
## 5. Acceptance criteria
|
||||||
|
|
||||||
|
Sprint 6 is done when:
|
||||||
|
- [ ] `git pull` + container rebuild on the deployment host succeeds
|
||||||
|
- [ ] S6.A bulk pantry add works
|
||||||
|
- [ ] S6.C plan-the-week button works
|
||||||
|
- [ ] No regression in S6.E
|
||||||
|
|
||||||
|
## 6. Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git revert 8ad4ef6
|
||||||
|
docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
No migrations to revert. No data loss possible — both endpoints only insert or upsert user-controlled data.
|
||||||
@@ -76,6 +76,12 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
|
|||||||
> - **CRITICAL 0015 cast fix** (also in `d78bd18`): the CASE expression in `0015_normalize_pantry_aisles.py` failed with `text = boolean` on the `varchar(100) aisle` column. Sprint 2's dry-run query used a different path so the bug was not caught during Sprint 2. The fix is an explicit `::varchar(100)` cast on the whole CASE expression + simplified `WHEN '' THEN NULL` branch. **Without this fix, the deployment host's `alembic upgrade head` would have failed**, blocking Sprints 2, 3, 4 from going live. The local dev DB has been migrated successfully as of 2026-06-04.
|
> - **CRITICAL 0015 cast fix** (also in `d78bd18`): the CASE expression in `0015_normalize_pantry_aisles.py` failed with `text = boolean` on the `varchar(100) aisle` column. Sprint 2's dry-run query used a different path so the bug was not caught during Sprint 2. The fix is an explicit `::varchar(100)` cast on the whole CASE expression + simplified `WHEN '' THEN NULL` branch. **Without this fix, the deployment host's `alembic upgrade head` would have failed**, blocking Sprints 2, 3, 4 from going live. The local dev DB has been migrated successfully as of 2026-06-04.
|
||||||
> - **Backend changes:** `meals.py` and `shopping_list.py` (new query param) + `0015_normalize_pantry_aisles.py` (cast fix).
|
> - **Backend changes:** `meals.py` and `shopping_list.py` (new query param) + `0015_normalize_pantry_aisles.py` (cast fix).
|
||||||
> - **Verification log:** `Review/sprint5-verification.md`. Deploy is a single batch for Sprints 2-5: backup → migrate → rebuild backend + frontend.
|
> - **Verification log:** `Review/sprint5-verification.md`. Deploy is a single batch for Sprints 2-5: backup → migrate → rebuild backend + frontend.
|
||||||
|
>
|
||||||
|
> **Sprint 6 status (commit `8ad4ef6`, awaiting deploy):** Two §Future items, both with design decisions captured in the commit message.
|
||||||
|
> - **F3** Bulk 'add checked to pantry' on ShoppingList. Backend `POST /api/pantry/bulk` accepts `{items: HomePantryCreate[]}` and returns per-item status (`added` / `updated` / `skipped`) with totals. Per-item failure model: unknown ingredient → `skipped` with reason, not a 4xx. Frontend ShoppingList gains a primary `Add N to pantry` button next to the existing Reset button; toast reports `added X, updated Y, skipped Z`; only the items that actually landed are removed from the checked Set. **Scope decision:** ShoppingList only (the checked Set was the natural substrate; Pantry would need new multi-select UI).
|
||||||
|
> - **F4** Plan the whole week on Dashboard. Backend `POST /api/meals/{id}/fill-empty-slots` with body `{meal_types: [str, ...]}` returns `FillEmptySlotsResult { filled: [{day, meal_type, item}], failed: [{day, meal_type, reason}] }`. Iterates day 1..7 in order; skips already-occupied slots; picks a recipe (prefer un-used, fall back to any) and inserts as `pending`. Per-slot failure model — never aborts mid-batch. Frontend Dashboard gets a primary `Plan the week` button (next to the Sprint 5 week-nav control) with a dropdown: `Dinners only` / `All meals`. Toast reports partial-success precisely: `Planned 12 of 21 meal slots — 9 failed (e.g. <reason>)`.
|
||||||
|
> - **Backend changes:** `pantry.py` + `meals.py` (new endpoints) + `schemas/__init__.py` (3 new schema types).
|
||||||
|
> - **Verification log:** `Review/sprint6-verification.md`. No migration. Deploy is `docker compose up -d --build backend frontend`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+28
-1
@@ -4,6 +4,7 @@ You are taking over a project in mid-flight. Read `docs/ORIENTATION.md` first fo
|
|||||||
|
|
||||||
**Date of handoff: 2026-06-04. Last commits before handoff:**
|
**Date of handoff: 2026-06-04. Last commits before handoff:**
|
||||||
```
|
```
|
||||||
|
8ad4ef6 feat(ui): bulk pantry add + plan-the-week button (Sprint 6 F3+F4)
|
||||||
f740f40 feat(ui): global keyboard shortcuts + shortcut help banner (Sprint 5 F2)
|
f740f40 feat(ui): global keyboard shortcuts + shortcut help banner (Sprint 5 F2)
|
||||||
d78bd18 feat(ui): URL week selector + aisle-migration 0015 cast fix (Sprint 5 F5)
|
d78bd18 feat(ui): URL week selector + aisle-migration 0015 cast fix (Sprint 5 F5)
|
||||||
d71b67a feat(ui): global react-query error handler + plan-status a11y (Sprint 4 F7+F6)
|
d71b67a feat(ui): global react-query error handler + plan-status a11y (Sprint 4 F7+F6)
|
||||||
@@ -301,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-04** — UI/UX audit & fix cycle (Sprints 1, 2, 3, 4, 5) complete. 18 findings closed (5 P0 + 6 P1 + 3 P2 + 4 §Future), code committed across 9 commits (`f3e4a44`, `36038bb`, `ccc70aa`, `f5fb755`, `e90a9d6`, `427d8ac`, `d71b67a`, `d78bd18`, `f740f40`), build green. **CRITICAL: Sprint 2's deploy was blocked on a cast bug in migration 0015; that bug is fixed in `d78bd18`. Sprints 2-5 are now deployable as a single batch (Sprint 1 already live; Sprints 2-5 require backend rebuild + migration + frontend rebuild).** Full UI-audit handoff at `Review/handoff-ui-audit.md`.
|
**Last updated: 2026-06-04** — UI/UX audit & fix cycle (Sprints 1, 2, 3, 4, 5, 6) complete. 20 findings closed (5 P0 + 6 P1 + 3 P2 + 6 §Future), code committed across 10 commits (`f3e4a44`, `36038bb`, `ccc70aa`, `f5fb755`, `e90a9d6`, `427d8ac`, `d71b67a`, `d78bd18`, `f740f40`, `8ad4ef6`), build green. Sprint 1 deployed; Sprints 2-6 awaiting deploy. **Sprint 2's deploy was blocked on a cast bug in migration 0015; that bug is fixed in `d78bd18`.** Full UI-audit handoff at `Review/handoff-ui-audit.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -318,6 +319,7 @@ A full Nielsen-10-heuristics audit of the live deployment at `http://100.108.208
|
|||||||
- `Review/sprint3-verification.md` — Sprint 3 deploy + smoke-check checklist (frontend only)
|
- `Review/sprint3-verification.md` — Sprint 3 deploy + smoke-check checklist (frontend only)
|
||||||
- `Review/sprint4-verification.md` — Sprint 4 deploy + smoke-check checklist (F7 + F6, frontend only)
|
- `Review/sprint4-verification.md` — Sprint 4 deploy + smoke-check checklist (F7 + F6, frontend only)
|
||||||
- `Review/sprint5-verification.md` — Sprint 5 deploy + smoke-check (F5 + F2 + 0015 fix; backend + frontend)
|
- `Review/sprint5-verification.md` — Sprint 5 deploy + smoke-check (F5 + F2 + 0015 fix; backend + frontend)
|
||||||
|
- `Review/sprint6-verification.md` — Sprint 6 deploy + smoke-check (F3 + F4; backend + frontend, no migration)
|
||||||
- `Review/handoff-ui-audit.md` — focused handoff for a fresh agent continuing UI-audit work
|
- `Review/handoff-ui-audit.md` — focused handoff for a fresh agent continuing UI-audit work
|
||||||
|
|
||||||
**Commits on `main` (ahead of `origin/main` by 9 prior WIP commits plus these 7):**
|
**Commits on `main` (ahead of `origin/main` by 9 prior WIP commits plus these 7):**
|
||||||
@@ -333,6 +335,7 @@ A full Nielsen-10-heuristics audit of the live deployment at `http://100.108.208
|
|||||||
| `d71b67a` | 4 | F7 global error handler (10 try/catch blocks deleted, QueryCache/MutationCache onError wired) + F6 plan-status aria-label |
|
| `d71b67a` | 4 | F7 global error handler (10 try/catch blocks deleted, QueryCache/MutationCache onError wired) + F6 plan-status aria-label |
|
||||||
| `d78bd18` | 5 | F5 URL week selector (backend `?week_start=`, frontend prev/next + `useSearchParams`) + **CRITICAL 0015 cast fix** (was blocking Sprint 2 deploy) |
|
| `d78bd18` | 5 | F5 URL week selector (backend `?week_start=`, frontend prev/next + `useSearchParams`) + **CRITICAL 0015 cast fix** (was blocking Sprint 2 deploy) |
|
||||||
| `f740f40` | 5 | F2 keyboard shortcuts (vim-style sequences, focus-search bus, help banner) + new `hooks/` and `components/ShortcutHelpBanner.tsx` |
|
| `f740f40` | 5 | F2 keyboard shortcuts (vim-style sequences, focus-search bus, help banner) + new `hooks/` and `components/ShortcutHelpBanner.tsx` |
|
||||||
|
| `8ad4ef6` | 6 | F3 bulk pantry add (POST /api/pantry/bulk + ShoppingList 'Add N to pantry' button) + F4 plan-the-week (POST /api/meals/{id}/fill-empty-slots + Dashboard dropdown) |
|
||||||
|
|
||||||
**Critical Sprint 2 deploy note:** the user must run on the deployment host *after* `git pull`:
|
**Critical Sprint 2 deploy note:** the user must run on the deployment host *after* `git pull`:
|
||||||
|
|
||||||
@@ -410,6 +413,30 @@ docker compose -f docker-compose.yml up -d --build backend frontend
|
|||||||
|
|
||||||
The order matters: backup → migration → rebuild. The migration will lock the `ingredient` and `grocery_item` tables for the duration; the persist script creates recoverable backups.
|
The order matters: backup → migration → rebuild. The migration will lock the `ingredient` and `grocery_item` tables for the duration; the persist script creates recoverable backups.
|
||||||
|
|
||||||
|
### Sprint 6 — F3 (bulk pantry add) + F4 (plan the whole week)
|
||||||
|
|
||||||
|
Third wave of §Future. Both M-L size, both with design decisions made by the user during planning.
|
||||||
|
|
||||||
|
**F3 — Bulk 'add checked to pantry' (ShoppingList):**
|
||||||
|
- Backend `POST /api/pantry/bulk` accepts `{items: HomePantryCreate[]}`; returns `HomePantryBulkResult { added, updated, skipped, results: [{ingredient_id, status, id, reason}] }`. Per-item failure model: unknown ingredient → `skipped` with reason, not a 4xx. Each item follows the same upsert semantics as the single-item endpoint.
|
||||||
|
- Frontend `mealPlannerApi.pantry.addBulk(items)`.
|
||||||
|
- ShoppingList gains a primary `Add N to pantry` button (next to the existing Reset button) that appears when `checked.size > 0`. Toast reports `added X, updated Y, skipped Z`. On success, only the items that landed in the pantry are removed from the checked Set; skipped items stay checked so the user can see what failed.
|
||||||
|
- **Scope decision:** ShoppingList only. Pantry does not have row-selection state, and adding multi-select to a 4-column table on mobile is a larger surface than Sprint 6's budget. The audit's F3 ticket can be re-scoped later.
|
||||||
|
|
||||||
|
**F4 — Plan the whole week (Dashboard):**
|
||||||
|
- Backend `POST /api/meals/{id}/fill-empty-slots` with body `{meal_types: [str, ...]}` returns `FillEmptySlotsResult { filled: [{day, meal_type, item}], failed: [{day, meal_type, reason}] }`. Iterates day 1..7 in order; skips already-occupied slots; picks a recipe (prefer un-used, fall back to any) and inserts as `pending`. Per-slot failure model — never aborts mid-batch. Invalid meal_type (e.g. `'brunch'`) returns immediately with a single FailedSlot explaining why.
|
||||||
|
- Frontend `mealPlannerApi.meals.fillEmptySlots(planId, mealTypes)`.
|
||||||
|
- Dashboard gets a primary `Plan the week` button (next to the Sprint 5 week-nav control) with a dropdown. Two options: `Dinners only` (sends `['dinner']`) and `All meals` (sends `['breakfast','lunch','dinner']`). Each option has a one-line secondary label.
|
||||||
|
- Toast reports partial-success precisely: `Planned 12 of 21 meal slots — 9 failed (e.g. <reason>)`. Query invalidated so new slots show up.
|
||||||
|
- **Scope decision:** dropdown (per design-call); partial-success (per design-call).
|
||||||
|
- **Out of scope:** the no-op `Generate Meal Plan` empty-state CTA at `Dashboard.tsx:415` (when the family has NO plan at all, distinct from the F4 case of "plan exists but slots are empty"). Routing that CTA needs a user-facing "create a new plan" path (orchestrator/admin flow), which is a different feature. Documented as a follow-up.
|
||||||
|
|
||||||
|
**Sprint 6 deploy (backend + frontend, no migration):**
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
|
```
|
||||||
|
|
||||||
### Deployment-host vs dev-host (Tailscale gotcha)
|
### Deployment-host vs dev-host (Tailscale gotcha)
|
||||||
|
|
||||||
This repo lives on a dev host (Tailscale `100.108.146.47`). The user's home server (Tailscale `100.108.224.12`) serves the live app at `100.108.208.56:8082`. The user's workflow is **commit locally, `git pull` on the deployment host, rebuild there**. Don't `docker compose up` on the local dev host expecting it to update the live site — it won't.
|
This repo lives on a dev host (Tailscale `100.108.146.47`). The user's home server (Tailscale `100.108.224.12`) serves the live app at `100.108.208.56:8082`. The user's workflow is **commit locally, `git pull` on the deployment host, rebuild there**. Don't `docker compose up` on the local dev host expecting it to update the live site — it won't.
|
||||||
|
|||||||
+29
-1
@@ -215,7 +215,35 @@ Resolve the 14 issues (5 P0, 6 P1, 3 P2) from `Review/ui-nielsen-audit.md` in th
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sprint 5 — Power-user navigation (F5 + F2)
|
## Sprint 6 — Bulk actions (F3 + F4)
|
||||||
|
|
||||||
|
**Status (2026-06-04):** ✅ Both items implemented. One commit: `8ad4ef6`. `npm run build` green; both new backend endpoints smoke-tested locally with curl. **Scope decision:** F3 = ShoppingList only (the checked Set is the natural substrate). F4 = `Plan the week` button with `Dinners only` / `All meals` dropdown (per design-call), partial-success with detailed report (per design-call).
|
||||||
|
|
||||||
|
### S6.1 · F3 — Bulk 'add checked to pantry' on ShoppingList
|
||||||
|
- **Files:** `backend/app/api/pantry.py`, `backend/app/schemas/__init__.py`, `frontend/src/api/index.ts`, `frontend/src/pages/ShoppingList.tsx`
|
||||||
|
- **Change (one commit `8ad4ef6`):**
|
||||||
|
1. **Backend `POST /api/pantry/bulk`:** new endpoint accepting `{items: HomePantryCreate[]}`. Each item follows the same upsert semantics as the single-item `POST /api/pantry` (insert or overwrite qty/unit/expires_at). Per-item status is reported as `added` / `updated` / `skipped` with a human-readable reason for skips. Total counts and per-item details both returned (`HomePantryBulkResult` schema).
|
||||||
|
2. **Frontend `mealPlannerApi.pantry.addBulk(items)`** is the API binding.
|
||||||
|
3. **ShoppingList:** a new primary `Add N to pantry` button appears next to the existing Reset button when `checked.size > 0`. Click → POST → toast shows `'Pantry: added X, updated Y, skipped Z'`. On success, the items that actually landed are removed from the checked Set; skipped items stay checked so the user can see what failed. Button shows `Adding…` while in flight; disabled during the request.
|
||||||
|
- **Verify:** checked items get bulk-added; partial successes surface in the toast; the Pantry list reflects the new entries after a refresh.
|
||||||
|
|
||||||
|
### S6.2 · F4 — Plan the whole week (Dashboard button)
|
||||||
|
- **Files:** `backend/app/api/meals.py`, `backend/app/schemas/__init__.py`, `frontend/src/api/index.ts`, `frontend/src/pages/Dashboard.tsx`
|
||||||
|
- **Change (one commit `8ad4ef6`):**
|
||||||
|
1. **Backend `POST /api/meals/{id}/fill-empty-slots`:** new endpoint with body `{meal_types: [str, ...]}` (subset of `["breakfast","lunch","dinner"]`). Iterates day 1..7 in order; for each day, iterates the requested meal_types; skips already-occupied slots; picks a recipe (prefer un-used, fall back to any) and inserts as `pending`. Per-slot failure model — never aborts mid-batch — returns `FillEmptySlotsResult { filled: [{day, meal_type, item}], failed: [{day, meal_type, reason}] }`. Invalid `meal_type` (e.g. `'brunch'`) returns immediately with a single FailedSlot explaining why.
|
||||||
|
2. **Frontend `mealPlannerApi.meals.fillEmptySlots(planId, mealTypes)`** is the API binding.
|
||||||
|
3. **Dashboard:** new `Plan the week` button in the header (next to the Sprint 5 week-nav control). Primary color, Sparkles icon, ChevronDown caret indicates a dropdown. Two options: `Dinners only` (sends `meal_types=['dinner']`) and `All meals` (sends `meal_types=['breakfast','lunch','dinner']`). Each option has a one-line secondary label.
|
||||||
|
4. **Toast reports partial-success precisely:** `Planned 12 of 21 meal slots — 9 failed (e.g. No recipes available)` or `Planned 15 meal slots` (full success). Query invalidated so new slots show up immediately.
|
||||||
|
- **Verify:** button fills the empty slots; partial-success toast shows the right counts; query refresh shows the new meals.
|
||||||
|
- **Out of scope (documented in `Review/handoff-ui-audit.md`):** the no-op `Generate Meal Plan` empty-state CTA at `Dashboard.tsx:415` (when the family has NO plan at all, distinct from the F4 case of "plan exists but slots are empty"). Routing that CTA needs a user-facing "create a new plan" path, which is a different feature (orchestrator/admin flow).
|
||||||
|
|
||||||
|
### S6.3 · Sprint 6 verification gate
|
||||||
|
- [x] `npm run build` green.
|
||||||
|
- [x] Backend smoke on local dev DB: `/api/pantry/bulk` (skipped count for unknown ingredient), `/api/meals/{id}/fill-empty-slots` (dinners-only partial-success).
|
||||||
|
- [ ] Deploy verified (git pull + container rebuild; backend + frontend per `Review/sprint6-verification.md`).
|
||||||
|
- [ ] No regression in Sprints 1-5.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Status (2026-06-04):** ✅ Both items implemented. Two commits: `d78bd18` (F5 + 0015 cast fix) and `f740f40` (F2). `npm run build` green; backend smoke-tested locally with `alembic upgrade head` + `curl` confirming the new `?week_start=` param works. **Includes a critical bug fix to migration 0015 (Sprint 2) that was blocking Sprint 2's deploy too** — see S5.0.
|
**Status (2026-06-04):** ✅ Both items implemented. Two commits: `d78bd18` (F5 + 0015 cast fix) and `f740f40` (F2). `npm run build` green; backend smoke-tested locally with `alembic upgrade head` + `curl` confirming the new `?week_start=` param works. **Includes a critical bug fix to migration 0015 (Sprint 2) that was blocking Sprint 2's deploy too** — see S5.0.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user