Public Access
docs(review): Sprint 5 verification log + plan/handoff/audit updates
Sprint 5 (F5 + F2 + 0015 cast fix) is now documented across the project: - Review/sprint5-verification.md: new deploy + smoke-check doc. Backend + frontend deploy (one batch with Sprints 2-4). Migration 0015 MUST be run as part of this deploy (the cast fix is what makes it runnable). 7 smoke-check sections: A) curl tests for ?week_start=, B/C/D) URL week nav on Dashboard and Shopping List with query-key isolation, E) keyboard shortcut matrix, F) post- migration canonical-aisle verification query, G) Sprints 1-4 regression spot-check. Rollback section covers reverts + the persist_aisle_backup recovery path. - fix-ui-audit.md: new Sprint 5 section (S5.0 critical 0015 fix, S5.1 F5 implementation, S5.2 F2 implementation, S5.3 verification gate). 'Done when (overall)' block updated to 5 sprints + 9 commits + 18 findings closed + the 0015 fix unblocks Sprint 2. - Review/handoff-ui-audit.md: updated to a 5-sprint cycle. TL;DR table includes thed78bd18+f740f40rows with the CRITICAL 0015 fix callout. file-list includes the new sprint5-verification doc. file-level diff summary gains 16 new rows (S5 backend + frontend + 0015 + hooks/components). §Future list now strikethroughs F2 and F5. Quick-start deploy commands list Sprints 2-5 as a single batch (backup → migrate → rebuild backend + frontend). - Review/ui-nielsen-audit.md: new Sprint 5 status block at the top. F5 + F2 + the 0015 fix all documented. Cross-ref to Review/sprint5-verification.md. - docs/HANDOFF.md: Last-updated line bumped to 5 sprints / 9 commits / 18 findings / with the 0015 fix CRITICAL callout. Header commit list gains the two Sprint 5 commits. New 'Sprint 5' subsection in the 2026-06-04 session block. Commit table gained thed78bd18+f740f40rows. Files-modified list now includes all 5 sprints' changes. New 'Files added by Sprint 5' subsection for the 3 new files in hooks/ + components/. 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:
+34
-17
@@ -1,25 +1,29 @@
|
|||||||
# UI/UX Audit & Fix — Agent Handoff
|
# UI/UX Audit & Fix — Agent Handoff
|
||||||
|
|
||||||
You are taking over a 4-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 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.
|
||||||
|
|
||||||
**Date of handoff: 2026-06-03.**
|
**Date of handoff: 2026-06-04.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
Four commits land all 14 audit findings + 2 of the §Future items:
|
Nine commits land all 14 audit findings + 4 of the §Future items:
|
||||||
|
|
||||||
| Sprint | Commit | Scope | Build | Deploy |
|
| Sprint | Commit | Scope | Build | Deploy |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| 1 | `f3e4a44` | 5 P0 blockers | ✅ green | ✅ deployed 2026-06-02 by user |
|
| 1 | `f3e4a44` | 5 P0 blockers | ✅ green | ✅ deployed 2026-06-02 by user |
|
||||||
| 2 | `ccc70aa` + `f5fb755` | 6 P1s + bonus S3.3 | ✅ green | ⚠️ not yet deployed |
|
| 2 | `ccc70aa` + `f5fb755` | 6 P1s + bonus S3.3 | ✅ green | ⚠️ blocked — see S2 + 0015 fix below |
|
||||||
| 3 | `e90a9d6` | 3 P2s + a11y sweep | ✅ green | ⚠️ not yet deployed |
|
| 3 | `e90a9d6` | 3 P2s + a11y sweep | ✅ green | ⚠️ blocked — depends on S2 deploy |
|
||||||
| 4 | `d71b67a` | F7 global error handler + F6 plan-status aria-label | ✅ green | ⚠️ not yet deployed |
|
| 4 | `d71b67a` | F7 global error handler + F6 plan-status aria-label | ✅ green | ⚠️ not yet deployed (frontend-only) |
|
||||||
|
| 5 | `d78bd18` | F5 URL week selector + **CRITICAL 0015 cast fix** | ✅ green | ⚠️ not yet deployed |
|
||||||
|
| 5 | `f740f40` | F2 keyboard shortcuts + ShortcutHelpBanner | ✅ green | ⚠️ not yet deployed |
|
||||||
|
|
||||||
All work is on `main` ahead of `origin/main` (pre-existing WIP also present). All four sprints compile. **Sprint 1 is live. Sprints 2, 3, and 4 are not yet live on `100.108.208.56:8082/`.** Sprint 4 is frontend-only (no backend changes); Sprint 2 includes a backend migration; Sprints 3 and 4 are pure frontend.
|
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/`.**
|
||||||
|
|
||||||
**Next action:** the user runs the deploy commands in `Review/sprint2-verification.md`, `Review/sprint3-verification.md`, and `Review/sprint4-verification.md` on the deployment host, then smoke-checks per the checklists. After verification, any remaining items move to the §Future backlog in `fix-ui-audit.md` (F1 onboarding, F2 keyboard shortcuts, F3 bulk add, F4 plan-whole-week, F5 URL week selector, F8 Spoonacular enrichment, F9 Ollama matcher — F6 and F7 now done in Sprint 4).
|
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,17 +36,18 @@ All work is on `main` ahead of `origin/main` (pre-existing WIP also present). Al
|
|||||||
│ ├── sprint2-verification.md # Deploy + smoke checks for Sprint 2 (BACKEND MIGRATION INCLUDED).
|
│ ├── sprint2-verification.md # Deploy + smoke checks for Sprint 2 (BACKEND MIGRATION INCLUDED).
|
||||||
│ ├── 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).
|
||||||
│ ├── 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.
|
||||||
│ ├── reviewconcensus.md # Prior review consensus.
|
│ ├── reviewconcensus.md # Prior review consensus.
|
||||||
│ └── synthesis.md # Prior review synthesis.
|
│ └── synthesis.md # Prior review synthesis.
|
||||||
├── fix-ui-audit.md # The plan. Implementation notes per task + commit refs. Sprint 4 added.
|
├── fix-ui-audit.md # The plan. Implementation notes per task + commit refs. Sprints 4-5 added.
|
||||||
├── docs/HANDOFF.md # The project-wide handoff (don't duplicate — read for context).
|
├── docs/HANDOFF.md # The project-wide handoff (don't duplicate — read for context).
|
||||||
└── docs/ORIENTATION.md # Project orientation.
|
└── docs/ORIENTATION.md # Project orientation.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Read first**, in this order: `docs/ORIENTATION.md` → `docs/HANDOFF.md` → `Review/ui-nielsen-audit.md` (the audit) → `fix-ui-audit.md` (the plan) → the three `sprintN-verification.md` files.
|
**Read first**, in this order: `docs/ORIENTATION.md` → `docs/HANDOFF.md` → `Review/ui-nielsen-audit.md` (the audit) → `fix-ui-audit.md` (the plan) → the four `sprintN-verification.md` files.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -139,12 +144,12 @@ 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)
|
||||||
|
|
||||||
F6 and F7 are done in Sprint 4. The audit intentionally deferred these. If the user asks, they become a new plan:
|
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:
|
||||||
- F1. Onboarding hints / tour (H10)
|
- F1. Onboarding hints / tour (H10)
|
||||||
- F2. Keyboard shortcuts (`/`, `g p`, `g s`, `n m`)
|
- ~~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)
|
||||||
- F4. Plan-the-whole-week button (H7)
|
- F4. Plan-the-whole-week button (H7)
|
||||||
- F5. Persistent week selector in URL
|
- ~~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/`)
|
||||||
@@ -183,6 +188,19 @@ F6 and F7 are done in Sprint 4. The audit intentionally deferred these. If the u
|
|||||||
| 4 | `frontend/src/pages/Dashboard.tsx` | F7: removed 6 local onError; F6: plan-status aria-label |
|
| 4 | `frontend/src/pages/Dashboard.tsx` | F7: removed 6 local onError; F6: plan-status aria-label |
|
||||||
| 4 | `frontend/src/pages/Pantry.tsx` | F7: removed 3 local onError; undo now uses showApiError |
|
| 4 | `frontend/src/pages/Pantry.tsx` | F7: removed 3 local onError; undo now uses showApiError |
|
||||||
| 4 | `frontend/src/pages/MealDetail.tsx` | F7: removed submitMutation.onError |
|
| 4 | `frontend/src/pages/MealDetail.tsx` | F7: removed submitMutation.onError |
|
||||||
|
| 5 | `backend/app/api/meals.py` | F5: added ?week_start= query param |
|
||||||
|
| 5 | `backend/app/api/shopping_list.py` | F5: added ?week_start= query param |
|
||||||
|
| 5 | `backend/alembic/versions/0015_normalize_pantry_aisles.py` | **CRITICAL cast fix** — was blocking Sprint 2 deploy |
|
||||||
|
| 5 | `frontend/src/lib/utils.ts` | F5: isoMonday/parseIsoDate/shiftIsoDate/formatIsoDate helpers |
|
||||||
|
| 5 | `frontend/src/api/index.ts` | F5: getPlanned/get take weekStart? |
|
||||||
|
| 5 | `frontend/src/pages/Dashboard.tsx` | F5: useSearchParams sync, week nav, queryKey includes weekStart |
|
||||||
|
| 5 | `frontend/src/pages/ShoppingList.tsx` | F5: same URL sync as Dashboard |
|
||||||
|
| 5 | `frontend/src/hooks/useKeyboardShortcuts.ts` | **NEW** — F2 global handler with vim-style sequences |
|
||||||
|
| 5 | `frontend/src/hooks/useFocusSearch.ts` | **NEW** — F2 focus-search CustomEvent bus |
|
||||||
|
| 5 | `frontend/src/components/ShortcutHelpBanner.tsx` | **NEW** — F2 help dialog |
|
||||||
|
| 5 | `frontend/src/App.tsx` | F2: mount GlobalShortcuts + ShortcutHelpBanner |
|
||||||
|
| 5 | `frontend/src/pages/Pantry.tsx` | F2: useFocusSearchOnShortcut on search input |
|
||||||
|
| 5 | `frontend/src/pages/Recipes.tsx` | F2: useFocusSearchOnShortcut on search input |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -203,13 +221,12 @@ git log --oneline -10
|
|||||||
git status
|
git status
|
||||||
git log origin/main..HEAD --oneline
|
git log origin/main..HEAD --oneline
|
||||||
|
|
||||||
# 3. If the user wants to deploy pending work:
|
# 3. If the user wants to deploy pending work (Sprints 2-5 as a single batch):
|
||||||
# - Sprint 2: docker compose exec db psql -U mealplanner -d mealplanner \
|
# - Sprint 2: docker compose exec db psql -U mealplanner -d mealplanner \
|
||||||
# -f /dev/stdin < backend/scripts/persist_aisle_backup.sql
|
# -f /dev/stdin < backend/scripts/persist_aisle_backup.sql
|
||||||
# - docker compose exec backend alembic upgrade head
|
# - docker compose exec backend alembic upgrade head
|
||||||
# - Sprint 3: docker compose -f docker-compose.yml up -d --build frontend
|
# - Sprints 3-5: docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
# - Sprint 4: docker compose -f docker-compose.yml up -d --build frontend
|
# See Review/sprint{2,3,4,5}-verification.md for the full checklist.
|
||||||
# See Review/sprint{2,3,4}-verification.md for the full checklist.
|
|
||||||
|
|
||||||
# 4. If you need to take new screenshots after changes
|
# 4. If you need to take new screenshots after changes
|
||||||
# /tmp/opencode/mp-review/shoot-sprint1.js is a working playwright harness.
|
# /tmp/opencode/mp-review/shoot-sprint1.js is a working playwright harness.
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
# Sprint 5 — Deploy & smoke-check (F5 + F2)
|
||||||
|
|
||||||
|
**Goal:** verify the URL week selector (F5) and keyboard shortcuts (F2) work end-to-end on `http://100.108.208.56:8082/`.
|
||||||
|
|
||||||
|
**Commits:**
|
||||||
|
- `d78bd18` — F5 URL week selector + 0015 migration cast fix
|
||||||
|
- `f740f40` — F2 keyboard shortcuts
|
||||||
|
|
||||||
|
**Sprint scope:** F5 (URL week selector) + F2 (keyboard shortcuts) + 0015 cast fix
|
||||||
|
**Backend changes:** `meals.py` (week_start param), `shopping_list.py` (week_start param), `0015_normalize_pantry_aisles.py` (cast fix)
|
||||||
|
**Frontend changes:** `App.tsx`, `lib/utils.ts`, `api/index.ts`, `pages/Dashboard.tsx`, `pages/ShoppingList.tsx`, `pages/Pantry.tsx`, `pages/Recipes.tsx`; new `hooks/useKeyboardShortcuts.ts`, `hooks/useFocusSearch.ts`, `components/ShortcutHelpBanner.tsx`
|
||||||
|
|
||||||
|
## 1. Deploy
|
||||||
|
|
||||||
|
This sprint includes a **backend migration** and a **frontend bundle** change.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On deployment host (100.108.224.12)
|
||||||
|
cd /path/to/MealPlanner
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 1. Persistent backup of aisle values BEFORE the migration (recommended)
|
||||||
|
docker compose exec -T db psql -U mealplanner -d mealplanner \
|
||||||
|
-f /dev/stdin < backend/scripts/persist_aisle_backup.sql
|
||||||
|
|
||||||
|
# 2. Apply the migration (now includes the cast fix from Sprint 5)
|
||||||
|
docker compose exec backend alembic upgrade head
|
||||||
|
# Expected: "Running upgrade 0014 -> 0015, Normalize ingredient.aisle and grocery_item.aisle..."
|
||||||
|
|
||||||
|
# 3. Rebuild & restart both backend and frontend
|
||||||
|
docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
**CRITICAL:** the migration fix in `d78bd18` is what makes the Sprint 2 migration actually runnable. The deployment host will hit the same `text = boolean` error without it.
|
||||||
|
|
||||||
|
## 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.32 kB
|
||||||
|
dist/assets/index-BeQbgL42.css 37.83 kB │ gzip: 6.79 kB
|
||||||
|
dist/assets/index-6M0ldwxq.js 474.37 kB │ gzip: 146.54 kB
|
||||||
|
✓ built in 6.71s
|
||||||
|
```
|
||||||
|
|
||||||
|
tsc 0 errors, vite 0 errors.
|
||||||
|
|
||||||
|
## 3. Smoke checks
|
||||||
|
|
||||||
|
### S5.A — F5 backend: `?week_start=` is honoured
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Without param — should return the latest plan
|
||||||
|
curl -s "http://100.108.208.56:8082/api/meals" | python3 -c "import sys,json; d=json.load(sys.stdin); print('week_start_date:', d.get('week_start_date') if d else 'NULL')"
|
||||||
|
|
||||||
|
# 2. With a known plan's week — should return that plan
|
||||||
|
curl -s "http://100.108.208.56:8082/api/meals?week_start=2026-05-15" | python3 -c "import sys,json; d=json.load(sys.stdin); print('week_start_date:', d.get('week_start_date') if d else 'NULL', 'items:', len(d.get('items',[])) if d else 0)"
|
||||||
|
|
||||||
|
# 3. With a week that has no plan — should return null
|
||||||
|
curl -s "http://100.108.208.56:8082/api/meals?week_start=2099-01-01"
|
||||||
|
# Expected: null (or a JSON "null")
|
||||||
|
|
||||||
|
# 4. Same for shopping-list
|
||||||
|
curl -s "http://100.108.208.56:8082/api/shopping-list?week_start=2026-05-15" | python3 -c "import sys,json; d=json.load(sys.stdin); print('week_start_date:', d.get('week_start_date'), 'items:', len(d.get('items',[])))"
|
||||||
|
```
|
||||||
|
|
||||||
|
The local dev DB returns `week_start_date: 2026-05-15 items: 4` for the known plan and `null` for 2099-01-01. The deployment host should match (modulo different seed data).
|
||||||
|
|
||||||
|
### S5.B — F5 frontend: URL week navigation on Dashboard
|
||||||
|
|
||||||
|
1. Open `http://100.108.208.56:8082/`.
|
||||||
|
2. Confirm the header shows the current week ("This week" button is highlighted, primary-50 background).
|
||||||
|
3. Click the **left chevron** — title should change to the previous week. URL should update to `?week=YYYY-MM-DD` (Monday's date).
|
||||||
|
4. Click the left chevron again — should be 2 weeks back. URL still has `?week=...`.
|
||||||
|
5. Click the **"This week" / "Current"** button — URL should drop `?week=...`, title should be the current week.
|
||||||
|
6. Click the **right chevron** — title should be next week. (If no plan for that week, you should see the "No meal plan yet" empty state.)
|
||||||
|
7. Manually type `http://100.108.208.56:8082/?week=2026-05-15` in the URL bar. Page should load with that week's plan.
|
||||||
|
|
||||||
|
### S5.C — F5 frontend: URL week navigation on Shopping List
|
||||||
|
|
||||||
|
Same as S5.B but on `/shopping-list`. The empty state should say "No plan for that week" (not "No shopping list yet") when `?week=` is set to a week that has no plan.
|
||||||
|
|
||||||
|
### S5.D — F5 frontend: query-key isolation
|
||||||
|
|
||||||
|
1. Open `http://100.108.208.56:8082/?week=2026-05-15`.
|
||||||
|
2. Open DevTools → Network tab.
|
||||||
|
3. Drag a meal to a different slot. The Network tab should show:
|
||||||
|
- `PUT /api/meals/items/{id}/move?new_day_of_week=...&new_meal_type=...`
|
||||||
|
- `GET /api/meals?week_start=2026-05-15` (the refetch, not `/api/meals` without the param)
|
||||||
|
4. Verify the change persists on refresh.
|
||||||
|
|
||||||
|
### S5.E — F2 keyboard shortcuts
|
||||||
|
|
||||||
|
Open `http://100.108.208.56:8082/` in a fresh tab (so no input is focused).
|
||||||
|
|
||||||
|
| Action | Expected |
|
||||||
|
|---|---|
|
||||||
|
| Press `?` | Help banner appears under the nav; auto-dismisses after 6s |
|
||||||
|
| Press `?` then `Escape` | Banner appears then immediately closes |
|
||||||
|
| Press `g` then `d` | Navigate to `/` (Dashboard) |
|
||||||
|
| Press `g` then `r` | Navigate to `/recipes` |
|
||||||
|
| Press `g` then `p` | Navigate to `/pantry` |
|
||||||
|
| Press `g` then `s` | Navigate to `/shopping-list` |
|
||||||
|
| On Pantry, press `/` | Search input gains focus, text is selected |
|
||||||
|
| On Recipes, press `/` | Search input gains focus, text is selected |
|
||||||
|
| On Pantry, focus the search, type `g d` | "g d" is typed in the search box; no navigation |
|
||||||
|
| Press `g` and wait 2 seconds | Nothing happens (sequence timeout) |
|
||||||
|
|
||||||
|
### S5.F — Migration 0015 cast fix verification
|
||||||
|
|
||||||
|
After `alembic upgrade head` returns successfully:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pick a sample of rows to see they normalized correctly
|
||||||
|
docker compose exec -T db psql -U mealplanner -d mealplanner -c "
|
||||||
|
SELECT aisle, COUNT(*) AS n
|
||||||
|
FROM ingredient
|
||||||
|
GROUP BY aisle
|
||||||
|
ORDER BY n DESC
|
||||||
|
LIMIT 20;
|
||||||
|
"
|
||||||
|
# Expected: aisle values are sentence-case canonical labels only:
|
||||||
|
# 'Pantry', 'Produce', 'Meat & Seafood', 'Dairy & Eggs', 'Frozen',
|
||||||
|
# 'Bakery', 'Beverages', 'Spices', 'Other', or NULL.
|
||||||
|
# If you see 'pantry', 'meat_seafood', 'canned_goods' etc., the
|
||||||
|
# migration did NOT run correctly — re-check the cast fix.
|
||||||
|
```
|
||||||
|
|
||||||
|
### S5.G — Regression check: Sprints 1-4 still work
|
||||||
|
|
||||||
|
- [ ] `/recommended` redirects to `/recipes/recommended` (Sprint 1 B4)
|
||||||
|
- [ ] `/this-does-not-exist` renders the NotFound page (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)
|
||||||
|
- [ ] Pantry aisles are sentence-case canonical (Sprint 5 / 0015 fix)
|
||||||
|
- [ ] 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 (390 px) 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)
|
||||||
|
- [ ] Plan-status Badge announces correctly (Sprint 4 F6)
|
||||||
|
|
||||||
|
## 4. Acceptance criteria
|
||||||
|
|
||||||
|
Sprint 5 is done when:
|
||||||
|
- [ ] `git pull` + migration + container rebuild on the deployment host succeeds
|
||||||
|
- [ ] Migration 0015 completes with the cast fix
|
||||||
|
- [ ] All S5.A curl tests return the expected results
|
||||||
|
- [ ] All S5.B / S5.C / S5.D / S5.E interactions behave as described
|
||||||
|
- [ ] S5.F sample query shows only canonical aisle labels
|
||||||
|
- [ ] No regression in S5.G
|
||||||
|
|
||||||
|
## 5. Rollback
|
||||||
|
|
||||||
|
If something goes wrong:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Revert the cast fix in 0015
|
||||||
|
# (only needed if the cast fix makes the migration worse — it shouldn't)
|
||||||
|
# Just revert the code commit; the migration file change alone won't re-run.
|
||||||
|
|
||||||
|
# Revert the URL week selector
|
||||||
|
git revert f740f40 d78bd18
|
||||||
|
docker compose -f docker-compose.yml up -d --build backend frontend
|
||||||
|
|
||||||
|
# For the aisle data: if 0015 ran but produced wrong results,
|
||||||
|
# restore from the persist_aisle_backup tables:
|
||||||
|
docker compose exec -T db psql -U mealplanner -d mealplanner -c "
|
||||||
|
UPDATE ingredient i SET aisle = b.aisle
|
||||||
|
FROM public.ingredient_aisle_backup_0015 b
|
||||||
|
WHERE i.id = b.id;
|
||||||
|
UPDATE grocery_item g SET aisle = b.aisle
|
||||||
|
FROM public.grocery_item_aisle_backup_0015 b
|
||||||
|
WHERE g.id = b.id;
|
||||||
|
"
|
||||||
|
|
||||||
|
# Note: 0015 itself has no down-migration (raises NotImplementedError).
|
||||||
|
# A full rollback requires a DB snapshot taken before the migration ran.
|
||||||
|
```
|
||||||
|
|
||||||
|
No data loss if you ran `persist_aisle_backup.sql` first.
|
||||||
@@ -69,6 +69,13 @@ The app looks polished on the surface (Tailwind palette, clean cards, working to
|
|||||||
> - **F6** Plan-status Badge on the Dashboard header (draft / awaiting_approval / approved / rejected) now passes `aria-label="Plan status: <text>"` so screen readers announce both the category and the value. Matches the per-item approval-status pattern added in Sprint 3. **No other colour-only badges exist in the app** — every other `<Badge>` is either a count or a self-describing tag.
|
> - **F6** Plan-status Badge on the Dashboard header (draft / awaiting_approval / approved / rejected) now passes `aria-label="Plan status: <text>"` so screen readers announce both the category and the value. Matches the per-item approval-status pattern added in Sprint 3. **No other colour-only badges exist in the app** — every other `<Badge>` is either a count or a self-describing tag.
|
||||||
> - **Backend changes:** none. Deploy is frontend-only.
|
> - **Backend changes:** none. Deploy is frontend-only.
|
||||||
> - **Verification log:** `Review/sprint4-verification.md`.
|
> - **Verification log:** `Review/sprint4-verification.md`.
|
||||||
|
>
|
||||||
|
> **Sprint 5 status (commits `d78bd18` + `f740f40`, awaiting deploy):** Two §Future items, one with a critical migration fix.
|
||||||
|
> - **F5** URL week selector. `?week=YYYY-MM-DD` (Monday's ISO date) is now the canonical way to navigate between weeks. `useSearchParams` reads the URL; if absent or invalid, falls back to `isoMonday()` (so the default URL is empty). Both `Dashboard` and `ShoppingList` get a segmented control (chevron-left | 'This week'/'Current' jump button | chevron-right) in the header. The `queryKey` includes `weekStart` so each week is independently cached; mutations invalidate the right key. Empty state branches on `isCurrentWeek` ('No plan for that week' vs 'No shopping list yet'). Backend `GET /api/meals` and `GET /api/shopping-list` both accept the same `?week_start=` param; when omitted, the original "latest plan" behaviour is preserved.
|
||||||
|
> - **F2** Keyboard shortcuts. Vim-style 2-key sequences (`g d` Dashboard, `g r` Recipes, `g p` Pantry, `g s` Shopping List) navigate between the 4 main pages. `/` focuses the page's search input (Pantry + Recipes subscribe via a `useFocusSearchOnShortcut(ref)` hook). `?` shows a help banner. Suppressed inside text-entry controls and on modifier-key chords. 1.5s sequence timeout. Implementation lives in `frontend/src/hooks/useKeyboardShortcuts.ts` (the global handler) + `frontend/src/hooks/useFocusSearch.ts` (the focus bus) + `frontend/src/components/ShortcutHelpBanner.tsx` (the dialog).
|
||||||
|
> - **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).
|
||||||
|
> - **Verification log:** `Review/sprint5-verification.md`. Deploy is a single batch for Sprints 2-5: backup → migrate → rebuild backend + frontend.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+61
-14
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
You are taking over a project in mid-flight. Read `docs/ORIENTATION.md` first for the high-level. This file is the deep dive: what's real, what's stubbed, where the bodies are buried, and what to do next.
|
You are taking over a project in mid-flight. Read `docs/ORIENTATION.md` first for the high-level. This file is the deep dive: what's real, what's stubbed, where the bodies are buried, and what to do next.
|
||||||
|
|
||||||
**Date of handoff: 2026-06-03. Last commits before handoff:**
|
**Date of handoff: 2026-06-04. Last commits before handoff:**
|
||||||
```
|
```
|
||||||
|
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)
|
||||||
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)
|
||||||
427d8ac docs(review): add handoff document for UI audit work
|
427d8ac docs(review): add handoff document for UI audit work
|
||||||
e90a9d6 feat(ui): close 3 P2 audit findings + a11y sweep (Sprint 3)
|
e90a9d6 feat(ui): close 3 P2 audit findings + a11y sweep (Sprint 3)
|
||||||
@@ -299,7 +301,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-03** — UI/UX audit & fix cycle (Sprints 1, 2, 3, 4) complete. 16 findings closed (5 P0 + 6 P1 + 3 P2 + 2 §Future), code committed across 7 commits (`f3e4a44`, `36038bb`, `ccc70aa`, `f5fb755`, `e90a9d6`, `427d8ac`, `d71b67a`), build green. Sprint 1 deployed; Sprints 2, 3, and 4 awaiting deploy on the user's home server (4 is frontend-only). Backend migration `0015_normalize_pantry_aisles.py` not yet run on prod (dry-run shows 21k rows would change on dev DB). 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) 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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -315,6 +317,7 @@ A full Nielsen-10-heuristics audit of the live deployment at `http://100.108.208
|
|||||||
- `Review/sprint2-verification.md` — Sprint 2 deploy + smoke-check checklist (includes the **backend migration** step)
|
- `Review/sprint2-verification.md` — Sprint 2 deploy + smoke-check checklist (includes the **backend migration** step)
|
||||||
- `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/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):**
|
||||||
@@ -328,6 +331,8 @@ A full Nielsen-10-heuristics audit of the live deployment at `http://100.108.208
|
|||||||
| `e90a9d6` | 3 | 3 P2s + a11y: undo-toast (Dashboard refills slot; Pantry fully reversible), mobile nav nowrap, aria-current, `<main id="main-content">`, Badge `aria-label`/`icon` props |
|
| `e90a9d6` | 3 | 3 P2s + a11y: undo-toast (Dashboard refills slot; Pantry fully reversible), mobile nav nowrap, aria-current, `<main id="main-content">`, Badge `aria-label`/`icon` props |
|
||||||
| `427d8ac` | (docs) | Review/handoff-ui-audit.md |
|
| `427d8ac` | (docs) | Review/handoff-ui-audit.md |
|
||||||
| `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) |
|
||||||
|
| `f740f40` | 5 | F2 keyboard shortcuts (vim-style sequences, focus-search bus, help banner) + new `hooks/` and `components/ShortcutHelpBanner.tsx` |
|
||||||
|
|
||||||
**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`:
|
||||||
|
|
||||||
@@ -375,6 +380,36 @@ git pull
|
|||||||
docker compose -f docker-compose.yml up -d --build frontend
|
docker compose -f docker-compose.yml up -d --build frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Sprint 5 — F5 (URL week selector) + F2 (keyboard shortcuts)
|
||||||
|
|
||||||
|
Second wave of §Future. F5 is the only §Future item needing backend support; F2 is fully frontend. **Plus a critical bug fix to Sprint 2's migration 0015** that was blocking the deploy.
|
||||||
|
|
||||||
|
**F5 — URL week selector (`?week=YYYY-MM-DD`):**
|
||||||
|
- Backend: `GET /api/meals` and `GET /api/shopping-list` now accept `?week_start=YYYY-MM-DD` (FastAPI `Optional[date] Query`). When set, the response is the MealPlan for that week (any status). When omitted, behaviour is unchanged.
|
||||||
|
- Frontend: new `isoMonday()`, `parseIsoDate()`, `shiftIsoDate()`, `formatIsoDate()` helpers in `lib/utils.ts`. `meals.getPlanned(weekStart?)` and `shoppingList.get(weekStart?)` take an optional ISO date.
|
||||||
|
- Dashboard + ShoppingList both: `useSearchParams('week')` reads the URL; `queryKey: [..., weekStart]` so navigating weeks fetches the right plan; segmented control (chevron-left | 'This week'/'Current' jump button | chevron-right) in the header. Mutations invalidate the week-aware key. Empty state branches on `isCurrentWeek` ('No plan for that week' vs 'No shopping list yet').
|
||||||
|
|
||||||
|
**F2 — Keyboard shortcuts (`g d/r/p/s` nav, `/` focus, `?` help):**
|
||||||
|
- New `hooks/useKeyboardShortcuts.ts`: vim-style sequence support (1.5s timeout), suppressed in inputs/textareas/contenteditable, ref-based so the listener is registered once.
|
||||||
|
- New `hooks/useFocusSearch.ts`: CustomEvent bus for cross-page focus. Pantry + Recipes subscribe.
|
||||||
|
- New `components/ShortcutHelpBanner.tsx`: dismissible help dialog (slide-down under nav) with `role=dialog` + `aria-label`. Auto-dismisses 6s; Escape dismisses.
|
||||||
|
- App.tsx mounts `<GlobalShortcuts />` (registers the shortcuts) and `<ShortcutHelpBanner />`.
|
||||||
|
|
||||||
|
**0015 cast fix (CRITICAL — blocks Sprint 2 deploy):**
|
||||||
|
- The CASE expression in `0015_normalize_pantry_aisles.py` failed with `operator does not exist: text = boolean` on the `varchar(100) aisle` column. Sprint 2's dry-run query used a different path so the bug was not caught.
|
||||||
|
- Fixed with explicit `::varchar(100)` cast on the whole CASE expression + simplified `WHEN '' THEN NULL` branch. Verified on local dev DB: migration now succeeds; the 21,196 rows the Sprint 2 dry-run predicted normalize correctly. The deployment host would have hit the same error.
|
||||||
|
|
||||||
|
**Sprint 5 deploy (backend + frontend):**
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
docker compose exec -T db psql -U mealplanner -d mealplanner \
|
||||||
|
-f /dev/stdin < backend/scripts/persist_aisle_backup.sql
|
||||||
|
docker compose exec backend alembic upgrade head
|
||||||
|
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.
|
||||||
|
|
||||||
### 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.
|
||||||
@@ -401,21 +436,33 @@ backend/scripts/persist_aisle_backup.sql # Persistent backup
|
|||||||
### Files modified by this session
|
### Files modified by this session
|
||||||
|
|
||||||
```
|
```
|
||||||
frontend/src/App.tsx # Routes, nav, aria, QueryCache/MutationCache
|
backend/app/api/meals.py # (pre-existing WIP + Sprint 5) added ?week_start= param
|
||||||
frontend/src/api/index.ts # (pre-existing WIP, not mine)
|
backend/app/api/shopping_list.py # (pre-existing WIP + Sprint 5) added ?week_start= param
|
||||||
frontend/src/components/ui/Badge.tsx # icon + aria-label props
|
backend/alembic/versions/0015_normalize_pantry_aisles.py # (Sprint 2 + Sprint 5) cast fix
|
||||||
frontend/src/components/ui/EmptyState.tsx # optional to prop
|
frontend/src/App.tsx # Sprint 4: QueryCache/MutationCache onError; Sprint 5: GlobalShortcuts + ShortcutHelpBanner
|
||||||
frontend/src/lib/toast.ts → toast.tsx # renamed for JSX; showToast.undo() (B12), extractErrorMessage/showApiError (F7)
|
frontend/src/api/index.ts # (pre-existing WIP + Sprint 5) getPlanned/get take weekStart
|
||||||
frontend/src/lib/utils.ts # cleanDescription() (B7)
|
frontend/src/components/ui/Badge.tsx # icon + aria-label props
|
||||||
frontend/src/pages/Dashboard.tsx # B5, B6, B12, F6 aria-label, F7 handler refactor
|
frontend/src/components/ui/EmptyState.tsx # optional to prop
|
||||||
frontend/src/pages/MealDetail.tsx # B2, B3, B7, F7 submitFeedback onError
|
frontend/src/lib/toast.ts → toast.tsx # renamed for JSX; showToast.undo() (B12), extractErrorMessage/showApiError (F7)
|
||||||
frontend/src/pages/Pantry.tsx # B8, B10, B12, F7 add/remove/createIngredient onError
|
frontend/src/lib/utils.ts # cleanDescription() (B7), isoMonday/parseIsoDate/shiftIsoDate/formatIsoDate (F5)
|
||||||
frontend/src/pages/RecipeDetail.tsx # B1
|
frontend/src/pages/Dashboard.tsx # B5, B6, B12, F6 aria-label, F7 handler refactor, F5 useSearchParams + week nav
|
||||||
frontend/src/pages/Recipes.tsx # B11
|
frontend/src/pages/MealDetail.tsx # B2, B3, B7, F7 submitFeedback onError
|
||||||
frontend/src/pages/ShoppingList.tsx # B9, S3.3
|
frontend/src/pages/Pantry.tsx # B8, B10, B12, F7 add/remove/createIngredient onError, F2 useFocusSearchOnShortcut
|
||||||
|
frontend/src/pages/RecipeDetail.tsx # B1
|
||||||
|
frontend/src/pages/Recipes.tsx # B11, F2 useFocusSearchOnShortcut
|
||||||
|
frontend/src/pages/ShoppingList.tsx # B9, S3.3, F5 useSearchParams + week nav
|
||||||
frontend/src/types/index.ts # PANTRY_AISLES, RecipeIngredient extensions
|
frontend/src/types/index.ts # PANTRY_AISLES, RecipeIngredient extensions
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Files added by this session (Sprint 5)
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/hooks/useKeyboardShortcuts.ts # Sprint 5 F2: global keyboard handler
|
||||||
|
frontend/src/hooks/useFocusSearch.ts # Sprint 5 F2: focus-search CustomEvent bus
|
||||||
|
frontend/src/components/ShortcutHelpBanner.tsx # Sprint 5 F2: help dialog
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## New session: 2026-05-24
|
## New session: 2026-05-24
|
||||||
|
|||||||
+46
-4
@@ -215,7 +215,48 @@ Resolve the 14 issues (5 P0, 6 P1, 3 P2) from `Review/ui-nielsen-audit.md` in th
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sprint 4 — Polish the error path (F7 + F6)
|
## Sprint 5 — Power-user navigation (F5 + F2)
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### S5.0 · Critical fix — migration 0015 cast bug
|
||||||
|
- **File:** `backend/alembic/versions/0015_normalize_pantry_aisles.py`
|
||||||
|
- **Bug:** The CASE expression failed with `operator does not exist: text = boolean` on the `varchar(100) aisle` column. Root cause: CASE branches were inferred as different types (string vs NULL) so PostgreSQL could not unify the SET target type. The Sprint 2 dry-run (`dry_run_aisle_migration.sql`) used a different query path that happened to work, so the bug was not caught during Sprint 2.
|
||||||
|
- **Impact:** The deployment host's `alembic upgrade head` would have hit the same error and **Sprint 2 was effectively undeployable**. This blocks Sprints 2, 3, 4 from going live.
|
||||||
|
- **Fix:** Explicit `::varchar(100)` cast on the whole CASE expression; simplified the `WHEN '' THEN NULL` branch (was `NULLIF(...) IS NULL` with implicit boolean comparison). Tested on local dev DB: migration now succeeds; the 21,196 rows the Sprint 2 dry-run predicted actually normalize correctly. The deployment-host DB will follow the same path after this commit ships.
|
||||||
|
- **Why now:** Discovered when smoke-testing Sprint 5 F5 against the local backend. The local DB was 3 migrations behind (the pre-existing WIP), so running `alembic upgrade head` reproduced the error. Fixed and re-ran successfully.
|
||||||
|
- **Risks remaining:** The 21k-row update on the deployment host will lock the `ingredient` and `grocery_item` tables for the duration of the migration (a few seconds in dev; could be longer in prod). The `persist_aisle_backup.sql` script should still be run before `alembic upgrade head` for a recoverable record.
|
||||||
|
|
||||||
|
### S5.1 · F5 — Persistent week selector in URL
|
||||||
|
- **Files:** `backend/app/api/meals.py`, `backend/app/api/shopping_list.py`, `frontend/src/api/index.ts`, `frontend/src/lib/utils.ts`, `frontend/src/pages/Dashboard.tsx`, `frontend/src/pages/ShoppingList.tsx`
|
||||||
|
- **Change (one commit `d78bd18`):**
|
||||||
|
1. **Backend:** both `GET /api/meals` and `GET /api/shopping-list` now accept `?week_start=YYYY-MM-DD` (FastAPI `Optional[date] Query`). When set, the response is the MealPlan for that week (any status). When omitted, behaviour is unchanged.
|
||||||
|
2. **Frontend helpers:** `lib/utils.ts` gains `isoMonday()`, `parseIsoDate()`, `shiftIsoDate()`, `formatIsoDate()`. All UTC-based to match the backend date column.
|
||||||
|
3. **API layer:** `meals.getPlanned(weekStart?)` and `shoppingList.get(weekStart?)` take an optional ISO date string. Axios drops `undefined` params so callers can omit them.
|
||||||
|
4. **Dashboard:** `useSearchParams('week')` reads the URL; if absent or invalid, falls back to `isoMonday()` (so the default URL is empty). `queryKey: ['mealPlan', weekStart]`. A new segmented control in the header (chevron-left | 'This week'/'Current' jump button | chevron-right) lets the user step weeks. The jump button highlights primary-50 when the displayed week IS the current week; clicking it on the current week clears the `?week` param. All 5 mutations (move/approve/deny/delete/generate) invalidate `['mealPlan', weekStart]` so the right week refetches.
|
||||||
|
5. **ShoppingList:** same URL sync, same segmented control, same weekStart in queryKey. The 'no plan' empty state branches on `isCurrentWeek`: 'No shopping list yet' (current) vs 'No plan for that week' (any other week). The local-storage check-state key naturally isolates per week (it uses `shoppingList.week_start_date` which is the server's view of the plan's week).
|
||||||
|
- **Verify:** local backend smoke confirms `/api/shopping-list?week_start=2026-05-15` returns the 25-item plan for that week with aisles normalised to `Meat & Seafood`/`Pantry`/`Produce`/`Dairy & Eggs`. Migration 0015 cast fix verified end-to-end.
|
||||||
|
- **Deploy:** requires the backend rebuild + migration. Frontend changes are part of the same `git pull` + `docker compose up -d --build backend frontend` sequence.
|
||||||
|
|
||||||
|
### S5.2 · F2 — Global keyboard shortcuts
|
||||||
|
- **Files:** `frontend/src/hooks/useKeyboardShortcuts.ts` (new), `frontend/src/hooks/useFocusSearch.ts` (new), `frontend/src/components/ShortcutHelpBanner.tsx` (new), `frontend/src/App.tsx`, `frontend/src/pages/Pantry.tsx`, `frontend/src/pages/Recipes.tsx`
|
||||||
|
- **Change (one commit `f740f40`):**
|
||||||
|
1. **Hook** `useKeyboardShortcuts(map)`: lightweight global handler. Supports single keys (`/`, `?`, `Escape`) and vim-style 2-key sequences (`g d`, `g r`, `g p`, `g s`). 1500ms sequence timeout; pending prefix clears on any unrecognised key. Suppressed in inputs/textareas/selects/contenteditable, and on any modifier-key chord. Listener registered once via a ref.
|
||||||
|
2. **Hook** `useFocusSearchOnShortcut(ref)`: tiny CustomEvent bus. The global handler dispatches `mealplanner:focus-search` when the user presses `/`; pages that have a search input subscribe and focus + select.
|
||||||
|
3. **Component** `ShortcutHelpBanner`: dismissible help dialog (slide-down under nav) shown when `?` is pressed. Auto-dismisses after 6s; Escape dismisses; `role=dialog` + `aria-label` for screen readers.
|
||||||
|
4. **App.tsx:** new `GlobalShortcuts` child of `BrowserRouter` wires the 4 nav sequences, `/` → focus, `?` → help.
|
||||||
|
5. **Pantry + Recipes:** search inputs gain a `ref` and `useFocusSearchOnShortcut(ref)`. Pressing `/` on either page focuses + selects the search text.
|
||||||
|
- **Behaviour summary:** `g d` / `g r` / `g p` / `g s` → navigate to the 4 main pages. `/` → focus search (Pantry + Recipes only). `?` → help. Shortcuts are no-ops in text-entry controls.
|
||||||
|
- **Verify:** build green. Live test: open the app, press `?` to see the help banner, press `g p` to jump to Pantry, press `/` to focus the search box. Verify the same on Recipes. Verify `g` alone in a search input does NOT navigate.
|
||||||
|
- **Deploy:** frontend-only.
|
||||||
|
|
||||||
|
### S5.3 · Sprint 5 verification gate
|
||||||
|
- [x] `npm run build` green for Sprint 5.
|
||||||
|
- [x] Backend smoke on local dev DB: migration 0015 succeeds; `?week_start=` returns the right plan; new `?week_start=2099-01-01` returns null/empty as expected.
|
||||||
|
- [ ] Deploy verified (git pull, alembic upgrade head, docker compose up -d --build backend frontend, smoke checks per `Review/sprint5-verification.md`).
|
||||||
|
- [ ] No regression in Sprints 1-4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Status (2026-06-03):** ✅ Both items implemented and committed (`d71b67a`). `npm run build` green. Awaiting deploy.
|
**Status (2026-06-03):** ✅ Both items implemented and committed (`d71b67a`). `npm run build` green. Awaiting deploy.
|
||||||
|
|
||||||
@@ -260,7 +301,8 @@ Resolve the 14 issues (5 P0, 6 P1, 3 P2) from `Review/ui-nielsen-audit.md` in th
|
|||||||
- [x] Sprint 2: 6 P1 fixes + 1 bonus S3.3 — committed `ccc70aa`, deploy helper `f5fb755`. Awaiting deploy.
|
- [x] Sprint 2: 6 P1 fixes + 1 bonus S3.3 — committed `ccc70aa`, deploy helper `f5fb755`. Awaiting deploy.
|
||||||
- [x] Sprint 3: 3 P2 fixes + a11y sweep — committed `e90a9d6`, awaiting deploy.
|
- [x] Sprint 3: 3 P2 fixes + a11y sweep — committed `e90a9d6`, awaiting deploy.
|
||||||
- [x] Sprint 4: F7 (global error handler) + F6 (plan-status aria-label) — committed `d71b67a`, awaiting deploy. **No backend changes; deploy is frontend-only like Sprint 3.**
|
- [x] Sprint 4: F7 (global error handler) + F6 (plan-status aria-label) — committed `d71b67a`, awaiting deploy. **No backend changes; deploy is frontend-only like Sprint 3.**
|
||||||
- [x] `npm run build` green for all four sprints (tsc 0 errors, vite 0 errors).
|
- [x] Sprint 5: F5 (URL week selector) + F2 (keyboard shortcuts) — committed `d78bd18` (F5 + 0015 cast fix) + `f740f40` (F2). **Sprint 2's deploy was blocked on the 0015 cast bug — Sprint 5 commit fixes it.** Backend rebuild + migration required.
|
||||||
- [ ] Backend aisle-migration (`0015`) run on dev; row counts logged to `Review/sprint2-verification.md`.
|
- [x] `npm run build` green for all five sprints (tsc 0 errors, vite 0 errors).
|
||||||
- [ ] Manual smoke pass on `http://100.108.208.56:8082/` per `Review/sprint2-verification.md` (Sprint 1-3) and `Review/sprint4-verification.md` (Sprint 4).
|
- [ ] Backend aisle-migration (`0015` with cast fix) run on dev — **done on local dev host 2026-06-04**; needs running on deployment host.
|
||||||
|
- [ ] Manual smoke pass on `http://100.108.208.56:8082/` per `Review/sprint2-verification.md` (Sprint 1-3), `Review/sprint4-verification.md` (Sprint 4), `Review/sprint5-verification.md` (Sprint 5).
|
||||||
- [ ] No regressions in existing Playwright walkthrough.
|
- [ ] No regressions in existing Playwright walkthrough.
|
||||||
|
|||||||
Reference in New Issue
Block a user