You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(perf): render the root block list in memoized chunks
The document rendered as one flat array of children under a single
`Children` component. Every content edit re-ran the root `useChildren`
(rebuilding all n JSX elements, ~12.3ms per keystroke at 8k blocks,
~1.5us per child) and forced React to reconcile all n positions (fresh
props objects defeat element-identity shortcuts, so the
`MemoizedElement` comparator ran per child). The per-node memo chain
held, only the edited block's components re-rendered, but the rebuild
and reconcile above it were O(n) per keystroke.
The root children now render as contiguous chunks. `editor.rootChunks`
(`internal-utils/root-chunks.ts`) is maintained per operation by
`transformRootChunks`, following the `transformBlockIndexMap` pattern:
a pure transform that resolves the operation's root index against the
pre-op `blockIndexMap` (verified against the chunks, with a scan
fallback for unmaintained maps and a full-rebuild fallback for
unresolvable shapes), rebuilds only the owning chunk's block array,
splits chunks above 200 blocks, and drops empty ones. Every op except
`set.selection` refreshes the owning chunk because the tree updates
immutably, even a text op replaces the owning root block's reference;
the transform therefore runs before the update-value subscriber's
text-op early return. The oracle test pins `flatten(chunks) === value`
across a 400-step fuzzed op sequence plus explicit identity-reuse
cases.
At the root, `useChildren` dispatches to `MemoizedChunk` components
(one per chunk, keyed by chunk id) which re-enter `useChildren` with
an explicit `chunkBlocks` slice; `splitDecorationsByChild` accepts the
same override, so each chunk resolves the full root decorations
against its own key-to-index map and decorations outside the chunk
fall away naturally. The chunk memo bails on chunk identity (the
transform's contract), render-prop identity, and decoration content
equality (the root decorations array is recreated every render, so
identity comparison would defeat the memo). Chunks render as
fragments: the contenteditable DOM is byte-identical, which leaves
selection, IME, `toDOMNode`, and `RestoreDOM` untouched.
Chunk-level render decisions (container resolution, pipeline
membership) read the registration maps, and the chunk memo blocks the
root re-render that used to refresh them, caught by the
container-normalization suite. Chunks therefore subscribe to the
registrations selector channel, which fires only on renderer
register/unregister; registrations are config-time, so this is off the
hot path.
React-side cost per keystroke (chromium, warmed, settled): root
dispatch + one chunk body 0.87ms at 1k blocks and 1.27ms at 8k, versus
0.85ms and 12.3ms for the flat map, size-independent where it was
linear. Probe wall time per keystroke at 8k: ~61ms to ~23.5ms. Full
suite green: 848 unit (including the new transform oracle), 1694
chromium.
0 commit comments