Skip to content

docs(planning): FL4 operational Home flow — spec for state map, read model, gate replacement - #85

Draft
SamGu-NRX wants to merge 6 commits into
obv/fl-next-historical-nutrition-r1from
obv/fl-l4-operational-home
Draft

SamGu-NRX wants to merge 6 commits into
obv/fl-next-historical-nutrition-r1from
obv/fl-l4-operational-home

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Implements the non-visual core of the FL4 operational Home flow (planning/operational-home-flow-spec.md). This PR was spec-only until 332ac36; the implementation now follows in three milestones plus a clean base merge. Full implementation record: planning/operational-home-flow-implementation.md.

Summary

Home gated its dashboard behind tutorialProgress.isComplete (HomeDashboardView.body forked on @AppStorage("tutorialProgressStorage") quest progress), so a user who stocked a real fridge but skipped the tour saw quest cards instead of a kitchen. The spec replaces that fork — inside Home-owned files only — with a pure five-state map (emptyNew, partlyStocked, cooking, returning, error) built from public repository outputs, plus a read model/view model pair and a Linux home-flow-check package cloning the proven historical-nutrition-check pattern. That replacement is now implemented and Linux-verified (38/38 tests).

Behavior change, in one line: tutorial quest cards appear only for a genuinely empty-new kitchen with an incomplete tour; a stocked or returning cook now sees their kitchen even if they skipped the tour; a live cook sees the cooking context.

Decisions made

  • Gate replacement happens inside HomeDashboardView.swift; ContentView.swift and the 13-member Home init surface stay byte-identical.
  • Learning progress is removed from dashboard presentation only; TutorialProgress/TutorialQuest types and the storage key keep their current write behavior (quest completion, skip, reset), so Progress-tab gating is untouched.
  • cooking state reads only the Live Assistant coordinator's context surfaced as a value — no persisted active-cook record is invented (none exists on this branch).
  • Freshness is expiry-date based only; unknown expiry is nil, never zero.
  • Native SwiftUI render/keyboard/Dynamic Type/VoiceOver checks are recorded as deferred — this workspace has no macOS host; no web mockup may stand in for them.
  • "Hosted app checks" from the original brief has no in-repo referent (root package.json is an unrelated Electron manifest); the check record covers package tests plus repo audit scripts.

Implementation (this branch, in order)

  • 332ac36 — HomeFlowModels.swift: the pure five-state map with explicit primary actions; tutorial progress is not an input anywhere in the flow core (compile-time surface); unknown expiry is nil, never zero. HomeFlowReadModel.swift: one repository-backed read pass producing both the state-map inputs and the existing dashboard snapshot from the same reads, plus the pure HomeLoadGeneration staleness guard; Combine-free so it compiles on Linux. HomeFlowViewModel.swift: the thin observable shell (app target only — ObservableObject does not exist on Linux) wiring the same GRDB cooking-history and inventory live observers Home already used to the guarded load. apps/ios/Tools/home-flow-check/: package mirroring historical-nutrition-check — Scripts/refresh.sh copies 24 real production sources into the ignored Sources/HomeFlowCheck/Real/; tests drive them through real migrated GRDB databases.
  • 42ff652 — the gate in HomeDashboardView.body now resolves HomeFlowStateMap.state(from: snapshot, cookContext:) and switches on the result. A snapshot-level projection (state(from:cookContext:)) documents the shipped view's seam: same precedence rules, evaluated over the snapshot the sections already consume; it never reports .error — the nil-snapshot branch keeps owning that. The cooking context is built solely from LiveAssistantCoordinator.matchedRecipeContext (the surfaced value), mapped to HomeCookContext(recipeName:); coordinator internals are never read.
  • d2d1d57 — planning/operational-home-flow-implementation.md: state map, read model, generation guard, wiring, and verification documented so the redesigned Home UI can sit on this core unchanged.
  • 427c5c7 — merge of base c9555fc (obv/fl-next-historical-nutrition-r1 moved by one commit: CutoverParityTests.swift; clean merge, no rebase, no history rewrite). Suite re-proven green on the merged tree.

Proof

All commands run at head 427c5c7ac764f4d29e198b25cfe8d3a61f51f11a on Linux x86_64, Swift 6.4.

