Files
Meal-Planner/Review/sprint5-verification.md
T
admin 2029e80c5a 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 the d78bd18 + f740f40 rows 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 the d78bd18 + f740f40 rows. 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.
2026-06-04 12:42:54 -07:00

8.2 KiB

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.

# 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

# 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:

# 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:

# 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.