Back from the day view landed on Accounts — the simulator had nothing to say about it

Tapping Back from the day view should return you to the month calendar. Instead it returned you to the Accounts screen. The calendar was not behind the day view, and it did not animate back into view. It was absent from the navigation chain, floating above the current stack in a way that Back could not traverse. The only route back to the calendar was to restart the flow from Accounts.

That is finding A8 from the round-2 device QA pass on AnF, recorded in docs/qa/2026-06-18-round-2-findings.md. The simulator had run the same flow. It had approved it at every step.

How the three-level flow arrived

Phase K (2026-06-15) rebuilt the Accounts tab from scratch. The result was a stacked-module screen: a total-balance hero, a 30-day trend chart below it, then a horizontally-scrollable daily-activity strip showing per-day transactions. The strip was a placeholder for something with more depth.

Phase O (2026-06-16) gave it that depth. The strip became a month calendar. CJ deepened the original ask mid-design: click the calendar → full-screen, then click a day → day mode. The flow became three levels: the Accounts tab at the root, the month calendar presenting full-screen when tapped, and a day view one level deeper still. The three-level structure was the right call. The daily-activity strip was always too compressed for meaningful interaction at the month scale; a full-screen calendar with a dedicated day view is the correct shape for that data.

The problem was not the UX decision. The problem was how the three levels were wired together. Phase K built the root; Phase O built the layers on top of it. Both phases delivered what they promised. The failure sat in the interface between them.

The mismatch in navigation ownership

A tab destination in SwiftUI sits inside a NavigationStack. Navigating forward from that destination pushes views onto that stack. Back resolves against it: pop the top view, return to the one below it.

A full-screen modal is not a NavigationStack push. It presents above the current stack; it is not part of it. When the month calendar opens full-screen over Accounts, it sits above the Accounts NavigationStack. The Accounts stack does not contain it.

From there, if the day view is navigated to in a way that couples it to the Accounts stack rather than to a stack the calendar owns, the Back resolution is wrong. Back from the day view looks at the Accounts NavigationStack. The calendar is not in it. The stack’s last entry before the day view is the Accounts screen itself. Back returns there. The calendar, floating above the stack, is never in the resolution path.

This is why A8 looked the way it did: not a crash, not a missing animation, not a black screen. The right screens opened at every step going forward. The wrong destination appeared going backward. Forward navigation composed correctly across the modal boundary; backward navigation did not, because the modal was never part of the stack that Back was traversing.

What the simulator approved

The simulator ran the flow and registered no problem. The calendar opened. The day view opened. Back ran. A transition played. Nothing failed. The visual output was correct at every step except the final destination after Back.

This is the hardest category of simulator gap to anticipate. Not a missing view, not a crash, not a layout regression: just a wrong destination after a standard navigation action. The screens exist; the animations play; the navigation lands somewhere unexpected.

The simulator does not reproduce NavigationStack ownership state under a real TabView with the same fidelity a device does. The ownership mismatch either does not surface in the simulator or surfaces inconsistently; consistently enough to pass during implementation, inconsistently enough to be invisible until a full device QA run.

Improving on the earlier finding that the simulator approved three regressions the device caught: A8 is the instance where the root cause is structural and architectural rather than incidental. The other regressions were visual-state bugs, fixable by addressing the specific case. A8 is a navigation ownership bug: fix the pattern and the defect does not recur. Leave the pattern wrong and every future modal added to Accounts under the same approach is a candidate for the same failure.

The fix

The round-2 branch corrected the ownership before merge. The calendar layer took responsibility for its own NavigationStack. The day view then navigated within the calendar’s stack, not the Accounts stack. Back from the day view now resolves against the calendar’s stack and returns to the calendar. Back from the calendar dismisses the modal and returns to Accounts.

Both levels work because both levels own their stack. The three-level flow is intact; the routing is correct. Device-verified on AnF before merge.

The invariant

Any full-screen modal inside a tab destination that navigates further (a day view, a detail record, a drilled item) needs its own NavigationStack. It cannot inherit navigation ownership from the tab. If it does, Back from any view pushed inside the modal resolves against the tab’s stack and skips the modal entirely.

Tab → full-screen modal → detail is a standard iOS flow shape. The structural invariant is: the modal owns its stack. Violate it and Back goes home. Uphold it and each level of the hierarchy resolves correctly.

Phase O is the canonical case because the three levels were added deliberately and correctly from a UX perspective. The stack ownership was the only thing wrong. Correcting it required no UX change. The three-level flow still works; it just needed its implementation to match the structure the UX implied.

That is the design-time signal: when a phase adds a full-screen modal over an existing tab, the question to ask is not “does the modal look right?” but “does the modal own its stack?” The first question is answerable in the simulator. The second is only answered under Back, on device.

The gate this adds

Round-1 device QA (2026-06-16) could not have caught A8. Phase O and round-1 arrived in the same commit batch. The three-level structure did not exist before that moment; neither did the mismatch.

Round-2 (2026-06-18) caught A8 immediately. Fifteen seconds of Back-navigation testing (Day → Back, Calendar → Back) surfaced it at the first device run after Phase O landed.

That check is now a merge prerequisite for any commit that adds a full-screen or modal layer inside a tab destination. Not a full regression pass. Just: does Back land correctly at every new level of the hierarchy? The check is proportionate to the risk. A8 ran through two QA rounds because the check was not on the Phase O merge criteria. It is now.

All writing