Skip to content

fix(WhereUI): show splash before the first foreground reveal - #229

Open
kyleve wants to merge 7 commits into
mainfrom
codex/consistent-first-foreground-splash
Open

fix(WhereUI): show splash before the first foreground reveal#229
kyleve wants to merge 7 commits into
mainfrom
codex/consistent-first-foreground-splash

Conversation

@kyleve

@kyleve kyleve commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Posted by an AI agent on kve's behalf.

Summary

  • User-facing: show Where's branded splash for the existing 800 ms minimum before the first foreground-visible main UI, including a runner promoted from a headless background launch
  • User-facing: keep MainTabs warming beneath the splash, restart an interrupted first-reveal hold on return, and avoid replaying the splash after content has actually appeared
  • Internal: add an opt-in LifecycleReadyRevealPolicy while preserving LifecycleKitUI's phase-driven default for existing callers
  • Internal: model coalesced promotion, interrupted first reveal, and ordinary resumes in TLA+, with broken-design and reachability controls

Why

A background-ready runner can be promoted and finish its foreground drive between SwiftUI render passes. The container then observes .ready without ever observing the intervening splash phase, so its existing minimum-duration hold is never armed and the Locations loading surface can appear directly.

Launch reason is also not scene visibility: once promoted, a runner remains .userForeground. Without a separate active-scene input, the splash deadline can expire while the app is inactive and the next foreground can reveal main UI that was never previously visible.

Review focus

  • the reveal task is keyed on readiness, runner tree eligibility, and active-scene presentation visibility
  • an actually rendered splash retains its original deadline, while an interruption before the first uncovered frame resets the opt-in state to awaiting
  • timer expiry moves to a non-sticky releasing state; only SwiftUI committing the uncovered content marker makes the reveal permanent
  • ready content remains built, accessibility-hidden, and non-interactive beneath the splash
  • headless phases, gates, failures, zero-duration callers, and warm resumes after a committed reveal retain their existing behavior
  • FirstForegroundReveal models one pre-reveal interruption and assumes SwiftUI commits the inactive presentation update before reactivation; scene destruction, multiple windows, and actual scheduler behavior remain explicitly excluded

Testing

  • ./swiftformat --lint — 0 files require formatting
  • ./test LifecycleKitUITests — 30 tests passed
  • ./test WhereUITests — 425 tests passed
  • ./test --snapshots — 42 tests passed; no reference changes
  • ./tla-check FirstForegroundReveal — 8 cases passed: 2 expected broken-design failures, 4 reachability controls, and 2 clean current bounds

// Include visibility in the identity: a background runner can remain
// `.ready` across foreground promotion, and that false → true transition
// is what must start an opt-in first-reveal hold.
.task(id: isReadyAndVisible) {

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.

Posted by an AI agent on kve's behalf.

Review focus: visibility is deliberately part of this task identity. A background runner can remain .ready before and after foreground promotion, so readiness alone would not restart the task that establishes the first visible splash hold.

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.

Posted by an AI agent on kve's behalf.

Addressed in 9587d79. The hosted regression test now mounts a ready runner headlessly, applies a coalesced foreground-only reason change while the phase stays ready, proves the splash appears and releases, and was mutation-checked to fail if the task identity is reduced to readiness alone.

splashSeen, contentBuilt, contentRevealCount,
splashPhaseRendered, resumeCount>>

\* A SwiftUI update reads the runner atomically on the main actor. The

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.

Posted by an AI agent on kve's behalf.

Review focus: Render deliberately keeps the synchronous onChange hold update atomic with the main-actor view update, while StartReadyTask is a separate scheduled action. That source-correspondence assumption is load-bearing; if SwiftUI permits another main-actor mutation to interleave inside the synchronous callback, the model result becomes inconclusive until this boundary is revised.

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