docs/plans/2026-04-03-slate-react-v2-projection-proof-plan.md
Prove the first real runtime answer for decorations and selection-anchored
annotation overlays without smearing projection state back into slate-v2.
The proof should show:
slate-v2 owns pure range semantics and range-to-text-slice projectionslate-react-v2 owns the projection store and local subscription policydecorate churnslate-react-v2slate-v2 for pure range projection helpers onlydecorate parityThe issue corpus is not subtle here:
#4483 is a renderer invalidation problem, not “decorations are vaguely slow”#4477 says selection-anchored comment overlays are a real product seam#5987 says async decoration updates must not destabilize the caret#4392, #3382, and #3352 say cross-node projection is part of the shape,
even if this first proof stays narrowerThe current code also makes the next move obvious:
slate-react-v2 already has selector subscriptions and almost nothing elseslate-v2 snapshots expose ids, paths, selection, and marks, but no overlay
projection seam yetslate-v2Add one pure helper:
Editor.projectRange(editor, range)It returns per-text local segments keyed by runtime id.
The helper is semantic, not renderer-owned:
Range{ runtimeId, path, start, end }slate-react-v2Add one projection store:
createSlateProjectionStore(editor, source)Where source(snapshot) returns logical overlay ranges.
The store:
Editor.projectRange(...)Add one hook:
useSlateProjections(runtimeId)And wire the optional store through <Slate ...>.
This cut is small, honest, and future-proof enough:
EditorSnapshotRejected because that makes core carry render-time junk it should not own.
decorate prop directly to <Slate>Rejected for the first proof because it reintroduces effect-mirroring pressure and hides the real seam inside React component props.
Rejected because that needs range-ref or bookmark semantics and will bloat the first proof.
Add a slate-react-v2 runtime test proving projection subscriptions stay
slice-scoped when projection output changes for one runtime id only.
Expected failure:
useSlateProjections(...) does not existImplement the minimal projection store and hook until the local-rerender test passes.
Add a runtime test proving a selection-derived annotation overlay source tracks committed selection changes and only rerenders the affected text slices.
Expected failure:
Editor.projectRange(...)Implement pure range segmentation in slate-v2 and wire store recomputation on
editor commits.
Only after both tests pass:
slate-react-v2 currently exposes only:
SlateuseSlateSelectoruseSlateSelector already proves the subscription model; the missing piece
is a second external store for overlays, not a new React philosophy.slate-v2 already has stable runtime ids in snapshot.index, so the right
overlay key is obvious.Acceptance:
Acceptance:
Editor.projectRange(editor, range)Acceptance:
createSlateProjectionStore(editor, source)useSlateProjections(runtimeId)Slate contextAcceptance:
slate-v2 tests if new pure helper needs themEditor.projectRange(...) plus a React-owned projection store.tmp/slate-v2:
slate-v2 pure range projectionslate-react-v2 projection store, context wiring, and local projection hooknpx -y node@20 /opt/homebrew/Cellar/yarn/1.22.22/libexec/bin/yarn.js workspace slate-react-v2 testnpx -y node@20 /opt/homebrew/Cellar/yarn/1.22.22/libexec/bin/yarn.js mocha --require ./config/babel/register.cjs ./packages/slate-v2/test/snapshot-contract.ts