fix: skip the farewell frame when back() answers a system back - #370
fix: skip the farewell frame when back() answers a system back#370SRWieZ wants to merge 2 commits into
Conversation
back() publishes one last frame of the departing screen so a PHP-initiated pop has fresh content to show during the animation. When the back comes FROM the system (hardware button, back chevron, edge-swipe pop), the native pop has already animated and the coordinator's path has already shrunk — the farewell frame then arrives as an unknown URI and is reconciled as a brand-new push: the screen flashes back in, then out again once the router publishes the level below. Visible whenever a screen's teardown spans a few frames. The type-8 dispatch now goes through handleSystemBack(), which flags the context so back() skips the farewell; PHP-initiated backs keep it. The test harness's pressBack() mirrors the production dispatch.
|
Nice find 🔥 Reproduced on simulators, fixed by this branch on both platforms. On iOS the effects were the worst, it shows on every system back with no slow teardown at all, the departed screen sits under the root's nav bar for about 300 ms before snapping. Android is clean at the natural 20 ms gap, hitches from about 30 ms of teardown work and bounces fully from 100 ms. Worth adding to the description: it only shows when the farewell tree differs from the last publish. A byte-identical republish is dropped by the bridge diff and never re-pushes. One test & two docblock updates I suggest before merge:
public function test_the_harness_back_press_skips_the_farewell_frame_too(): void
{
$bridge = Native::fakeBridge();
$screen = Native::test(DetailScreen::class);
$mounted = count($bridge->publishes);
$screen->pressBack()->assertWentBack();
$this->assertCount($mounted, $bridge->publishes);
}
public function test_the_harness_still_publishes_a_farewell_frame_for_a_php_initiated_back(): void
{
$bridge = Native::fakeBridge();
$screen = Native::test(DetailScreen::class);
$mounted = count($bridge->publishes);
$screen->tap('Go back')->assertWentBack();
$this->assertCount($mounted + 1, $bridge->publishes);
}
Two things I checked so they don't need a decision: |
|
Small sidenote, not for this PR: the suite has two styles. The build and compiler tests are PHPUnit classes from the original codebase, everything around EDGE and the testing harness written since July is Pest.
|
The bug
With shared-stack chrome, backing out of a screen via the system back (back chevron, edge-swipe, hardware button) can flash the screen back in and immediately out again. It shows when the departing screen's farewell tree differs from its last publish and teardown spans a few frames. A byte-identical republish is dropped by the bridge diff, so that case never re-pushes.
Why
Three pieces, each fine on its own:
back()publishes a farewell frame of the departing screen before setting the BACK intent, so a PHP-initiated pop has fresh content to show while the native side animates it.sendSystemBackEvent.So for affected system backs: native navigation starts backing away → PHP answers with a changed farewell frame of the departing screen → the coordinator pushes it back on → the router publishes the level below → it disappears again.
The fix
The type-8 dispatch now goes through
handleSystemBack(), which flags the native-system-back context.back()skips the farewell frame while that flag is set; PHP-initiated backs are untouched and keep their farewell frame.pressBack()in the test harness mirrors the production dispatch.navigate()fromonBackPressed()deliberately keeps its farewell publish because that re-push is what aligns the native path with PHP's resulting stack. On Android plain screens, the previous frame remains visible during teardown instead of publishing a fresh one; this affects only state mutated inonBackPressed()immediately beforeback(), for the teardown interval plus the next screen'sonResume().Tests
SystemBackFarewellTestnow uses the EDGE suite's Pest style and pins five cases:onBackPressed()throws.pressBack()publishes no farewell frame.Full suite: 901 passed. Pint and PHPStan clean.