Skip to content

feat: shouldSkipChildren — leave a node's children to another renderer - #26

Merged
aralroca merged 1 commit into
mainfrom
feat/should-skip-children
Aug 8, 2026
Merged

feat: shouldSkipChildren — leave a node's children to another renderer#26
aralroca merged 1 commit into
mainfrom
feat/should-skip-children

Conversation

@aralroca

@aralroca aralroca commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

What

New shouldSkipChildren?: (node: Node | null) => boolean option: a node it matches keeps its subtree out of the diff entirely. The node itself still participates (attributes sync as usual), but its children are never updated, moved, pruned — or donated to a replacement element.

Why

An embedded renderer (a live React root mounted inside a host element) keeps internal references to its DOM nodes. When the diff walks into such a host:

  • Text rewritten behind the renderer's back makes its later renders bail out against content they never wrote — the DOM shows the incoming markup while the renderer believes its own output is committed.
  • A positional match against a different tag replaces the host and re-parents its (already-diffed, in the normal case) children into the clone. For renderer-owned children this steals nodes the renderer still tracks, and its teardown then throws NotFoundError: removeChild.

Callers that own such hosts can now declare them, diff the rest of the page, and refresh the embedded root through their own channel (e.g. serialized props on the host attribute, which still syncs).

Details

  • The predicate is called with nodes from both trees, so the answer must come from the node itself (tag name, attributes), not external identity — documented in the README, index.d.ts and the option's JSDoc.
  • A skipped subtree may still be streaming when the walk passes its host: its late-arriving children are not awaited, and the settled reconciliation pass does not count them as unvisited pending work.
  • On a same-position replacement by a different element, the incoming subtree is applied wholesale (settled, then deep-cloned); the old children leave the document attached to their old node, so the embedded renderer can tear down cleanly.

Tests

Three new cases (run across chrome/firefox/safari like the rest of the suite): subtree kept while attributes sync, chunk boundaries inside the skipped subtree, and no child donation on replacement. Full suite: 150/150.

🤖 Generated with Claude Code

A node whose children are owned by an embedded renderer (a live React
root) must not have its subtree diffed: the renderer's internal tree keeps
node references, so text rewritten behind its back makes later renders
bail out against content they never wrote, and a replacement by a
different element that inherits (re-parents) those children makes the
renderer's own teardown throw removeChild.

The new option skips the subtree walk for matching nodes — attributes on
the node itself still sync — never donates their children to a
replacement, and keeps the settled reconciliation pass from counting the
skipped (possibly still-streaming) children as pending work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aralroca added a commit to aralroca/Janux that referenced this pull request Aug 8, 2026
…visories, update the test census

- `patchedDependencies` carries brisa-build/diff-dom-streaming#26 (build +
  types) until 0.6.11 is on npm: the registry's 0.6.10 has neither the
  behavior the foreign-safe morph relies on (the interop refresh test is racy
  without it) nor the option in its types (typecheck and the declaration emit
  both tripped on TS2353). Drop the patch and refresh the lockfile once the
  release lands.
- js-yaml 3.15.1 / 4.3.1 and nanoid 3.3.17 in the lockfile: the three High
  advisories osv-scan gates on, all in-range patch fixes.
- Test census: 11970 → 11992 (the suite grew by the empty-host guard, the
  skip-children coverage and the config re-read test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aralroca
aralroca merged commit fe358c0 into main Aug 8, 2026
1 check passed
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