# Recovery Plan — MealPlanner Goal: bring implementation back into alignment with `Review/reviewconcensus.md`. Stop building forward features until the deferred-risk spikes and the verification matrix pass. ## Active sprint: Sprint 7 — Fix webui "empty meal plan" (date-semantics mismatch) **Owner:** this agent. **Status:** in progress (approved by user 2026-06-05, code not yet written). **Tracking:** `Review/sprint7-verification.md` (deploy + smoke checks), `.agent/plan.md` (checklist), `.agent/context.md` (decisions + open Qs). Root cause: today is Fri 2026-06-05. The orchestrator sends Friday emails for the **upcoming** Mon-Sun week and keys the plan by the upcoming Monday. The frontend's `isoMonday()` returns the **current** calendar week's Monday, which is 4 days behind. So the email says "Meal plan for week of 2026-06-08" but the webui opens on "week of 2026-06-01" — no plan, empty state. ### S7.1 — Backend: `_current_week_start()` returns upcoming Monday - [ ] `backend/app/services/orchestrator/runner.py:20-24` — change body to: `today.weekday() == 0 → today; else today + timedelta(days=(7 - today.weekday()))`. Add docstring "the upcoming Mon-Sun week the email advertises." Also update `scheduler/__main__.py` docstring if needed. - [ ] Add an inline comment that the email subject uses this same date (so they stay in sync). - [ ] Verify: read `step_email` to confirm it uses `run.week_start_date` for the subject (it does, per `steps.py:305`). ### S7.2 — Frontend: align `isoMonday` with backend - [ ] `frontend/src/lib/utils.ts:44-50` — rename `isoMonday` to `upcomingMonday` with new logic (today if Mon, else next Mon). - [ ] Keep the old `isoMonday` (calendar week) for any code that needs it; the function is currently only used for the F5 default, so rename is safe. - [ ] Add `formatWeekRange(mondayIso: string): string` helper that returns `"Jun 8 — Jun 14"`. Used by the new nav. - [ ] `Dashboard.tsx` and `ShoppingList.tsx` — update imports; `useSearchParams`, `navigateWeek`, `isCurrentWeek` all reference `upcomingMonday()` instead of `isoMonday()`. ### S7.3 — Frontend: new `WeekRangeNav` component - [ ] `frontend/src/components/WeekRangeNav.tsx` — new file. Props: `{ weekStart: string; isCurrentWeek: boolean; onPrev: () => void; onNext: () => void; onJumpHome: () => void }`. - [ ] Renders: `[<]` button (chevron-left), a button showing the formatted range (e.g. `Jun 8 — Jun 14`, clickable → jump home), `[>]` button (chevron-right), and a `This week` chip (visible only when `!isCurrentWeek`). - [ ] All buttons have `aria-label`s; clickable range label says "Jump to upcoming week". - [ ] Acceptable to use lucide-react `ChevronLeft` / `ChevronRight` (already imported in Dashboard/ShoppingList). - [ ] Replaces the inline segmented control in `Dashboard.tsx:479-503` and `ShoppingList.tsx:259-283`. ### S7.4 — Data: migrate 2026-06-05 plan to 2026-06-08 - [ ] `backend/scripts/fix_2026_06_05_to_2026_06_08.sql` — guarded `UPDATE meal_plan SET week_start_date='2026-06-08' WHERE week_start_date='2026-06-05';` with `SELECT COUNT(*)` first. Operator runs on deployment host. - [ ] Decide: also migrate older Friday-keyed plans (2026-05-29 was a Friday). User has the option; for now include it as a separate guarded statement (commented out, opt-in) in the same script. ### S7.5 — Verify - [ ] `npm run build` green. - [ ] Write `Review/sprint7-verification.md` with deploy + smoke checks (backend + frontend, no migration, plus the SQL update step). - [ ] Backend smoke: `curl /api/meals?week_start=2026-06-08` returns the user's 3 pending items. - [ ] Frontend smoke: load `/` (no `?week=` param) on a browser; the dashboard opens on the upcoming Mon-Sun plan with 3 items. ### S7.6 — Docs - [ ] Add Sprint 7 status block to `Review/ui-nielsen-audit.md` (top of file, after Sprint 6 block). - [ ] Add Sprint 7 plan section to `fix-ui-audit.md`. - [ ] Add Sprint 7 section to `Review/handoff-ui-audit.md` (new "Active sprint" callout near the top). - [ ] Add Sprint 7 section to `docs/HANDOFF.md` (replace the "Last updated" line at line 305). ### Done when (Sprint 7) - All 6 checkboxes above ticked. - `npm run build` green. - `Review/sprint7-verification.md` exists. - All four doc files have a Sprint 7 status block. - User reports the webui no longer shows empty after pulling the S7 batch + running the SQL fix. ### Out of scope (Sprint 7) - Thread 2: cross-week "rejected means" semantics. Defer until after S7 deploys. - Thread 3: §Future backlog (F1 onboarding, F8/F9 proposals, dead-CTA wire-up). - The 2026-05-29 plan migration (operator opt-in; documented but not auto-run). --- ## Phase R1 — Stabilize (parallel-safe) - [ ] R1-A: Verification harness. Add `backend/tests/` with pytest config, a `conftest.py` with a transactional DB fixture, and smoke tests covering: app import, `/health`, `/health/db`, every router's GET list endpoint, Alembic `upgrade head` round-trip on a throwaway DB. Add `.github/workflows/ci.yml` running lint + pytest + frontend `npm run build`. - [ ] R1-B: Auth dependencies on existing routers. Implement an `app.security` module with: (1) `require_admin` dep — bearer token compared to `settings.ADMIN_TOKEN`, applied to ALL `/api/admin/*` routes; (2) `require_session` dep — signed-cookie session (itsdangerous, key = `SECRET_KEY`) for profile/pantry/recipes/meals/shopping-list mutations; reads stay open inside the trusted network. Per-voter approval token flow stays as-is. Update `.env.example` with `ADMIN_TOKEN`. Document the model in `docs/SECURITY.md`. - [ ] R1-C: Make `/api/admin/scrape` async. Convert the endpoint to enqueue a background job (FastAPI `BackgroundTasks` for now; APScheduler later). Endpoint returns 202 + `scrape_log_id`; status polled via `/api/admin/logs/{id}`. ScraperService must open its own DB session inside the task (the request-scoped `db` is gone by then). ## Phase R2 — De-risk deferred work (parallel-safe, must run BEFORE further feature work per review §2.4) - [ ] R2-A: Live-scrape spike. Run `LuckyCaliforniaScraper` against `https://luckysupermarkets.com` once, capture the raw HTML/PNG to `backend/tests/fixtures/lucky_ca/`, write a unit test that parses the captured fixture (no live network in CI). Document selector decisions in `.agent/context.md`. If the page can't be parsed, file the schema impact before going further. - [ ] R2-B: Email + approval round-trip spike. Implement minimal SendGrid sender (`app/services/email.py`), an `app/services/approval.py` that issues per-voter signed tokens (TTL, single-use), the GET confirmation page + POST submit handler (the routes already exist as stubs in `meals.py`), and a CLI script `scripts/send_test_approval.py` that creates a fake meal plan, emails one voter, and verifies the click→POST→DB write path end to end against a sandboxed inbox or `MAIL_BACKEND=console`. Goal: prove the schema (family_member, approval_token tables) survives one full round trip BEFORE building Phase 4/5/9. ## Phase R3 — Resume feature work (sequential, only after R1+R2 green) - [ ] R3-A: Phase 4 Recipe Engine — search, tagging, never-suggest filter. - [ ] R3-B: Phase 9 Meal Planner generation algorithm. - [ ] R3-C: Phase 6 SendGrid templated emails (proposal, reminder, confirmation). - [ ] R3-D: Phase 8 Feedback UI. - [ ] R3-E: APScheduler with `--workers 1` for weekly scrape + plan generation + email send. - [ ] R3-F: Phase 10 image strategy. ## Halt conditions - R2 spikes fail → stop, propose schema/spec change, await approval. - Verification matrix in `Review/reviewconcensus.md §6` not green → no R3 work begins.