Repository navigation
feat(meal-prep): batch prep sessions, yield to inventory & prep→shopping (009) - #31
Conversation
…ing (009) Rebases the 006-meal-prep build (34 tasks, never committed) onto current main and brings it up to the conventions the three intervening features established. Feature: - meal_prep_sessions + meal_prep_session_recipes: pick recipes with servings, schedule a prep day, and on completion yield PREP_SESSION-origin portions into prepped inventory (INV-PL-007/008 enforced by CHECKs). - Prep → Shopping (REQ-PP-019..022): aggregate a session's ingredients across its recipes, subtract pantry on hand, generate a FROM_PREP list for the gap. - Planning (REQ-PP-023..026): prepped inventory surfaced as a non-forcing suggestion; prepped portions place onto the calendar via the HAVE_IT path. - Entry point is the previously-disabled "Start a prep session" button in Pantry → Prepped. Distribution/replan (REQ-PP-005..009) stays out of scope. Fixes a real bug the mocked unit test hid: addPreppedMeal() fired a positive INITIALIZED portion event that the client guard, the DB CHECK and INV-INV-011 all reject, so direct entry threw against any real backend. The row insert already sets portions_remaining; the ledger records only consume/adjust deltas. Locked with non-mocked pgTAP coverage. Rebase adjustments: - Renumber migrations 0009-0011 → 0013-0015 (main took 0009-0012). - ensureSession() → currentUser() per feature 008. - Regenerate database.types.ts against the full 0001-0015 schema. - Satisfy the 0011/0012 security guard the migrations predated: pin search_path on detach_prep_shopping_lists, revoke EXECUTE on the SECURITY DEFINER ownership trigger, and wrap auth.uid() as (select auth.uid()) in both policies. bun run check: 0 errors · 383 unit tests pass · 155 pgTAP tests pass (9 files, security_advisors guard included) · lint clean Claude-Session: https://claude.ai/code/session_01YZxPcM8tizytxMsXNf6QeL
… nav The sidebar "Meal prep" entry was href: null, rendering muted with a "soon" badge, while the feature itself was only reachable through the "Start a prep session" button on Pantry → Prepped. Add a /prep route hosting the session overview and creation sheet (mirroring shopping/+page.svelte's sheet-overlay and inert conventions) and point the nav entry at it. The null-href branch stays as the scaffold for the next unbuilt surface. Bottom tabs are unchanged — the five mobile slots are still spoken for, so mobile reaches prep via Pantry → Prepped. bun run check: 0 errors · 383 unit tests pass · build clean · lint clean Claude-Session: https://claude.ai/code/session_01YZxPcM8tizytxMsXNf6QeL
Feature dir renamed 006 → 009, migration references renumbered 0009-0011 → 0013-0015, and ensureSession() → currentUser() in the quickstart, plan and task list, so the artifacts describe what actually shipped. Claude-Session: https://claude.ai/code/session_01YZxPcM8tizytxMsXNf6QeL
|
Claude finished @rghamilton3's task in 2m 25s —— View job Claude finished the review
|
…pping-build error handling Addresses Claude review feedback on PR #31: - completeSession() previously short-circuited on "any prepped_meals row linked to this session," so a retry after addPreppedMeal() threw partway through the yield loop would skip straight to markCompleted() and silently drop the remaining recipes' portions (FR-PP-015). Now keys the idempotency check off the set of already-yielded recipe_ids and only yields the recipes still missing a portion. - handleBuildShoppingList in src/routes/prep/+page.svelte had no try/catch, so a failed generatePrepShoppingList() call was an unhandled rejection with no user feedback and no guard against a double-click creating duplicate FROM_PREP lists. Brought it in line with the other mutation entry points in this feature (visible role="alert" error + disabled/busy button state). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBm5fpRZTqVqzTAdhsizHF

Closes #16.
Builds the meal prep subsystem (SRS §3.10): batch-cook sessions that yield ready-to-eat portions into inventory, plus the shopping and planning integrations.
What this adds
meal_prep_sessions+meal_prep_session_recipes— pick recipes with servings, schedule a prep day (weekend default, overridable), and on completion yieldPREP_SESSION-origin portions into prepped inventory with fridge/freezer shelf lives.FROM_PREPlist for the gap./preproute — the sidebar entry washref: null, rendering muted with a "soon" badge while the feature was reachable only through the "Start a prep session" button on Pantry → Prepped. It now has its own surface and the nav entry points at it.Out of scope (clarified in the spec): distribution — REQ-PP-005..009 (auto-generated meal plan, planning horizon, replan). A completed session yields portions; users place them onto days themselves.
Bug fix
addPreppedMeal()fired a positiveINITIALIZEDportion event that the client guard, the DB CHECK and INV-INV-011 all reject — so direct entry threw against any real backend. The mocked unit test hid it. The row insert already setsportions_remaining; the ledger records only consume/adjust deltas. Locked with non-mocked pgTAP coverage.Provenance
This rebases a prior build that had completed all 34 tasks but was never committed, sitting in a stale worktree based three features behind main. Rebase repairs:
0009–0011collided with main's meal_logs / reminders / security-fixes0013–0015ensureSession()removed by feature 008currentUser()database.types.tsgenerated pre-0009search_pathondetach_prep_shopping_lists, revoked EXECUTE on the SECURITY DEFINER ownership trigger, wrappedauth.uid()as(select auth.uid())in both policiesThe guard catching all four advisor regressions before they reached the dashboard is worth noting.
Verification
bun run check0 errors (564 files) · 383 unit tests pass (42 files) · 155 pgTAP tests pass (9 files,security_advisorsguard included) · production build clean · lint clean.Manually exercised end-to-end in a browser against the local stack — session creation, shopping-list build, and completion:
/prep→/signin?next=%2Fprep→ back to/prepCOMPLETEDwithcompleted_atset (INV-PL-008)FROM_PREPand linked to the session (INV-XD-004)2→3 can,1→1.5 can,3→4.5 tbsp,200→300 gFREEZER, expires +90 days (REQ-PP-012), on the "Eat this first" shelf with Defrost and "Eat 1" controlsportion_events= 0 rows — confirms theINITIALIZEDfix against a real DBhttps://claude.ai/code/session_01YZxPcM8tizytxMsXNf6QeL