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:
@@ -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.
|
||||
Reference in New Issue
Block a user