docs/plans/2026-04-21-slate-v2-final-api-runtime-shape-plan.md
Finish the Slate v2 API and runtime as if designed from scratch:
This is not a backward-compat pass.
Compatibility can survive only as thin, explicitly named adapters that do not shape the core or React runtime.
The final v2 shape is:
slate: data-model-first core, transaction-first execution, operation truthslate-dom: browser translation, DOM selection, clipboard, IME, repairslate-browser: proof harness and browser automation contractsslate-react: React-perfect runtime over commit records, live reads,
projection sources, semantic islands, and DOM-owned text capabilityCut these as primary documented APIs:
editor.childreneditor.selectioneditor.markseditor.operationseditor.apply(...)editor.onChange(...)Primary API:
Editor.getChildren(editor)Editor.getLiveSelection(editor)Editor.getMarks(editor)Editor.getOperations(editor, since?)Editor.apply(editor, op)Editor.withTransaction(editor, tx => ...)Editor.subscribe(editor, listener)Mutable fields may remain only as compatibility mirrors while migration pressure exists. They must not be used in docs, examples, or new tests as the primary API.
Every local write should execute through a transaction boundary.
Requirements:
EditorCommitEditor.getSnapshot() remains, but only as observer artifact.
Required work:
getSnapshot()Official live read surface:
Editor.getLiveNode(editor, path)Editor.getLiveText(editor, path)Editor.getLiveChildren(editor, path?)Editor.getLiveSelection(editor)Editor.getRuntimeId(editor, path)Editor.getPathByRuntimeId(editor, id)Editor.getLastCommit(editor)Editor.getDirtyRuntimeIds(editor, commit)Editor.getDirtyTopLevelRange(editor, commit)Live reads are runtime APIs. Snapshot reads are observer APIs.
decorateFinal API should not teach or expose Editable.decorate as primary.
Required final state:
createSlateDecorationSource exists only as migration/compat adapterdecorate is removed from final public React API or moved behind an
explicitly named compatibility adapterOne projection kernel, typed sources:
Every source declares:
Review/comments/widgets must not be forced through text decorations.
Already cut from current slate-react product runtime.
Final cleanup:
getChunkSize, renderChunk, or
data-slate-chunkFinal API:
Editable means the current semantic-blocks runtime.Editable implementation is deleted or private.EditableBlocks is removed or left only as a temporary alias during the
cutover.Required result:
EditableEditable props are the new runtime props:
decoraterenderChunkKeep the lane, but keep it strict.
Required public/internal contract:
Hard opt-outs:
slate-browser is mandatory for browser-facing closure.
Every risky editing lane must prove model and DOM together:
Unit tests alone do not close browser behavior.
Keep these separate:
No direct selection mutation for "just activation".
If activation selects, it must be a real user-visible selection operation.
Expose the smaller, deeper API:
createEditorEditor.*Transforms.*OperationRangePointPathDemote or remove:
onChange as primary notificationdecoraterenderChunkOwner:
packages/slate-reactGoal:
EditableBlocks runtime to public EditableEditableGates:
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1bunx vitest run --config ./vitest.config.mjs test/decorations.test.tsx test/use-selected.test.tsxdecorateOwner:
packages/slate-reactGoal:
decorate from final Editable propscreateSlateDecorationSource as adapter onlyGates:
Owner:
packages/slateGoal:
Gates:
bun test ./packages/slate/test/surface-contract.ts --bail 1bun test ./packages/slate/test/transaction-contract.ts --bail 1bun test ./packages/slate/test/snapshot-contract.ts --bail 1Owner:
packages/slateGoal:
getSnapshot() out of urgent pathsGates:
Owner:
packages/slate-reactGoal:
Gates:
Owner:
packages/slate-browserGoal:
slate-browserGates:
bun --filter slate-browser testbun run test:slate-browserOwner:
Goal:
decorateThis lane is done only when:
Editable is the semantic-blocks runtimeEditable implementation is removed/privateEditableBlocks is removed or temporary alias-onlyEditable has no decorate, no renderChunk, no chunkingdecorateEditor.* APIs as primary seamsPhase 1: rename current EditableBlocks runtime to public Editable.
Do not start by deleting core compatibility fields. The first dangerous API
confusion is React: the best runtime must own the Editable name.
After every slice, append:
Do not rely on chat history.
EditableActions:
packages/slate-react before browser proof so the static site used
current package output.playwright/integration/examples/richtext.test.ts with public
Editable mapped to the semantic-blocks runtime.NODE_TO_PARENT and
NODE_TO_INDEX for text nodes, not only element nodes.data-slate-dom-sync="true" capability.EditableRoot's root subscription from operation count to last commit
version so consecutive one-op commits can rerender the root when allowed by
shouldUpdate.insertText: after Slate handles
text insertion or keyboard navigation, stale DOM targetRange and
selectionchange cannot overwrite the model selection until a mouse/click
selection resets ownership.Commands:
bunx turbo build --filter=./packages/slate-react --forcebunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromiumEvidence:
__slateBrowserHandle.getText() did not include inserted text.page.keyboard.type('Undo Me') smeared characters because stale DOM
selection/target ranges stole the caret after the first character.Decision:
Editable cutover must treat model-owned input and
browser-owned input as explicit ownership modes, not let legacy target-range
repair blindly overwrite the model selection.Owner classification:
packages/slate-react owned the regression.Changed files:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsxRejected tactics:
serve-playwright.mjs process holds port 3101; it can serve stale output.Next action:
Editable cutover.Actions:
Editable cutover.slate-react major changeset for the public Editable runtime
flip.Commands:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1cd packages/slate-react && bunx vitest run --config ./vitest.config.mjs test/decorations.test.tsx test/use-selected.test.tsxbunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromiumbunx playwright test ./playwright/integration/examples/placeholder.test.ts ./playwright/integration/examples/styling.test.ts --project=chromiumbun run bench:react:rerender-breadth:localREACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:localbunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --forcebunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --forcebun run lint:fixbun run lintEvidence:
lint:fix.Decision:
EditableBlocks retained only as a
temporary alias for remaining example/doc cutover.Owner classification:
Editable browser/runtime owner.EditableBlocks usage
from examples and cut final public decorate teaching/API surfaces.Changed files:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx/Users/zbeyens/git/slate-v2/.changeset/slate-react-editable-semantic-runtime.mdRejected tactics:
bunx vitest --config ./vitest.config.mjs from repo root as a
product failure; the config lives under packages/slate-react.Next action:
EditableBlocks
imports/usages with public Editable and keeping projection-source examples
on the final API shape.EditableBlocks alias cutoverActions:
EditableBlocks with public
Editable.EditableBlocks* public type references with
EditableProps, RenderElementProps, or ComponentProps<typeof Editable>.decorate ranges. This keeps token spans split
correctly when typed code lives in one text node with embedded newlines.Commands:
bun run lint:fixbun run lintbunx playwright test ./playwright/integration/examples/code-highlighting.test.ts ./playwright/integration/examples/search-highlighting.test.ts ./playwright/integration/examples/markdown-preview.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/external-decoration-sources.test.ts ./playwright/integration/examples/review-comments.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromiumbunx playwright test ./playwright/integration/examples/code-highlighting.test.ts --project=chromiumEvidence:
[data-slate-string] contained full-line text instead of the expected
token text.EditableBlocks usage.Decision:
Editable; remaining
EditableBlocks references are package/test/benchmark alias debt.Owner classification:
Changed files:
/Users/zbeyens/git/slate-v2/site/examples/ts/code-highlighting.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/search-highlighting.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/markdown-preview.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/highlighted-text.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/external-decoration-sources.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/review-comments.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/large-document-runtime.tsxRejected tactics:
EditableBlocks for readability; the public
API name is now Editable.Next action:
Editable,
then remove the public EditableBlocks barrel export if no in-scope user
remains.EditableBlocks aliasActions:
Editable.EditableRenderElementProps internally and kept RenderElementProps as the
public export.EditableBlocks public barrel export.components/editable-blocks.tsx alias file.Commands:
bun run lint:fixbun run lintbun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1bun test ./packages/slate-react/test/app-owned-customization.tsx --bail 1bun run bench:react:rerender-breadth:localbunx playwright test ./playwright/integration/examples/code-highlighting.test.ts ./playwright/integration/examples/search-highlighting.test.ts ./playwright/integration/examples/markdown-preview.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/external-decoration-sources.test.ts ./playwright/integration/examples/review-comments.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromiumbunx playwright test ./playwright/integration/examples/code-highlighting.test.ts --project=chromiumREACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:localbunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --forcebunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --forceEvidence:
EditableBlocks,
EditableBlocksProps, or EditableBlocksRenderElementProps remains.Editable.Decision:
EditableBlocks is removed as a public concept. The only remaining
compatibility pressure is decorate adapter/API teaching, not the primary
React component name.Owner classification:
decorate teaching/API surface while
preserving projection-source adapters where explicitly named.Changed files:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/index.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-blocks.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/app-owned-customization.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/large-doc-and-scroll.tsx/Users/zbeyens/git/slate-v2/scripts/benchmarks/browser/react/rerender-breadth.tsx/Users/zbeyens/git/slate-v2/scripts/benchmarks/browser/react/huge-document-legacy-compare.mjs/Users/zbeyens/git/slate-v2/scripts/benchmarks/browser/react/huge-document-overlays.tsx/Users/zbeyens/git/slate-v2/scripts/benchmarks/browser/react/active-typing-breakdown.tsxRejected tactics:
EditableBlocks as a temporary public alias now that examples,
tests, and benchmarks are on Editable.Next action:
decorate from final public Editable typing/docs path and keep
createSlateDecorationSource as the explicitly named compatibility adapter.Actions:
slate-react Editable reference to document the final public
props: projection stores, render segments, render text, and large-document
islands.decorate from the documented EditableProps shape.renderChunk /
getChunkSize / data-slate-chunk guidance and toward
Editable.largeDocument.Editable.Commands:
bun run lintEvidence:
docs/libraries/slate-react/editable.md no longer lists decorate in
EditableProps.docs/walkthroughs/09-performance.md no longer teaches renderChunk,
getChunkSize, data-slate-chunk, or a chunking setup section.Decision:
createSlateDecorationSource as the explicitly named adapter for
callback-style decoration logic.decorate as the final public Editable API.Owner classification:
Editable surface are updated.EditableBlocks mentions in historical ledgers/plans are
archival unless they are promoted back into current reference docs.Changed files:
/Users/zbeyens/git/slate-v2/docs/libraries/slate-react/editable.md/Users/zbeyens/git/slate-v2/docs/walkthroughs/09-performance.md/Users/zbeyens/git/plate-2/docs/research/decisions/slate-v2-data-model-first-react-perfect-runtime.md/Users/zbeyens/git/plate-2/docs/slate-v2/replacement-gates-scoreboard.mdRejected tactics:
createSlateDecorationSource; named adapters are the right
boundary for compatibility.Next action:
EditableBlocks as
the active surface, then decide whether the final API/runtime shape lane is
complete or if core field demotion remains the next autonomous owner.EditableBlocksActions:
docs/slate-v2 reference/ledger rows that still described
EditableBlocks as the active surface.Commands:
rg -n "EditableBlocks" docs/slate-v2 -g '*.md'Evidence:
docs/slate-v2/** has no remaining EditableBlocks mention.Decision:
Editor.* APIs the primary documented surface.Owner classification:
Changed files:
/Users/zbeyens/git/plate-2/docs/slate-v2/ledgers/example-parity-matrix.md/Users/zbeyens/git/plate-2/docs/slate-v2/release-file-review-ledger.md/Users/zbeyens/git/plate-2/docs/slate-v2/references/architecture-contract.md/Users/zbeyens/git/plate-2/docs/slate-v2/references/replacement-family-ledger.mdRejected tactics:
Next action:
editor.children, editor.selection, editor.marks, editor.operations,
instance editor.apply, and instance editor.onChange in current docs/tests
and in-scope package source.Actions:
forced-layout now uses Editor.getChildren(editor).inlines now uses Editor.getLiveSelection(editor).Editor.getOperations,
Editor.getChildren, and Editor.apply.Editor.replace.Editor.getLiveSelection.Commands:
rg -n "\beditor\.(children|selection|marks|operations|apply|onChange)\b" ...bun run lint:fixbun run lintbunx playwright test ./playwright/integration/examples/forced-layout.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromiumEvidence:
editor.children / editor.selection / editor.operations /
editor.apply guidance.Decision:
editor.apply, Android input manager behavior, or low-level DOM
bridge internals.Owner classification:
docs/api/** that still teach
mutable fields or instance editor.apply as primary call style.Changed files:
/Users/zbeyens/git/slate-v2/site/examples/ts/forced-layout.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/inlines.tsx/Users/zbeyens/git/slate-v2/docs/walkthroughs/06-saving-to-a-database.md/Users/zbeyens/git/slate-v2/docs/walkthroughs/01-installing-slate.md/Users/zbeyens/git/slate-v2/docs/walkthroughs/09-performance.md/Users/zbeyens/git/slate-v2/docs/libraries/slate-react/hooks.md/Users/zbeyens/git/slate-v2/docs/concepts/03-locations.md/Users/zbeyens/git/slate-v2/docs/api/locations/range-ref.mdRejected tactics:
slate-history / slate-hyperscript.Next action:
docs/api/** and non-archival concept docs
away from editor.apply, editor.children, editor.selection, and
editor.marks as primary API language.Actions:
editor.apply as primary examples.editor.apply, Android input, or DOM bridge behavior.Commands:
rg -n "\beditor\.(children|selection|marks|operations|apply|onChange)\b" docs/api docs/concepts docs/walkthroughs docs/libraries/slate-react site/examples/ts/forced-layout.tsx site/examples/ts/inlines.tsxbun run lintbun test ./packages/slate/test/surface-contract.ts --bail 1bun test ./packages/slate/test/transaction-contract.ts --bail 1bun test ./packages/slate/test/snapshot-contract.ts --bail 1Evidence:
Decision:
Owner classification:
Editable surface.Changed files:
/Users/zbeyens/git/slate-v2/docs/api/transforms.md/Users/zbeyens/git/slate-v2/docs/api/nodes/editor.md/Users/zbeyens/git/slate-v2/docs/concepts/05-operations.mdRejected tactics:
Next action:
Actions:
Editable public
surface cleanup.Commands:
bun run bench:react:huge-document-overlays:localbun run bench:core:observation:compare:localbun run bench:core:huge-document:compare:localEvidence:
01 and projection recompute at 04.46ms, legacy 1.16ms10.37ms, legacy 8.93ms4.26ms, legacy 1.66ms4.13ms vs legacy 0.69ms,
middle 3.96ms vs legacy 0.51msDecision:
Owner classification:
Changed files:
Rejected tactics:
Next action:
scripts/benchmarks/core/compare/observation.mjs and
scripts/benchmarks/core/compare/huge-document.mjs, then read the core
public-state/apply paths behind the red rows.Decision:
Rationale:
Editable cutover.Deferred owner:
Next action:
createSlateDecorationSource(decorate) from current
examples that should expose direct projection-source APIs.Actions:
createSlateDecorationSource(decorate) with
direct projection-source functions.SlateProjection ranges directly from snapshots.Commands:
bun run lint:fixbun run lintbunx playwright test ./playwright/integration/examples/code-highlighting.test.ts ./playwright/integration/examples/search-highlighting.test.ts ./playwright/integration/examples/markdown-preview.test.ts --project=chromiumrg -n "EditableBlocks|renderChunk|getChunkSize|data-slate-chunk|decorate\\??:" docs/libraries docs/walkthroughs docs/api site/examples/ts packages/slate-react/src/index.ts packages/slate-react/src/components/editable-text-blocks.tsxrg -n "createSlateDecorationSource|const decorate|decorate =|decorate\\(" site/examples/ts -g '*.tsx'Evidence:
EditableBlocks,
renderChunk, getChunkSize, data-slate-chunk, or decorate?: hits.createSlateDecorationSource or local decorate
callback hits.Decision:
createSlateDecorationSource
remains available as an explicitly named adapter, but current examples teach
direct projection sources.Owner classification:
Changed files:
/Users/zbeyens/git/slate-v2/site/examples/ts/code-highlighting.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/search-highlighting.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/markdown-preview.tsxRejected tactics:
Next action:
completion-check.Actions:
Commands:
bun test:integration-localbunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "types at the browser-selected end|repairs DOM after Mac keyboard undo|undoes browser-inserted text"Evidence:
179 passed, 49 skipped, 38 failed, 2 flaky.!ZZZZThis is editable...Decision:
Owner classification:
markdown-shortcuts,
mentions)markdown-preview,
code-highlighting)paste-html code row)Changed files:
/Users/zbeyens/git/slate-v2/playwright/integration/examples/richtext.test.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable.tsx/Users/zbeyens/git/plate-2/active goal stateRejected tactics:
test:integration-local is
classified and substantially green.Next action: