A push between two web entries animates two empty panels. The outgoing screen has lost the web view before the slide starts, and the incoming one is holding it invisible until the page catches up, so for the length of the animation both screens are a flat background colour and the content appears afterwards.
Reported from an app in production as "sliding from page to page is kinda bad".
Why, from the shell's own comments
One WKWebView serves every web entry. CraftHybrid.swift:11 states the design: each tab keeps one UINavigationController whose stack mixes native screens and web entries, "and the one WKWebView moves into whichever web entry is on screen".
Three things then line up against the animation:
A web slot has no content of its own.
CraftHybridWebSlot(:968) sets onlyview.backgroundColor(:990), and its class comment says it outright (:967): "the others are empty until they are shown again."The move happens before the animation, not after. The incoming slot adopts the web view in
viewWillAppear(:995), andmoveWeb(:1219) callsremoveFromSuperview()andremoveFromParent()on the old owner. UIKit runsviewWillAppearahead of the push transition, so the outgoing screen is stripped at the moment it starts sliding.The incoming slot deliberately shows nothing until the page arrives.
webHost.view.alpha = slot.awaitingPage ? 0 : 1(:1335), revealed inreveal(_:)(:1442) once the reported path matches. The comment at:975is explicit that the entry shows its background "not the page it is leaving, until it has".
Each of those is reasonable alone. Together they mean a web to web push has nothing to animate.
What it looks like
| Push | Outgoing | Incoming |
|---|---|---|
| web to web | blank, the web view already left | blank until the page reports the new path |
| native to web | drawn natively, correct | blank until the page arrives |
| web to native | blank | drawn natively, correct |
Web to web is the common case inside an app that is mostly its web page, and it is the one with no correct half.
Shape of a fix
The standard hybrid remedy is to cover the move with a visual snapshot, so the animation has something to animate:
- take
snapshotView(afterScreenUpdates: false)of the web view beforemoveWebstrips it, and leave that view in the outgoing slot for the duration of the transition, removing it ondidShow. The outgoing screen then does what UIKit does natively: it shows the page it is leaving while it parallaxes away. - while
awaitingPageholds the live view at alpha 0, show something other than a bare background in the incoming slot. A snapshot of the destination when one is available, otherwise the existing background, but the outgoing half is the larger win and is unconditional.
snapshots already in this file is unrelated: it is CraftSnapshots, a JSON store for native screens, not a visual snapshot. Nothing in packages/ios/templates calls snapshotView today.
Verification gap, which is most of the work
packages/ios/scripts/test-native-navigation.ts drives a real simulator, but its fixture is WebView-free by design and asserts the app exposes no WebView, so nothing exercises a hybrid web slot transition. A fix here is visual, and a visual fix that cannot be run cannot be claimed, so a hybrid fixture comes first: two web entries and one native entry in a tab, pushed and popped, asserting the outgoing screen is not a flat fill mid-transition.
Not Android
The Android shell has no equivalent. CraftNativeShell.kt.template routes nativeScreens and tabs but does not mix web entries into a native back stack with a shared WebView moved between them, so this is iOS hybrid only.