Skip to content

alpha: the minimal-product stack (#4, #5, #8, #9, #11, #7, #12, #13) - #87

Merged
SamGu-NRX merged 82 commits into
alphafrom
fleet/alpha-stack-20261011
Oct 11, 2026
Merged

SamGu-NRX merged 82 commits into
alphafrom
fleet/alpha-stack-20261011

Conversation

@SamGu-NRX

Copy link
Copy Markdown
Owner

Moves the integration branch alpha to the app's latest version, the minimal-product stack, instead of main.

The stack is merged in the order its previous run recorded (~/.long-run/fridgeluck/STATE.md): #4, then #5, #8, #9, #11, then #7, #12, #13. Every PR head matches the head recorded there, and all were green on macos-26 on 2026-10-08. The one documented conflict, #5 against #11 in ReverseScanResultsSections.swift, keeps #5's side; both PRs made the same plural fix. #6 is left out because main builds on Xcode 26.6 without it.

This PR exists so iOS CI builds and tests the composed head. Once CI is green, alpha is fast-forwarded to this head and the PR is closed. It is not merged through GitHub, and nothing goes into main.

🤖 Generated with Claude Code

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 25 commits October 7, 2026 22:19
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>
…into fl-lead/alpha-compose

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

coderabbitai Bot commented Oct 11, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • 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.

@SamGu-NRX
SamGu-NRX merged commit 2e77938 into alpha Oct 11, 2026
3 checks passed
@SamGu-NRX
SamGu-NRX deleted the fleet/alpha-stack-20261011 branch October 11, 2026 11:48
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

This shows as merged only because alpha was fast-forwarded to this PR's head (2e77938) with a plain push after iOS CI passed (build-and-test, run 38136461873). It was not merged through GitHub, nothing was force-pushed, and main is unchanged.

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.

1 participant