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