Files
Meal-Planner/.agent/plan.md
T
MealPlanner 09c7525a12 fix(ui): align 'this week' to upcoming Monday (Sprint 7)
User report 2026-06-05: 'webui Meal Planner page is empty' on Friday
morning after the Friday email went out. Root cause: the orchestrator
keyed plans by the most-recent-Friday while the frontend's isoMonday()
returned the most-recent-Monday — a 7-day mismatch on Fridays.

Fixes (one semantic across the stack):
- runner._current_week_start() returns the upcoming Monday (today if
  Mon, else the next Mon). The Friday email subject
  ('Meal plan for week of <date>') automatically picks up the new
  value via run.week_start_date.
- frontend isoMonday -> upcomingMonday (same logic; renamed for
  intent). isoMonday kept as a deprecated alias.
- New WeekRangeNav component (Dashboard + ShoppingList share it).
  Renders [<]  Jun 8 - Jun 14  [>] with clickable chevrons and a
  clickable range label that jumps to the upcoming week. Replaces
  the Sprint 5 inline segmented control on both pages.
- New formatWeekRange(mondayIso) helper (UTC-stable; uses
  timeZone: 'UTC' so the rendered date matches the stored ISO date
  regardless of viewer TZ; closes a latent bug in formatIsoDate too).
- New SQL fix script that retargets the user's 3-pending-items plan
  from 2026-06-05 (Friday-keyed) to 2026-06-08 (upcoming Monday).
  Idempotent + transaction-wrapped. Optional block for 2026-05-29.

No backend migration. No new dependencies. Deploy is git pull +
run the SQL fix + docker compose up -d --build backend frontend.
See Review/sprint7-verification.md for the full deploy + smoke flow.

Files:
- backend/app/services/orchestrator/runner.py:20-35
- backend/scripts/fix_2026_06_05_to_2026_06_08.sql (new)
- frontend/src/lib/utils.ts:43-130
- frontend/src/components/WeekRangeNav.tsx (new)
- frontend/src/pages/Dashboard.tsx (3 call sites + 1 segmented control)
- frontend/src/pages/ShoppingList.tsx (5 call sites + 2 segmented controls)
- Review/{sprint7-verification,ui-nielsen-audit,handoff-ui-audit}.md
- fix-ui-audit.md
- docs/HANDOFF.md
- .agent/{plan,context}.md
2026-06-05 07:46:55 -07:00

7.5 KiB

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-labels; 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.