docs/plans/2026-04-09-slate-v2-extension-model-behavior-interception-completion-plan.md
Close the extension model / behavior interception lane in
master-roadmap.md
and turn the corresponding open bucket in
release-file-review-ledger.md
from partial to closed-by-proof.
For this lane, “100% completion” means:
The current repo already recovered a lot of extension surface:
createEditor()Editor.* delegation through the instance seamnormalizeNode(...) and shouldNormalize(...)withHistory(...)withReact(...)That is real progress, but it still reads like a pile of recovered seams rather than one finished extension model.
Right now the strongest proofs are:
snapshot-contract.ts monkey-patch rowsforced-layout.tsxwithHistory / withReact compatibility helpersThat is not yet the same thing as proving:
Do not treat this lane as a greenfield middleware rewrite.
The live completion target is the current instance-method plus transaction boundary model:
editor.apply(op) stays the low-level seamwithHistory(...) / withReact(...) stay explicit wrappersnormalizeNode(...) stays the real schema-extension seamThe future named middleware-phase architecture in architecture-contract.md remains reference-only.
If this plan tries to land that future architecture, it will balloon and miss the actual lane closeout.
markableVoid, insertBreak, insertSoftBreak, and the real
normalizeNode(...) seamshouldNormalize(...) into a pass-level gateextension model rows, but they are still partialnormalizeNode(...) wrappercreateEditor()withHistory(...) and withReact(...) is not yet
the organizing proof story of the laneThis lane is done when all of the following are true:
createEditor()withHistory(createEditor())withReact(createEditor())true-slate-rc-proof-ledger.md can describe the lane without overloaded
“partial” rows hiding missing capability coveragerelease-file-review-ledger.md flips the extension-model bucket closedFiles:
Work:
Reason:
snapshot-contract.ts
rowsFiles:
/Users/zbeyens/git/slate-v2/packages/slate/test/extension-contract.ts (new)/Users/zbeyens/git/slate-v2/packages/slate/test/snapshot-contract.tsWork:
snapshot-contract.ts as the broad surface oracleextension-contract.ts for explicit wrapper/interception scenariosTest scenarios:
insertText(...) and delegates through the current
engine while preserving transaction semanticsdeleteBackward(...), deleteForward(...), or
deleteFragment(...) and reads live draft selection correctly inside
Editor.withTransaction(...)insertBreak(...) and delegates through the same
instance seam Editor.* useseditor.apply(op) still works as the low-level seam under wrapped editorsFiles:
/Users/zbeyens/git/slate-v2/site/examples/ts/forced-layout.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/plugins/with-forced-layout.ts (new)/Users/zbeyens/git/slate-v2/packages/slate/test/normalization-contract.ts/Users/zbeyens/git/slate-v2/packages/slate-react/test/runtime.tsxWork:
forced-layout normalizer into a named wrapper moduleTest scenarios:
Editor.replace(...) and explicit normalize passesFiles:
/Users/zbeyens/git/slate-v2/site/examples/ts/plugins/with-links.ts (new)/Users/zbeyens/git/slate-v2/site/examples/ts/components/links-surface.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/runtime.tsx/Users/zbeyens/git/slate-v2/playwright/integration/examples/links.test.tsWork:
EditableBlocks isInline={...} as the main story for the links
exampleTest scenarios:
editor.isInline(...)
behavior instead of only a render propwithHistory(...)Files:
/Users/zbeyens/git/slate-v2/site/examples/ts/plugins/with-mentions.ts (new)/Users/zbeyens/git/slate-v2/site/examples/ts/components/mentions-surface.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/runtime.tsx/Users/zbeyens/git/slate-v2/playwright/integration/examples/mentions.test.tsWork:
Test scenarios:
isInline(...)withHistory(...)Files:
/Users/zbeyens/git/slate-v2/packages/slate-history/test/history-contract.ts/Users/zbeyens/git/slate-v2/packages/slate-react/test/runtime.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/surface-contract.tsxWork:
withHistory(withLinks(createEditor()))withHistory(withMentions(createEditor()))withReact(withLinks(createEditor()))withReact(withMentions(createEditor()))withReact(withForcedLayout(createEditor()))Test scenarios:
Files:
Work:
Primary test files:
/Users/zbeyens/git/slate-v2/packages/slate/test/extension-contract.ts/Users/zbeyens/git/slate-v2/packages/slate/test/normalization-contract.ts/Users/zbeyens/git/slate-v2/packages/slate-history/test/history-contract.ts/Users/zbeyens/git/slate-v2/packages/slate-react/test/runtime.tsx/Users/zbeyens/git/slate-v2/playwright/integration/examples/links.test.ts/Users/zbeyens/git/slate-v2/playwright/integration/examples/mentions.test.ts/Users/zbeyens/git/slate-v2/playwright/integration/examples/forced-layout.test.tsVerification bar:
extension-contract.tswith-forced-layoutwith-linkswith-mentionssite/examples/ts/plugins/ when that produces cleaner representative
ports and proof.