1. Full suite — bash apps/ios/Tools/home-flow-check/Scripts/refresh.sh && swift test --package-path apps/ios/Tools/home-flow-check

refreshed 24 real sources into Sources/HomeFlowCheck/Real
Test Suite 'All tests' passed at 2026-10-11 13:51:45.508
	 Executed 38 tests, with 0 failures (0 unexpected) in 0.33 (0.33) seconds

Coverage: 23 state-map tests (every state, every precedence overlap, every primary action, the cook-context arriving/leaving transition seam, the no-tutorial-input contract), 6 fork-projection tests (including agreement with the canonical HomeFlowInputs path and never-error for non-nil snapshots), 9 read-model tests (real-DB cold start, stocked kitchen with a makeable recipe, unknown-expiry-nil, fridge shelf-life derivation (+7-day writer default, pinned as behavior), use-soon surfacing, pre-onboarding stock hiding, generation guard, read→gate seam).

2. Byte-identity proof — git diff 372f6f3..HEAD

$ git diff 372f6f3..HEAD -- '*ContentView.swift'
(no output — ContentView.swift is byte-identical)

$ git diff 372f6f3..HEAD -- apps/ios/Feature/Home/HomeDashboardView.swift | grep '^@@'
@@ -102 +102,18 @@ struct HomeDashboardView: View {
@@ -104,2 +121,6 @@ struct HomeDashboardView: View {

Both hunks start at line 102 — inside body. The 13-member init surface (lines 48–88) and the _viewModel = StateObject(...) line are untouched.

3. Repo audits

$ bash scripts/offline_audit.sh    → exit 0, "Offline audit complete."
$ bash scripts/audit_swift_cohesion.sh → exit 0, "Audit complete."

(The audits' earlier "NSCameraUsageDescription missing" error was a false positive from rg not being installed in the sandbox, not a finding; ripgrep was installed locally at /home/user/.local/bin/rg and both audits then passed. The offline audit's network-grep lists only pre-existing intended integrations — Gemini clients, notification sync, GRDB package URLs — plus the same GRDB package URL declaration the tolerated historical-nutrition-check carries.)

4. Commit attribution — every commit on this branch is authored by Sam Gu with the required trailer:

$ git log --format='%h %an <%ae> | %(trailers:key=Co-authored-by,valueonly)' 372f6f3..HEAD
427c5c7 Sam Gu <127461594+SamGu-NRX@users.noreply.github.com> | obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
d2d1d57 Sam Gu <127461594+SamGu-NRX@users.noreply.github.com> | obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
42ff652 Sam Gu <127461594+SamGu-NRX@users.noreply.github.com> | obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
332ac36 Sam Gu <127461594+SamGu-NRX@users.noreply.github.com> | obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

5. Native CI compile evidence — iOS CI (ios-ci.yml, macOS build-and-test on macos-26, PR-triggered) completed on this exact head: run 38144868415 — status: completed, conclusion: success, head_sha: 427c5c7ac764f4d29e198b25cfe8d3a61f51f11a (fetched from the Actions API this session). This green covers macOS compilation of the SwiftUI changes and the macOS test targets; it does not cover visual rendering, Dynamic Type, VoiceOver, or XCUITest checks — those stay UNVERIFIED-NATIVE below.

Handoff

Done

  • The pure five-state map, read model, generation guard, and observable shell; every state and every transition the spec lists tested on Linux against real repositories; unknown expiry pinned nil-never-zero at the read layer; the cooking state pinned to the surfaced Assistant context only.
  • The gate replaced in HomeDashboardView.swift only; ContentView.swift and the Home init surface proven byte-identical (Proof §2).
  • The state map and read model documented for the redesigned Home (planning/operational-home-flow-implementation.md).
  • Base movement reconciled by a real merge (c9555fc in via 427c5c7); no rebase, no force-push, no workflow edits, base left as obv/fl-next-historical-nutrition-r1.

Not done (UNVERIFIED-NATIVE — no macOS host in this workspace)

  • Native SwiftUI rendering of the new fork on device/simulator (the six presentation paths are unchanged visually, but nothing here has rendered them).
  • Dynamic Type, VoiceOver, and reduced-motion behavior of the gate paths.
  • XCUITest run of the Home flow.
  • (iOS CI compile-and-test of the head is green — Proof §5; the items above are exactly the checks CI does not perform.)

Next steps

  1. Reviewer: read planning/operational-home-flow-implementation.md (10 minutes), then the Proof above; the redesigned-Home UI work builds directly on HomeFlowStateMap.state(inputs:cookContext:) + HomeFlowReadResult per that doc.
  2. On macOS: build the app target once (the Linux check compiles the flow core but never SwiftUI), run the native render/Dynamic Type/VoiceOver checks for the six fork paths, and clear the UNVERIFIED-NATIVE list above.
  3. If the base moves again: merge forward (no rebase), re-run refresh.sh && swift test, and re-verify byte-identity of ContentView.swift and the init region.

Risks / notes

Proof - home stock evidence fix (2026-10-11)

Scope of this fix. Home surfaces (canonical state map, read model, dashboard projection, and the shipped gate) now derive "stocked kitchen" evidence from active inventory quantities — distinct ingredients with remaining grams > 0 — never from the recipe-catalog count. Production HomeDashboardView is routed through HomeFlowViewModel + HomeFlowReadModel (the read model built for this PR). Until the canonical food-safety and FLIngredientFacts packages land, no composition tables were added and unknown composition continues to fail closed. Append-only section; the Handoff above predates this fix and names head 427c5c7 — the current head is 735883e4748a9f0ea9fbe2c13c9626075faa29bb, which supersedes it.

1. Linux suite — apps/ios/Tools/home-flow-check (real repositories, real migrated GRDB schema, no network):

$ swift test
Test Suite 'HomeFlowReadModelTests' passed ... Executed 14 tests, with 0 failures (0 unexpected)
Test Suite 'HomeFlowStateMapTests' passed ... Executed 30 tests, with 0 failures (0 unexpected)
Test Suite 'All tests' passed ... Executed 44 tests, with 0 failures (0 unexpected) in 2.652 (2.652) seconds

New regressions added for the reviewer findings (44 total, up from 38):

  • Populated catalog with an empty pantry resolves emptyNew through the read model's derivation path (catalog count is no longer consulted); mirrored at the snapshot-projection path.
  • stockedIngredientCount counts distinct ingredients with remaining grams > 0 (three lots across two ingredients + a zero-gram lot → 2, not the catalog count and not the lot count).
  • Partly-stocked → emptyNew when the last inventory is depleted (the old catalog-count evidence would have pinned the wrong state).
  • Depleted stock with cooked meals stays returning (durable meal evidence; fixture honors the current-version nutrition-snapshot invariant with provenance 'logged_at_capture').
  • Relaunch: a fresh read-model instance over the reopened database file resolves the same state (persistence, not process memory).
  • Structural contract: HomeFlowInputs exposes no tutorial-progress field — asserted over the real stored surface with Mirror, not against a literal.

2. Byte-identity proofs (required by the fix constraints):

$ git diff HEAD -- 'apps/ios/App/ContentView.swift' | wc -l
0
$ git show HEAD:apps/ios/Feature/Home/HomeDashboardView.swift | sed -n '/^  init($/,/^  ) {$/p' > /tmp/sig_head.txt
$ sed -n '/^  init($/,/^  ) {$/p' apps/ios/Feature/Home/HomeDashboardView.swift > /tmp/sig_work.txt
$ diff /tmp/sig_head.txt /tmp/sig_work.txt && echo SIGNATURE_IDENTICAL
SIGNATURE_IDENTICAL
$ git status --porcelain | grep -c "\.github/"
0

The Home init parameter surface is byte-identical to the previous head; ContentView.swift untouched; nothing under .github/.

3. Audits (repo scripts, both exit 0):

$ bash scripts/offline_audit.sh; echo "exit=$?"
Offline audit complete. exit=0
$ bash scripts/audit_swift_cohesion.sh; echo "exit=$?"
Audit complete. exit=0

4. Commit attribution — the fix commit is authored by Sam Gu with the required trailer:

$ git log -1 --format='%H%n%an <%ae>'
735883e4748a9f0ea9fbe2c13c9626075faa29bb
Sam Gu <127461594+SamGu-NRX@users.noreply.github.com>
$ git log -1 --format=%B | grep -v '^$' | tail -1
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

5. Native CI compile evidence — iOS CI on this exact head: run 38190050413 — both jobs completed / success (build-and-test on macOS, and dead-code-advisory; fetched from the Actions API this session). This covers macOS compilation of the rewired HomeDashboardView (including the deleted HomeDashboardViewModel) and the macOS test targets; it does not cover visual rendering, Dynamic Type, VoiceOver, or XCUITest — those remain UNVERIFIED-NATIVE.

Pending the canonical packages (not started, per instructions):

  • Composition-aware stock/counting decisions (e.g. substitute and composite ingredients) stay fail-closed; no local tables were added. When overhaul/food-safety-canon and the FLIngredientFacts package land, HomeFlowReadModel is the single integration point — its ingredients dependency is already injected and its load pass is documented for that swap in planning/operational-home-flow-implementation.md.
  • No merges, vendoring, or copies from either sibling branch were made; their heads were read as fetched refs only.

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

Complete specification for replacing the tutorialProgress.isComplete
dashboard gate with a five-state operational map (empty/new, partly
stocked, cooking, returning, error), a Home-owned read model, an
optional restartable learning panel, and a Linux home-flow-check
package. Spec only — implementation not started; see PR Handoff.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@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 and others added 4 commits October 11, 2026 13:38
The operational Home flow core from planning/operational-home-flow-spec.md:

- HomeFlowModels.swift: the pure five-state map (emptyNew, partlyStocked,
  cooking, returning, error) over repository outputs, with explicit primary
  actions. Tutorial progress is not an input anywhere; unknown expiry is nil,
  never zero.
- HomeFlowReadModel.swift: one repository-backed read pass producing both the
  state-map inputs and the existing dashboard snapshot, plus a pure
  HomeLoadGeneration staleness guard. Combine-free so it compiles on Linux.
- HomeFlowViewModel.swift: the thin observable shell (app target only;
  ObservableObject is not available on Linux) wiring GRDB live observers to
  the guarded load.
- Tools/home-flow-check: a Linux Swift package mirroring
  historical-nutrition-check: refresh.sh copies the real production sources
  in, tests drive them through real GRDB databases.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
The body gate in HomeDashboardView no longer forks on tutorial progress:
it resolves HomeFlowStateMap.state(from:cookContext:) and switches on the
result. Tutorial cards remain only for a genuinely empty-new kitchen whose
tour is incomplete; a stocked or returning cook now sees their kitchen even
if they skipped the tour, and the cooking state reads solely the Live
Assistant coordinator's surfaced matched-recipe context.

A snapshot-level projection (state(from:cookContext:)) documents the shipped
view's seam: same precedence rules as the canonical inputs path, evaluated
over the snapshot the sections already consume. It never reports .error —
the nil-snapshot branch keeps owning that.

ContentView.swift and the Home init surface are byte-identical (proved in
the PR body: the view diff's only hunks start at line 102, inside body).

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Implementation record for planning/operational-home-flow-spec.md: the five
states with precedence, the binding rules (unknown expiry nil never zero,
tutorial progress not an input, cooking reads only the surfaced Assistant
context), the canonical and fork-projection derivation paths, the read
model and generation guard, and how the shipped view wires them. Written
so the redesigned Home UI can sit on this core unchanged.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Keeps the PR diffable against the moved base; no rebase, no history
rewrite.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@SamGu-NRX SamGu-NRX added UNVERIFIED-NATIVE verified-linux verified-native-ci macOS CI (build-and-test) green on the exact head; run URL in PR body labels Oct 11, 2026

@SamGu-NRX SamGu-NRX left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: FIX

Reviewed head 427c5c7 against fl-integration-f1, Part 5.

Blockers:

  • apps/ios/Feature/Home/HomeFlowModels.swift:249: the shipped projection treats snapshot.ingredientCount as pantry stock. That value comes from IngredientRepository.count(), which counts catalog rows, not inventory. An onboarded user with a populated catalog, zero lots and zero cooks resolves partlyStocked instead of emptyNew. Feed actual active inventory into the shipped gate and add the catalog-present/empty-pantry regression. The existing projection fixtures assign inventory-like counts and miss this case.
  • apps/ios/Feature/Home/HomeDashboardView.swift:114: emptyNew still gates the operational dashboard on tutorialProgress.isComplete. The saved spec requires optional practice rather than a dashboard gate. HomeFlowReadModel and HomeFlowViewModel are also never instantiated by production Home, so their tested read path and generation guard do not protect the shipped view. Connect the real read result while preserving the init surface, or explicitly narrow the deliverable and obtain acceptance of that change.

Criteria: five-state core partly met, read/view-model pair partly met, Home-only fork and unchanged ContentView/init met, Linux behavioral coverage partly met, explicit UNVERIFIED-NATIVE handoff met. Fully met 2/5. The tests omit depleted/returning repository transitions and relaunch, and the no-tutorial-field test asserts a fixture tautology.

CI run 38144868415 is green at this exact SHA. No local build, Xcode or simulator run. No workflow or protected-file edits found. The existing seven-day shelf-life fallback is inherited behavior, not reviewed food-safety evidence.

Home treated IngredientRepository.count() — the recipe-catalog size — as
"stocked kitchen" evidence, so a user with a stocked pantry and an empty
catalog (or vice versa) resolved the wrong operational state, and the
shipped dashboard still gated the kitchen on tutorial progress through
its own catalog-count view model.

- HomeFlowReadModel derives stock evidence from active inventory lots
  (distinct ingredients with remaining grams > 0) and gates
  recommendations on that evidence; the catalog count no longer feeds
  the pass.
- HomeDashboardSnapshot.stockedIngredientCount carries the same
  quantity evidence; the projection reads it, so the canonical and
  shipped paths agree.
- HomeDashboardView now holds HomeFlowViewModel over the flow read
  model and switches its fork on the canonical state map; the empty-new
  kitchen renders the operational dashboard with the tutorial as
  optional practice below it. The init signature is byte-identical.
- The catalog-count HomeDashboardViewModel is deleted; the unreachable
  tutorial landing went with it (its practice pieces live in
  optionalPracticeSection).
- home-flow-check grows regression tests: populated catalog with empty
  pantry (both derivation paths), distinct-ingredient quantity
  evidence, partly-stocked to empty-new depletion, depleted stock with
  cooked meals staying returning, relaunch across a reopened database,
  and a structural no-tutorial-input contract over the real stored
  surface.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

@SamGu-NRX SamGu-NRX left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: FIX

Reviewed 735883e4748a9f0ea9fbe2c13c9626075faa29bb against fl-integration-f1 Part 5 and the saved operational Home spec.

The previous findings are resolved. HomeFlowReadModel.swift:135-148 derives stock from active inventory with positive quantity. HomeDashboardView.swift:75-85,112-128,330-336 now instantiates the shared read/view-model pair and renders the dashboard for empty-new users regardless of tutorial completion. The public init parameters and ContentView are unchanged. New repository tests cover catalog-without-stock, depletion, returning after depletion and reopening the same database.

Blockers:

None found in the reviewed delta.

Lesser findings:

  • The Proof and Handoff still name 427c5c7, claim 38 tests, and say the StateObject assignment was untouched. The current delta changes that assignment, removes HomeDashboardViewModel and adds tests. Record current-head home-flow-check execution and replace the obsolete byte-identity claim with the actual public-signature guarantee. Native app CI does not run this separate package.
  • HomeDashboardView.swift:125-128,330-335 offers practice only in empty-new state while the tour is incomplete. Once stock arrives, or the user skips/completes practice, that entry disappears. The saved spec calls for optional restartable practice in every state. Mark this presentation requirement unfinished or provide an entry without resetting kitchen data. The new dashboard gate itself works.
  • HomeFlowViewModel.swift:74,83 silently discards observer errors. Surface a refresh failure or recovery route so a dead observation does not leave the last dashboard indefinitely presented as current.
  • The inherited seven-day shelf-life default is not reviewed food-safety evidence. The read layer preserves genuinely unknown expiry as nil; its existing fridge-default test does not establish that seven days is safe for each ingredient.

Task criteria: five-state core met; repository-backed read/view-model pair met; Home-only fork with unchanged ContentView/public init met; Linux behavioral coverage partly met because current-head execution is unreported; explicit UNVERIFIED-NATIVE Handoff met. Fully met 4/5. No workflow or protected-file changes appear in this delta.

CI run 38190050413 passed native build-and-test at this exact head. Rendering, Dynamic Type and VoiceOver remain unverified. No local tests, Xcode or simulator were run.

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

Labels

UNVERIFIED-NATIVE verified-linux verified-native-ci macOS CI (build-and-test) green on the exact head; run URL in PR body

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant