Repository navigation
Fix stale component boundary bookkeeping after Link navigation - #129
Merged
nakasyou merged 2 commits intoSep 24, 2026
Merged
Conversation
When a shared layout survives a route change, the components map is kept without a container reset. Components whose previous DOM boundary was detached by the navigation slot swap still hit the 'wasActive' reuse early return in renderComponentToNodes, emitting only ec:c: boundary markers and relying on preserveReusableContentInRoots to recover the old DOM. The old DOM is no longer reachable, so the boundary renders empty. Gate the reuse paths on the previous boundary still being attached to a parent (previousDomReusable = previousStart/End have a parentNode), so detached components render fresh nodes instead of empty boundaries. Adds a permanent E2E regression test covering component-valued props and children surviving a Link navigation round trip through a shared layout. Co-authored-by: codex <codex@openai.com> Co-Authored-By: Shotaro Nakamura <syoutarou1103@outlook.jp>
When a shared layout survives a Link navigation, components whose DOM was dropped by the projection slot swap kept their start/end anchors pointing into the detached subtree. A later render then hit the wasActive reuse early return and emitted empty ec:c: boundary pairs, so component-valued props and children disappeared after a navigation round trip. Clear component start/end anchors when the DOM containing them is removed (replaceProjectionSlotContents / replaceBoundaryContents) and before a shared-layout nav renders the replacement slot contents, so stale records render fresh instead of reusing dead DOM. Also skip a queued dirty-flush activation when its captured symbol no longer matches the component after awaiting module load: the flush previously resumed with the old route's module and replaced the freshly-rendered boundary contents, destroying DOM nodes that compiled reactive effects still anchored to (e.g. a signal-driven update after Link navigation never reached the page). Co-authored-by: codex <codex@openai.com> Co-Authored-By: Shotaro Nakamura <syoutarou1103@outlook.jp>
Contributor
|
Website preview: https://pr-129-eclipsa.xiarenda61.workers.dev Commit: f2b1a99 |
Codecov Report❌ Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Link navigation broke content rendered through component-valued props/children inside a shared layout, and also broke signal-driven updates after navigating to a route. Two related runtime bugs:
Stale boundary anchors after DOM removal. On shared-layout navigation (
updateSharedLayoutBoundary),replaceProjectionSlotContentsdrops the old slot DOM but component records keptstart/endpointing into the detached subtree. On the next render those components took thewasActivereuse early-return inrenderComponentToNodesand emitted only emptyec:c:start/ec:c:endmarker pairs — the content (e.g.RenderPropProbe'saa/children slots) never re-rendered. Fixed by clearingcomponent.start/endfor boundaries inside removed ranges:clearRemovedComponentBoundaryAnchorsruns inreplaceProjectionSlotContents(which now takescontainer) and inreplaceBoundaryContentsbehind a newoptions.container, plus a pre-render clear (clearComponentBoundaryAnchorsInRange) before each nav slot re-render, since the render happens while the old DOM is still attached.Stale dirty-flush activation clobbering freshly rendered routes.
activateComponentcapturesactivateSymbol, thenawait loadSymbol(...). If the component was re-rendered by route navigation during the await (same component id, different symbol), the flush resumed with the old route's module andreplaceBoundaryContentsoverwrote the just-rendered boundary — destroying DOM nodes that compiled reactive effects still anchored to, so the/suspensereadysignal write ran its effect but the update landed in detached nodes. Fixed by bailing whencomponent.active || component.symbol !== activateSymbolafter the module load.Reproduction / regression test
e2e/test/example.dev.test.ts→keeps component-valued props and children rendered after Link navigation round trip(added in this branch): navigates Home → Counter → Home withLinkand asserts the prop/children components are still rendered. It fails without the fix (emptyec:c:boundaries, 0 elements) and passes with it.Verification
bun run test— all unit tests pass.bun run test:e2e— 48/49 pass, including the new regression test and the suspense-after-nav tests. The only failure isshares atom state across components and preserves it across Link navigation, which fails identically on unpatchedmain(pre-existing).vp checkandbun typecheckclean.Link to Devin session: https://app.devin.ai/sessions/d9018d13ba034be88c394f41f902b2e8
Open in Devin Desktop: https://app.devin.ai/desktop/session/d9018d13ba034be88c394f41f902b2e8?variant=devin
Requested by: @nakasyou