Skip to content

[obvious-fleet] Integration preview: combined stack of the 8 open product PRs (do not merge) - #17

Draft
obvious-autobuild[bot] wants to merge 82 commits into
mainfrom
obv/fridgeluck-004
Draft

obvious-autobuild[bot] wants to merge 82 commits into
mainfrom
obv/fridgeluck-004

Conversation

@obvious-autobuild

Copy link
Copy Markdown

INTEGRATION PREVIEW ONLY — THIS BRANCH MUST NOT BE MERGED. It exists solely to verify that the currently open product PRs merge and coexist in one tree. It will be closed once verification completes; it is not an integration to main.

What changed and why

Combined-stack verification preview: merges the eight currently open product PR branches into one branch so their interactions can be built, tested and reviewed in a single tree. Individual PR checks pass, but the children predate #4's latest fixes and no existing branch contains the full current stack. Merged with normal --no-ff merges, in this order, from base main @ 1151588d6bc6f5dcc3e848b6115813ff36ffbf0d:

# Branch Merged tip (exact SHA)
1 feat/minimal-product-20261007 (#4) 8c9c88fed3007b8ce0f81f8b5684461f5ff698bc
2 feat/meal-photo-confirmation-v1 (#5) 30f341847cec43b2788301ee07ee1e6a8cddb568
3 fix/recognition-precision (#8) a9df6aa39e450c0f6d704acaf99baec6b36041af
4 fix/onboarding-real-scan (#9) be5aecafcfc622117d3fee0d7c0552adcf1eb043
5 fix/ui-count-and-reduce-motion (#11) 9886dda994fad2f6bf9651b2adc759537a6ce46c
6 feat/routing-eval-harness (#7) f63affc542b70e73c6a30d3d9e0be0ea15babe2c
7 fix/vision-cancellation (#12) 954d6c63c79b744061b2973959fa414dd0f64a16
8 fix/scan-failure-report (#13) 3e6d321f1a405be4b96aab37dd69aa7ac3df5491

Merge commits on first-parent: 026cb3a, 48bd273, 28ba318, 4470931, 60725e2, bca9e6c, 6c94f98, 343ab09 (final head). PR #6 (fix/notification-settings-sendable) and all obv/* branches were intentionally excluded.

Conflict resolution (the one expected conflict): apps/ios/Feature/Estimate/ReverseScanResultsSections.swift conflicted when merging #11 into a tree containing #5. Both sides implement the same singular/plural count fix in different structures. Resolution: retained #5's version — the MealBreakdownContent-based if case .ingredients(let rows) block whose count line already carries the rows.count == 1 ? "" : "s" ternary — and verified #11's other change in that file (the previews.count singular/plural ternary) survives: it had auto-merged cleanly and was re-applied explicitly rather than lost to a wholesale --ours. Both ternaries are present in the merged file; no conflict markers remain. No other conflicts occurred.

How I verified it

  • git merge-base --is-ancestor for each of the eight tips above against this branch: all eight OK; obv/fridgeluck-walk-icon confirmed excluded.
  • git diff --check origin/main...HEAD: clean (no whitespace errors or conflict markers).
  • Duplicate-definition scan: grepped all top-level struct|class|enum|actor|protocol declarations across apps/ios for duplicate type names — none found.
  • Feature-survival checks on the combined tree:
  • Backend offline, in an isolated environment with no .env and no live keys (GEMINI_API_KEY/USDA_FDC_API_KEY/Google creds absent from the environment):
    • bun run check (tsc --noEmit): exit 0, no type errors.
    • bun test src/__tests__: 298 pass, 0 fail across 20 files, fully offline.

What is not verified

  • The iOS build and XCTest suite have not been run on this combined tree in this sandbox (Linux, no swift/Xcode). The hosted macos-26 build-and-test workflow run on this PR is the live verification of that; see its status below / in the checks section. Status at time of writing: pending — treat "pending" as unverified until it shows "passed".
  • The dead-code-advisory (Periphery) job is advisory only; its output was not evaluated.
  • No manual/UI verification of any flow; no simulator run. Existing simulator249-pass evidence attached to individual PRs is historical per-branch evidence, not a combined-head result.
  • The routing-eval harness's own hosted/runner artifacts (not just code) were not executed end-to-end beyond the offline scoring tests included above.

Assumptions

  • "env-i" in the task was interpreted as the offline, key-less environment (no .env, no live API keys); the repo contains no literal env-i file or script.
  • Merging stopped at exactly the eight branches listed; PR fix(ios): read notification permission without sending UNNotificationSettings #6 was excluded because it is not in the task's merge list.
  • The first merge commit (026cb3a) was created by a concurrent duplicate dispatch of this same task working in the same shared sandbox directory; I adopted it after verifying its parents are exactly main (1151588) and fix(ios): make the scan-to-meal loop work and log what was confirmed #4's tip (8c9c88f) with a clean tree, rather than duplicating the merge.
  • Mechanical merge handling only; no new product changes, no workflow/CI edits, and no changes to the original PRs were made.

SamGu-NRX and others added 30 commits October 7, 2026 07:27
- Open the offline cooking guide from "Start cooking", so finishing a
  recipe logs the meal and uses Kitchen inventory without the live
  backend. Cooking with Le Chef stays as a secondary option.
- Save meals with a timestamp and return the history row ID, so they
  appear in today's nutrition and Apple Health sync names the right meal.
- Scan each camera photo once, resolve camera labels to curated recipe
  ingredients before the USDA catalog, and stop OCR from mapping foods
  to different ones (oat milk to milk, mustard to soy sauce).
- Keep demo and tutorial reviews and cooking out of the real Kitchen and
  meal history.
- Reconcile a revisited scan review in one transaction, match recipes
  against the confirmed scan plus the Kitchen, and refresh Kitchen and
  Home after any inventory change.
- Mark photo-estimated amounts in the Kitchen (migration v16), and log
  the confirmed portion (v17) and cooking swaps (v18) wherever nutrition
  or consumption is computed. Reports count required ingredients only,
  matching the recipe preview.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Add meal-photo-confirmation-v1: an exact verdict preselects the top
  dish (one tap to log), review preselects it and asks the user to check
  dish and portion, and estimate-only preselects nothing and shows no
  computed macros until the user picks a dish.
- Show the no-match template's rough range only when the user asks;
  it can't be logged.
- Logging still always takes an explicit tap, and logged amounts follow
  the confirmed dish, servings and portion whatever the verdict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ith Decisions

- Run the app's Bayesian learner on frozen evaluation inputs: the
  backend copy for the original 18 diagnostic cases, and the real iOS
  ConfidenceLearningService (Swift, fresh migrated database per case)
  for the reverse-meal family. Each has a cold-prior arm and fixed
  development-only warm replays, and SQL row-count checks catch writes
  that try? would hide.
- Cross-check Swift against the TypeScript service on every case
  (identical modes, score difference 0).
- Build Decisions requests for gpt-6-luna from the derived request only,
  and parse named choices, refusals, malformed bodies, HTTP errors and
  timeouts into explicit statuses. Only a mock transport exists here.
- Lock normalized outputs and a sha256 manifest before any labels are
  read, and score them with a separate script that takes the labels path
  as an argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… derivative

The report names its parent manifest by SHA and leaves that manifest
unchanged. It records the scorer commit and input hashes, the move of the
locked files from the original worktree to this branch, and the
evaluation owner's interpretation notes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Onboarding's kitchen review showed name-based estimates (egg ~50 g), but
scan intake stored a separate catalog-unit estimate with a 30 g floor
(egg 120 g), so the Kitchen disagreed with what the user had just
confirmed. Intake now uses the review's estimator and label, so a
corrected detection is estimated from the chosen ingredient. The unused
catalog-unit estimator is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Run the main scan's recognition on the fridge and pantry photos
  separately, route results through ConfidenceRouter, and preselect only
  sure items. The 14 hard-coded detections are gone, so nothing the photos
  didn't show can reach the Kitchen.
- Show honest states: no photos, nothing found, and a failed scan with
  Try Again and Continue. If one location fails, the other's items stay
  reviewable and only the failed one is retried. A failed scan commits
  nothing.
- Keep one intake session per onboarding run, so coming back and adding
  again reconciles the Kitchen instead of adding a second copy. Choices
  for items already shown survive a rescan.
- Record whether each photo came from the camera or the library in
  FLCaptureView, and prepare library imports like other scan inputs.
- Give the photo remove and retry controls 44 pt targets and specific
  names, and announce scan results to VoiceOver.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iases

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…parts

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The report's pass-error count only covers crops where both requests
failed, so a classification failure with working text recognition was
invisible. List classification and text failures separately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… files

- Await the GRDB write inside the async VisionService test; the simulator
  build rejected the synchronous call.
- Apply the repository's swift-format configuration to the files this
  branch added.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The remove button was a 16 pt icon offset outside the thumbnail. It now
has a 44 pt target inside the thumbnail and names the photo it removes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… failures

- Reconcile fridge and pantry as separate intake sessions, and only for
  locations whose latest scan succeeded, so a failed rescan can't remove
  food the user already confirmed.
- File onboarding lots under the location they were photographed in.
  Intake now takes an explicit location; the main scan keeps inferring it.
- Keep the user's check and uncheck choices for the whole run, so a failed
  scan and retry don't reset them.
- Rescan when a late photo import changes the photos, drop stale results,
  and stop work when the user leaves mid-scan.
- Warn when some photos couldn't be read instead of reporting nothing found.
- Back from the final step returns to the review instead of replaying setup.
- Show an empty circle, not a check, when nothing is selected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SamGu-NRX and others added 27 commits October 7, 2026 22:16
The "Meal logged" alert always said "inventory updated", including when none
of the recipe's ingredients were in stock and nothing was consumed. The
message now comes from the consumption results logMeal returns: how many
ingredients came out, that none were in the Kitchen, or that the recipe has
no ingredient list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On pushed screens such as Review Ingredients, Skip tour sat 88 pt from the top, inside the
navigation bar. The bar takes every tap in that band, so Skip was visible but dead and the user
had to page through all seven cards. The top placements now start below the top safe area,
which includes the navigation bar on pushed screens and only the status bar on Home, so Home
keeps its 88 pt offset. The placement logic moves into SpotlightSkipLayout so it can be tested.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
141 of the 166 bundled recipes start their instructions with "Source: https://www.bbcgoodfood.com/...",
and all 166 number their steps. The guide used each line as a page, so step 1 was the URL and
every later page printed "1." under its own "02 of 7".

CookingGuideSteps now builds the pages: a "Source:" line becomes an attribution, and a leading
"1. " or "1) " is removed (numbers like "1.5 cups" stay). The last step shows
"Recipe from bbcgoodfood.com", linking to the original page, so the source stays visible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The preview's green check meant "required", not "you have it", so an ingredient the results
card listed as missing (Cucumber in the walk) appeared checked. The icon now follows the
search's missing IDs: a check for a required ingredient the user has, the results card's
exclamation glyph and a "Missing" line for one they don't. Optional and substituted rows get a
dashed circle, which makes no claim about the kitchen. VoiceOver reads the same state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hero is the first exact match, else the first near match, and the grid and "Almost there"
lists still contained it, so the same recipe appeared twice. RecommendationSections now owns
the hero choice (bestMatch) and the lists under it (belowBestMatch); the header counts still
use the full sections. When the hero is the only result, the divider and empty list are not
drawn, and the tutorial replay skips its "Browse Alternatives" card instead of waiting for a
list that never appears.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The chip row looped over fridge, pantry and freezer, while the On Hand list also showed an
"Other" section for items with no known storage location, so that section had no chip.
KitchenLocationOrder now gives one order to both, and the chip row shows a chip for every
location with items, "Other" last.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4138a04 moved the corner Skip button below the top safe area, but that inset was never
measured on a pushed screen, so its tests only proved the arithmetic. Skip now sits in the
card's control row as a text button in the overlay's secondary white, so no navigation bar can
cover it on any screen. It keeps the leading edge as Back appears, Back sits beside Next, and
the step dots move to their own row above. At large text sizes Skip drops below the row. The
label uses a scaling text style and a 44 pt minimum hit height.

The corner button, its placement and collision code, and SpotlightSkipLayout with its tests
are removed. SpotlightTourProgress holds the remaining rule: Skip on every step but the last,
where "Let's go" ends the tour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… trait

The chips, including the new Other chip, were about 28 pt tall to the touch. The capsule keeps
its size and the tappable area grows to 44 pt around it, which makes the chip row 16 pt taller.
VoiceOver now hears which chip is selected through the isSelected trait. There is no unit test:
a minimum frame and an accessibility trait have no pure logic to test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Recipe results, recipe preview, ingredient review and demo mode scrolled to each tutorial
highlight with the spotlightMove spring even with Reduce Motion on. All four now call
AppMotion.spotlightScroll(reduceMotion:), which returns no animation in that case, matching
what Home already did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ducts

The Inventory Deduction preview and the "Log Meal · Deduct N items" count
listed each detection at 100 g, but logging deducts the chosen recipe's
required ingredients. The preview now comes from the same requests
applyConsumption makes (shared consumptionRequests and servingFactor), each
capped at what the Kitchen holds, and leaves out ingredients with nothing in
stock. With no recipe chosen there is no preview and no count. The ingredient
breakdown uses the same servingFactor.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Prevent descriptor and truncated OCR tokens from becoming ingredient identities through catalog prefix aliases. Preserve prefix matching for multi-word windows and cover curated olive oil and black beans recognition.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The meal-photo recipe picker showed stored keys like "high_protein" and
"one_pot". RecipeTags.labels is only used for display, so it now returns
readable words, and the two screens that patched underscores themselves
use it directly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The scroll callbacks on ingredient review, demo mode, recipe preview and recipe results read
reduceMotion through the view, so the view's state held the spotlight coordinator, which held
the callback, which held the view. SpotlightCoordinator.scrollToAnchors(with:reduceMotion:after:)
now builds the callback capturing only the scroll proxy, the Bool and the delay. Each screen
sets it again when Reduce Motion changes and clears it with stopScrolling() on disappear.

No unit test: the change is which values a closure captures, with no pure logic to assert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With no exact matches, the first near match becomes the hero and, since 9cc7418, leaves
"Almost there", the only place that named its missing ingredients. The hero now shows a
"Missing" label and the same muted chips. RecipeMissingIngredientChips holds the chip style
for both places.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Selecting Other and then cooking away its last item removed the Other chip but left the
filter on Other, hiding every remaining item with no chip to clear it. The view kept its own
copy of the selection next to the view model's; now the view model's selectedLocation is the
only one, and after each refresh or removal it falls back to All when its location has no
items. The test drives the real path: an in-memory database, a depleting write, and the
inventory observer's refresh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
f206b2b put names(for:displayName:) on a View, so it inherited the main actor and its test
failed to compile under Swift 6: the nonisolated test sent a non-Sendable closure across
isolation. The helper is pure and touches no UI state, so it is now nonisolated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Logging re-resolves the chosen recipe, so picking an ingredientless recipe
logged and deducted a same-title recipe with ingredients while the screen
previewed the one picked. The lookup half of resolvePersistedRecipeID is now
a read-only existingRecipeID, and resolveForLogging returns that recipe with
its macros. The meal-photo screen resolves the choice once and uses the
result for the breakdown, macros, deduction preview and the log call.
Logging keeps its own recovery.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The alert said "no ingredient list" whenever consumption was empty, but a
recipe with only optional ingredients or zero-gram rows gives the same
result, and "none were in your Kitchen" for all-zero consumption. The
results don't say which, so the alert now reads "Your Kitchen didn't
change." in every zero case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
InventoryActiveItem title-cases ingredient names ("Olive Oil"), so the test's lowercase name
assertions failed while the reset itself worked. IDs don't depend on display formatting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (combined-stack preview)

# Conflicts:
#	apps/ios/Feature/Estimate/ReverseScanResultsSections.swift
@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d6a5b8a4-e414-43cc-990b-da88535ff7a8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants