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
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 updatescheduler/__main__.pydocstring if needed.- Add an inline comment that the email subject uses this same date (so they stay in sync).
- Verify: read
step_emailto confirm it usesrun.week_start_datefor the subject (it does, persteps.py:305).
S7.2 — Frontend: align isoMonday with backend
frontend/src/lib/utils.ts:44-50— renameisoMondaytoupcomingMondaywith 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): stringhelper that returns"Jun 8 — Jun 14". Used by the new nav. Dashboard.tsxandShoppingList.tsx— update imports;useSearchParams,navigateWeek,isCurrentWeekall referenceupcomingMonday()instead ofisoMonday().
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 aThis weekchip (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-503andShoppingList.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— guardedUPDATE meal_plan SET week_start_date='2026-06-08' WHERE week_start_date='2026-06-05';withSELECT 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 buildgreen.- Write
Review/sprint7-verification.mdwith deploy + smoke checks (backend + frontend, no migration, plus the SQL update step). - Backend smoke:
curl /api/meals?week_start=2026-06-08returns 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 buildgreen.Review/sprint7-verification.mdexists.- 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, aconftest.pywith a transactional DB fixture, and smoke tests covering: app import,/health,/health/db, every router's GET list endpoint, Alembicupgrade headround-trip on a throwaway DB. Add.github/workflows/ci.ymlrunning lint + pytest + frontendnpm run build. - R1-B: Auth dependencies on existing routers. Implement an
app.securitymodule with: (1)require_admindep — bearer token compared tosettings.ADMIN_TOKEN, applied to ALL/api/admin/*routes; (2)require_sessiondep — 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.examplewithADMIN_TOKEN. Document the model indocs/SECURITY.md. - R1-C: Make
/api/admin/scrapeasync. Convert the endpoint to enqueue a background job (FastAPIBackgroundTasksfor 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-scopeddbis 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
LuckyCaliforniaScraperagainsthttps://luckysupermarkets.comonce, capture the raw HTML/PNG tobackend/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), anapp/services/approval.pythat issues per-voter signed tokens (TTL, single-use), the GET confirmation page + POST submit handler (the routes already exist as stubs inmeals.py), and a CLI scriptscripts/send_test_approval.pythat creates a fake meal plan, emails one voter, and verifies the click→POST→DB write path end to end against a sandboxed inbox orMAIL_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 1for 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 §6not green → no R3 work begins.