Repository navigation
feat(ios): cook a catalog recipe end to end, logged with frozen nutrition - #174
obvious-autobuild[bot] wants to merge 11 commits into
Conversation
Finishing a catalog recipe now writes one history row linked to the (catalog_id, revision) that was cooked, in the same transaction. The meal's nutrition reports through that frozen link, so a later revision bump can never rewrite what an older meal says it was. - recipes_cooking_catalog_refs_v1: history -> catalog_id + revision link, registered after the existing recipes_ catalog migrations - PersonalizationService.recordCooking writes the link and names the catalog ref in the recipe_cooked event; a half-specified identity (id without revision) is refused fail-closed - MealLogService.logMeal carries catalogId/catalogRevision through - CookModeViewModel cooks structured catalog steps from the packet, resumes catalog sessions with the exact revision ref, routes the finish path to a catalog-aware logger, and Dependencies.ephemeral now reports the unknown substitute as unknown (nil blocks) - CatalogCookFlowTests: the end-to-end cook (timer, thermometer safety check, remember), frozen nutrition across a revision bump, timer relaunch, fail-closed seams, and the legacy parse path Co-authored-by: Sam Gu <sgu07966@gmail.com>
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
98beb69 added a \`catalog\` property that referenced CatalogCookContent without defining it (CI: cannot find type 'CatalogCookContent') and dropped the rest of the view-model wiring in an interrupted write. This lands the full wiring: - CatalogCookContent: catalog id, revision, structured CookSteps from the installed bundle-v2 revision - CookModeViewModel: catalog init parameter; session ref becomes (catalog, catalogId, revision) with packet steps for a catalog cook (legacy keeps parsed instructions + integer-id ref); the finish path routes to Dependencies.logCatalogMeal — nil logger on a catalog cook refuses the log instead of writing a row with no revision link - CookingGuideView: resolves catalog content once through the legacy bridge (catalogRecipeID -> catalogCookPacket), feeds the session VM and hands the identity to the celebration - CookingCelebrationView: carries catalogId/catalogRevision and logs through MealLogService.logMeal with them — the shipped cook entry path now writes the revision-linked meal log end to end Co-authored-by: Sam Gu <sgu07966@gmail.com>
3cfdf7a landed the struct, initializer, session ref, and finish routing, but a same-file edit race dropped four pieces CI then failed on. This commit completes them: - CookModeViewModel.Dependencies.logCatalogMeal: the revision-aware logger the finish path dereferences (CI: no member 'logCatalogMeal') - CookingGuideView.catalogContent: the legacy-bridge resolution the VM call and celebration hand-off reference (CI: cannot find in scope) - the guide's VM call passes arguments in declaration order (CI: argument 'dependencies' must precede argument 'catalog') - CookingCelebrationView: the real logger closure and saveAndDismiss pass catalogId/catalogRevision through to MealLogService.logMeal (CI: expects 7 arguments, but 5 were used / missing arguments) Co-authored-by: Sam Gu <sgu07966@gmail.com>
The app target compiles at 23705f8; CI now fails only in the test file: - CatalogCookFlowTests calls the @mainactor view model from nonisolated sync methods (CI: call to main actor-isolated ... in a synchronous nonisolated context). The suite now runs on the main actor, the repo's convention for actor-isolated harnesses. - the harness logMeal call omitted capturedImage, so the capturedImage and imagePath overloads both matched with defaulted params (CI: ambiguous use of 'logMeal'); passing capturedImage: nil pins it. Co-authored-by: Sam Gu <sgu07966@gmail.com>
The app and catalog targets compile at 95ec322; CI now fails in the inherited TasteEventStore tests — the cookedEvent helper is declared -> TasteEvent while constructing a TasteEventRecord, which only compiles in PR #172's merge preview because the trunk branch supplies its fixed copy (9de2985). The catalog-recommend base carries the pre-fix file, so PR #174's merge preview fails. Apply the same one-line return-type correction the trunk made, so the branch matches the trunk-fixed version and future base merges resolve cleanly. Co-authored-by: Sam Gu <sgu07966@gmail.com>
The taste helper fix unblocked compilation; the first full run then failed four catalog cook tests at fixture hydration: recipe rcp-900001 carried one ingredient line, below the compile-time minimum of two ingredient lines and two steps (RecipeBundleV2). Add a butter line alongside the egg; the egg stays first so step uses indices and the existing assertions are unchanged. Fixture JSON verified to parse with resolvable line keys and valid uses indices. Co-authored-by: Sam Gu <sgu07966@gmail.com>
The fixture fix unblocked hydration; the run then failed the cook and relaunch tests because startOrResume ignored the catalog content: the session ref was built from the legacy integer id (taste ref came back "1", not rcp-900001) and both start and resume attached steps parsed from the legacy free-text instructions (1 step, no stated timer, so no suggested timer persisted and resume started over). A catalog cook now carries the packet's structured steps and the exact (catalogId, revision) in the session ref, start and resume alike; legacy recipes keep the previous behavior unchanged. Co-authored-by: Sam Gu <sgu07966@gmail.com>
…r duration The session wiring fix left two failures. recordCooking built the recipe_cooked ref from the legacy integer id and revision 1 even when a catalog identity was passed — its own docstring already promised the catalog ref — so the taste event named "1" instead of rcp-900001; the ref now uses the catalog id/revision when present, for the cooked event and its swap events alike. And CookTimerStore.create used the init that leaves totalSeconds nil although the duration was in hand, so a restored timer kept its deadline but rendered deadline-only; create now records the duration, matching what adjust already did. Regenerated the taste-store-check mirror and its SOURCES.txt pin per Scripts/refresh.sh after the PersonalizationService change. Co-authored-by: Sam Gu <sgu07966@gmail.com>
|
Independent review (Obvious, Autobuild + Max) Verdict: changes needed — the service-layer work is correct, transactional, and genuinely tested, but the catalog cook flow only engages through the legacy-id bridge, and the frozen-nutrition read path has no production consumer. Both gaps sit in the glue no test exercises. Findings1. [Medium] The end-to-end catalog cook never engages for catalog-native recipes — for most of the shipped catalog, the exact defect the body says is fixed still happens.
Smallest fix: when 2. [Low] The frozen-nutrition read path has no production consumer.
Smallest fix: prefer 3. [Low] The body's drift sentence overstates the pre-PR behavior. "Any later revision bump silently rewrote what every past meal reported as its nutrition": journal and daily-total macros come from legacy Things I checked and consider sound (not findings): the fail-closed half-identity guard; the link written in the same transaction as the history row; What I verified (commands and results)
What I couldn't verify
|
Two review findings, both in glue no test exercised: the guide resolved the cook packet only through a legacy integer id, so catalog-native recommend results (Recipe(id: nil, ...)) fell back to the legacy path with a synthetic ref, and the journal computed macros from legacy ingredient rows instead of the frozen snapshot the meal was logged against. The gate is extracted from CookingGuideView into a static helper so tests drive the exact production resolution; the journal test logs a real catalog meal through the full flow and asserts the journal reports the snapshot. Both new tests fail at this head — the helper and journal still resolve the old way; the next commit fixes them. Co-authored-by: Sam Gu <sgu07966@gmail.com>
The fail-first run proved both review findings (38835855949 red at 5c8ac46): the catalog-native gate test got nil content, and the journal reported 0.0 kcal where the frozen snapshot says 840. The guide now resolves catalog content from the result's own catalog ref when there is no persisted integer id — the shape the recommend pool builds for every catalog-native recipe — keeping the legacy bridge and the parsed-instructions fallback for drafts and unloadable packets. The journal reports the frozen per-serving nutrition snapshot at the linked revision, scaled by servings consumed and portion multiplier, and keeps the legacy ingredient math for cooks with no catalog link. The bridge test inserts the legacy recipe before referencing it (FK order). Co-authored-by: Sam Gu <sgu07966@gmail.com>
RecipeCatalogNutrition lives in the FLFeatureLogic package module; RecipeRepositoryCatalog already imports it, UserDataRepository did not. Co-authored-by: Sam Gu <sgu07966@gmail.com>
What changed and why
Cooking a catalog recipe previously logged the meal with a legacy integer recipe id at revision 1: no link to the catalog recipe, no revision, and a
recipe_cookedtaste event that named the wrong identity. A later revision bump did not rewrite the storedrecipes_catalog_nutritionrows (they are insert-once) — but with no link from a meal to the revision it was cooked from, nothing could resolve a past meal's nutrition back to the snapshot it was actually cooked against: reads reported against whatever was currently installed, and surfaces computing from legacy ingredient rows (the journal) followed the installed data instead.Now the finish path logs the meal against the exact revision that was cooked:
recipes_cooking_catalog_refs_v1(new migration inRecipeCatalogMigrations, after the existingrecipes_migrations): one row percooking_historyentry —history_id(PK, cascade),catalog_id(restrict),revision(check ≥ 1) — plus a(catalog_id, revision)index.PersonalizationService.recordCookingwrites the link inside the same transaction as the history row and names the catalog ref (catalog_id,revision) in therecipe_cookedevent (and its swap events) instead of the integer id. A half-specified identity (id without revision or the reverse) throwsCookingLogError.catalogIdentityIncompletebefore any write — fail closed.MealLogService.logMealcarriescatalogId/catalogRevisionthrough both overloads intorecordCooking.RecipeRepository.catalogNutrition(forHistoryID:)resolves the meal's per-serving nutrition by joining the link torecipes_catalog_nutritionat that revision — the frozen snapshot, never the currently installed revision.CookModeViewModeltakes an optionalCatalogCookContent(catalog id, revision, structuredCookSteps fromRecipeRepository.catalogCookPacket): the session ref becomes(catalog, catalog_id, revision), steps come from the packet on both start and resume instead of parsing instructions, and the finish path routes to a newDependencies.logCatalogMeal(nil logger → nothing written rather than a half-linked row).Dependencies.ephemeral().substituteIsAllergennow reports unknown (nil→ blocks) instead offalse(clears) — the guide's production wiring already returnednil; the ephemeral default contradicted the fail-closed contract.CookTimerStore.createrecords the timer's full duration (totalSeconds) likeadjustalready did, so a timer restored after relaunch renders its drain ring instead of falling back to deadline-only.Independent review findings, fixed
The 2026-10-16 independent review (changes-needed verdict) drove commits
5c8ac46,770ab5e, andd6252a7:CookingGuideView.catalogContentrequiredscoredRecipe.recipe.id, but the recommend pool builds catalog-native results asRecipe(id: nil, …)— every catalog-native recipe would have cooked the legacy path with a synthetic integer ref, a defect no test could see because tests built the view model directly. The gate is now extracted intoCookingGuideView.makeCatalogContent(for:in:)— a bridged legacy recipe resolves throughrecipes_catalog.legacy_recipe_id, a catalog-native result through its own match's catalog ref, and unsaved drafts (no id, no match) stay on the parsed-instructions path. Covered by two new tests (catalog-native without a legacy id, and the bridged path).UserDataRepository.computeConsumedMacroscomputed macros from legacy ingredient rows, which a bundle apply never writes for catalog recipes — the journal reported 0 for every catalog meal. It now prefers the frozen per-serving snapshot at the linked revision, scaled by servings consumed × portion multiplier (same join as the accessor, cross-referenced in both docstrings), and keeps the legacy ingredient math as the fallback for cooks with no catalog link. Covered by a test that cooks a real catalog meal end to end and asserts the journal reports snapshot × servings.All three fixes are fail-first: commit
5c8ac46pushed the tests first and hosted CI went red exactly where predicted — the catalog-native gate test got nil content, and the journal test read 0.0 kcal against an expected 840.0 (run 38835855949). The fix commits turn them green.How I checked it
d6252a7(run 38838347813):build-and-testpass 4m55s,dead-code-advisorypass 3m03s. The fail-first run at5c8ac46was red exactly as designed (run 38835855949: only the two new gate/journal tests failed, for the predicted reasons); the first fix attempt at770ab5efailed fast on a missingFLFeatureLogicimport (run 38837004821), fixed atd6252a7. The earlier head4fd46bcwas green (run 38823477054:build-and-testpass 7m31s,dead-code-advisorypass 4m57s). No toolchain on the working sandbox (Linux; no Xcode), so nothing was compiled or run locally — CI on this PR's head is the compiler and test verifier.CatalogCookFlowTests(app-hosted, run by CI): the full cook flow through the real view model over a hydrated bundle-v2 fixture — suggested 90 s timer from the catalog step, persistedcook_timerrow with its full duration restoring after a simulated relaunch, thermometer step surfacing as a safety check (74 °C + pinned source), one history row linked to revision 1, logged nutrition equal to the revision-1 snapshot, and the cooked event carrying(rcp-900001, 1, catalog)with rating; a revision-2 loader bump leaving the old log's nutrition byte-equal; unknown substitute blocking; half-specified catalog identity refused; legacy recipes keeping the parsed-instructions path and integer-id ref. Review additions: the guide's gate resolving for a catalog-native result without a legacy id, the gate resolving through the legacy bridge, and the journal reporting the frozen snapshot for a catalog meal.recipes_catalog_nutritionjoin patterns inRecipeRepositoryCatalog; the new identifier isrecipes_-prefixed and registered inside the contiguous RecipeCatalog subsystem block (MigrationSeamTestspass on CI).Commit trail
Eleven commits; the failure history is documented here so reviewers can skip to the interesting parts:
98beb69feature commit (migration, link, catalog nutrition read path, catalog-aware guide/celebration wiring, ephemeral allergen fix, tests) — CI lost parts of the intended wiring;3cfdf7a+23705f8re-landed it.95ec322test fixes:@MainActorisolation and a pinnedlogMealoverload.9a3fc2ealigns the inheritedTasteEventStoreTestshelper with the trunkcookedEventfix (base branch had not picked it up yet).39a347cfixture: the catalog recipe needed two ingredient lines to meet bundle-v2 validation.1cdce5dthe substantive fix the tests drove out:startOrResumewas ignoring the catalog content — session ref from the legacy integer id, steps parsed from legacy instructions on both start and resume. A catalog cook now runs the packet's structured steps and carries(catalogId, revision)in the session ref.4fd46bctwo more the tests drove out:recordCookingbuilt the taste ref from the integer id despite its own docstring, and timercreatedropped the duration. Includes the regeneratedtaste-store-checkmirror + pin perScripts/refresh.sh.5c8ac46fail-first review tests (red by design — run 38835855949).770ab5ethe review fixes: catalog-native gate resolution in the guide, the journal's frozen-snapshot consumer, and FK-safe insert order in the bridge test.d6252a7the missingFLFeatureLogicimport the770ab5ecompile exposed.What I couldn't check
NoopTimerSchedulerin tests) and the drawer/guide UI over catalog packets. No Xcode/simulator here; iOS UI was not visually exercised.recipe.id— noted by the review as adjacent, not changed here.Assumptions
recipes_catalog_nutritionrows are already insert-once, so the link is the freeze; values are not duplicated into the history row.logCatalogMealbeing nil on a catalog session is a wiring error: the log is refused (no row) rather than written without its revision link.Human author: Sam Gu (sgu07966@gmail.com)
🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact