docs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.md
Done.
Execution started from complete-plan on 2026-04-27.
Current next owner: complete. The Editable runtime/root selector hard-cut lane meets its closure gates.
This plan resolves the two current architecture findings:
EditableDOMRoot still owns too much hot engine policy.useSlateSelector and snapshot reads
where named source selectors should own the facts.Do not treat this as a local cleanup. This is the next React 19.2 architecture
cut. The goal is not a smaller Editable file for aesthetics. The goal is a
runtime-owned editing engine with React as projection and wiring.
Editable owns too much engine policyCurrent hot owner:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEditableDOMRoot, especially selectionchange,
composition, Android input, repair, kernel trace, and force-render wiringObserved live responsibilities still inside EditableDOMRoot:
Editor.getLastCommit(...)EDITOR_TO_FORCE_RENDER registrationonDOMSelectionChange throttlingHarsh call: this is still too much editor engine inside a React component. React should wire refs/listeners and subscribe to runtime state. Runtime modules should decide selection import/export, repair, composition, Android, kernel trace, and forced view repair policy.
Current hot owners:
.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsxObserved live pattern:
useSlateSelectorEditor.getSnapshot(...)Harsh call: some root-level subscription is legitimate, but the current shape does not meet the absolute architecture bar. Named source selectors should own root runtime ids, selected top-level index, placeholder visibility, and island planning inputs.
React is a projection layer.
Runtime owns hot editing policy.
Named selector sources own root render facts.
Browser proof owns regression safety.
The final shape should make these statements true:
EditableDOMRoot wires root attributes, refs, listeners, and children.Editable policy creep
from returning.Editor.getLive*.forceRender().bun check.The runtime module graph should make ownership obvious:
editable/runtime-live-state.ts
editable/runtime-selection-state.ts
editable/runtime-mutation-state.ts
editable/runtime-selection-engine.ts
editable/runtime-repair-engine.ts
editable/runtime-composition-engine.ts
editable/runtime-android-engine.ts
editable/runtime-kernel-trace.ts
editable/root-selector-sources.ts
Names can change during implementation. Ownership cannot.
EditableDOMRoot should become a small wiring component:
scrollSelectionIntoViewIt should not contain:
Root render facts should have named hooks, for example:
useRootRuntimeIds(...)useSelectedTopLevelIndex(...)usePlaceholderVisibility(...)useLargeDocumentIslandInputs(...)The exact public/internal naming can change, but each source must own:
EditableTextBlocks should consume these facts. It should not build them with
inline useSlateSelector calls.
Purpose: avoid a blind extraction that changes browser timing.
Actions:
EditableDOMRoot responsibilities and classify each as:
selection, repair, composition, Android, kernel trace, force render, root
state, or React-only wiring.useSlateSelector calls under packages/slate-react/src.Acceptance:
Likely files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx.tmp/slate-v2/packages/slate-react/src/hooks/use-slate-selector.tsx.tmp/slate-v2/packages/slate-react/test/surface-contract.tsxTests:
.tmp/slate-v2/packages/slate-react/test/surface-contract.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.tsPurpose: remove the largest hot policy closure from EditableDOMRoot.
Actions:
onDOMSelectionChange into a runtime selection engine.EditableDOMRoot wires the returned handlers; it does not construct the
selection policy.Acceptance:
EditableDOMRoot no longer directly calls:
getEditableSelectionChangeOwnershipbeginEditableEventFrameapplyEditableDOMSelectionChangerecordEditableKernelTracecompleteEditableSelectionChangeImport
inside the component body.Likely files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-runtime.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.tsTests:
.tmp/slate-v2/packages/slate-react/test/selection-runtime-contract.test.ts.tmp/slate-v2/packages/slate-react/test/selection-controller-contract.ts.tmp/slate-v2/packages/slate-react/test/editing-epoch-kernel-contract.tsPurpose: make repair decisions explicit and stop leaking forceRender policy
through React component state.
Actions:
EDITOR_TO_FORCE_RENDER only as a compatibility bridge if no direct
replacement is possible in this slice.forceRender() calls in slate-react/src
are owned by the repair/view runtime or an audited proof handle.forceRender prop threading with a named repair request API
where feasible.Acceptance:
EditableDOMRoot does not register bare forceRender as policy.forceRender() calls are allowlisted with owners.Likely files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/dom-repair-queue.ts.tmp/slate-v2/packages/slate-react/src/editable/mutation-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/browser-handle.ts.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-repair-engine.tsTests:
.tmp/slate-v2/packages/slate-react/test/editing-kernel-contract.ts.tmp/slate-v2/packages/slate-react/test/target-runtime-contract.ts.tmp/slate-v2/packages/slate-react/test/rendered-dom-shape-contract.tsxPurpose: isolate platform-specific editing policy from React render code.
Actions:
EditableDOMRoot responsible for passing refs and listener handles
only.Acceptance:
EditableDOMRoot no longer owns composition abort rules.EditableDOMRoot no longer owns Android manager lifecycle policy.Likely files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/composition-state.ts.tmp/slate-v2/packages/slate-react/src/editable/native-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-composition-engine.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-android-engine.tsTests:
.tmp/slate-v2/packages/slate-react/test/editing-epoch-kernel-contract.ts.tmp/slate-v2/packages/slate-react/test/large-doc-and-scroll.tsxPurpose: resolve the root selector finding without inventing fake micro-hooks.
Actions:
EditableTextBlocks does not add new inline
useSlateSelector calls without an allowlist entry.Acceptance:
EditableTextBlocks consumes named source hooks/selectors for root facts.useSlateSelector calls in EditableTextBlocks are gone or
explicitly allowlisted with owner comments and test coverage.Likely files:
.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts.tmp/slate-v2/packages/slate-react/src/hooks/use-slate-selector.tsx.tmp/slate-v2/packages/slate-react/test/provider-hooks-contract.tsx.tmp/slate-v2/packages/slate-react/test/surface-contract.tsxTests:
Purpose: prevent the same regression class from returning under deadline pressure.
Actions:
EditableDOMRoot forbidden responsibilities.forceRender() owners.slate/internal import guard.Acceptance:
EditableDOMRoot fails a package
contract unless it is explicitly allowlisted.useSlateSelector in EditableTextBlocks fails a
package contract unless it is explicitly classified.forceRender() call outside repair/view owners fails a
package contract.Likely tests:
.tmp/slate-v2/packages/slate-react/test/surface-contract.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-browser/src/core/release-proof.tsPurpose: prove this was not just an architectural reshuffle.
Actions:
test:stress for generated operation-family rows, not default CI.Acceptance:
test:stress passes for the touched operation families.bun check passes.bun check:full passes before marking this plan complete.Reason for this order:
Editable policy owner.slate-react surface contractsbun checkbun test:stress scoped to touched families during iterationbun check:full before setting completion state to doneThis plan is complete only when:
EditableDOMRoot no longer owns hot selection, repair, composition, Android,
force-render, or kernel trace policy.bun check:full passes, with any retry investigated and recorded.Replan if:
forceRender() use grows instead of shrinking.slate-dom keep direct slate/internal access as the DOM bridge
owner, or should it get its own internal facade layer too?editable/ or a new
runtime-sources/ folder once there are more than a few?This plan is active.
active goal state is pending and points at this file.
Actions:
active goal state for the Editable runtime/root selector
lane.active goal state.Commands:
bun run completion-check passed before activation while the previous lane
was still marked done.Artifacts:
active goal stateactive goal stateEvidence:
Hypothesis:
EditableDOMRoot policy and root selector use, before moving timing-sensitive
selection or repair code.Decision:
Owner classification:
slate-react architecture/test guard ownership.Changed files:
active goal stateactive goal statedocs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.mdRejected tactics:
Next action:
.tmp/slate-v2/packages/slate-react hot owner files and add/update the
Phase 0 static inventory guard.Actions:
useSlateSelector ownership inventory to the slate-react surface
contract.EditableDOMRoot hot policy ownership inventory to the kernel
authority audit contract..test.ts wrapper so the kernel authority audit actually
runs under Vitest.import.meta.dir path root with
fileURLToPath(import.meta.url).Commands:
bun --filter slate-react test:vitest -- kernel-authority-audit-contract surface-contract
bun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsxbun --filter slate-react typecheckbun lint:fixArtifacts:
.tmp/slate-v2/packages/slate-react/test/surface-contract.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.test.tsEvidence:
slate-react typecheck passes.bun lint:fix reports no fixes needed.Hypothesis:
Decision:
Owner classification:
EditableDOMRoot hot policy debt is now inventoried, with selectionchange
owned next by a runtime selection engine.Changed files:
active goal stateactive goal statedocs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.md.tmp/slate-v2/packages/slate-react/test/surface-contract.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.test.tsRejected tactics:
.test contract files as live guards unless they are
imported by an included test wrapper.Next action:
onDOMSelectionChange construction into
.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.ts
and leave EditableDOMRoot as ref/listener wiring.Actions:
onDOMSelectionChange policy into
.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.ts.EditableDOMRoot responsible for wiring refs/controllers and passing
the returned callbacks to Android/input/selection listeners..test.ts wrappers for the plan-referenced selection/kernel contracts
so they run under the package Vitest include pattern.Commands:
bun --filter slate-react typecheckbun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsxbun --filter slate-react test:vitest test/selection-runtime-contract.test.ts test/selection-controller-contract.test.ts test/editing-kernel-contract.test.ts test/editing-epoch-kernel-contract.test.tsbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/hovering-toolbar.test.ts playwright/integration/examples/richtext.test.ts --project=chromium --grep "hovering toolbar|selectionchange|persistent native word-delete"Artifacts:
.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-react/test/editing-epoch-kernel-contract.test.ts.tmp/slate-v2/packages/slate-react/test/editing-kernel-contract.test.ts.tmp/slate-v2/packages/slate-react/test/selection-controller-contract.test.tsEvidence:
slate-react package Vitest suite passes: 18 files, 97 tests.slate-react typecheck passes.slate-react build passes. It reports the existing externalized
is-hotkey warning from slate-dom.bun lint:fix passes.Hypothesis:
Decision:
Owner classification:
EditableDOMRoot still owns other event-family trace/repair policy and
remains open for later phases.Changed files:
active goal statedocs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.md.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.ts.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-react/test/editing-epoch-kernel-contract.test.ts.tmp/slate-v2/packages/slate-react/test/editing-kernel-contract.test.ts.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.test.ts.tmp/slate-v2/packages/slate-react/test/selection-controller-contract.test.tsRejected tactics:
Next action:
.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts
and move top-level runtime ids plus selected top-level index selector bodies
out of EditableTextBlocks.Actions:
.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts.EditableTextBlocks.EditableTextBlocks.useSlateSelector ownership inventory.Commands:
bun --filter slate-react test:vitest test/surface-contract.test.tsx test/provider-hooks-contract.test.tsxbun --filter slate-react typecheckbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/large-document-runtime.test.ts --project=chromiumArtifacts:
.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx.tmp/slate-v2/packages/slate-react/test/provider-hooks-contract.tsx.tmp/slate-v2/packages/slate-react/test/surface-contract.tsxEvidence:
slate-react package Vitest suite passes: 18 files, 98 tests.slate-react typecheck passes.slate-react build passes with the existing externalized is-hotkey
warning.Hypothesis:
Decision:
Owner classification:
root-selector-sources.ts.Changed files:
active goal statedocs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.md.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts.tmp/slate-v2/packages/slate-react/test/provider-hooks-contract.tsx.tmp/slate-v2/packages/slate-react/test/surface-contract.tsxRejected tactics:
Next action:
requestEditableRepair/direct forceRender application behind a runtime
repair/view module.Actions:
.tmp/slate-v2/packages/slate-react/src/editable/runtime-repair-engine.ts.EDITOR_TO_FORCE_RENDER registration into the runtime repair
engine.useRuntimeRepairEngine(...).forceRender() call owners.Commands:
bun --filter slate-react typecheckbun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsx test/editing-kernel-contract.test.tsbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|selectionchange|rendered DOM shape after repeated leaf-boundary word-delete"PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/richtext.test.ts --project=chromium --grep "keeps model and DOM coherent after persistent native word-delete"Artifacts:
.tmp/slate-v2/packages/slate-react/src/editable/runtime-repair-engine.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.tsEvidence:
slate-react package Vitest suite passes: 18 files, 99 tests.slate-react typecheck passes.slate-react build passes with the existing externalized is-hotkey
warning.bun lint:fix passes.Hypothesis:
check:full closure.Decision:
Owner classification:
runtime-repair-engine.ts.Changed files:
active goal statedocs/plans/2026-04-27-slate-v2-editable-runtime-and-root-selector-hard-cut-plan.md.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/runtime-repair-engine.ts.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.tsRejected tactics:
forceRender() calls blindly; browser proof
handle and keyboard fallback need separate owner cuts.Next action:
EditableDOMRoot as
ref/listener wiring.Actions:
runtime-composition-engine.ts and runtime-android-engine.ts.setEditableComposingState(...) and
useAndroidInputManager(...) ownership from EditableDOMRoot.EditableDOMRoot as ref/listener wiring for platform handlers.Commands:
bun --filter slate-react typecheckbun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/editing-epoch-kernel-contract.test.tsbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "composition|Android|IME"Artifacts:
.tmp/slate-v2/packages/slate-react/src/editable/runtime-composition-engine.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-android-engine.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.tsEvidence:
slate-react package Vitest suite passes: 18 files, 99 tests at this
checkpoint.slate-react typecheck passes.slate-react build passes with the existing externalized is-hotkey
warning.Decision:
Actions:
root-selector-sources.ts.useLargeDocumentRootSources(...).EditableDOMRoot and into
useEditableRootCommitWakeup().useSlateSelector calls from both
EditableTextBlocks and EditableDOMRoot.Commands:
bun --filter slate-react test:vitest test/surface-contract.test.tsx test/provider-hooks-contract.test.tsxbun --filter slate-react typecheckbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/placeholder.test.ts --project=chromiumPLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/large-document-runtime.test.ts --project=chromiumArtifacts:
.tmp/slate-v2/packages/slate-react/src/editable/root-selector-sources.ts.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/provider-hooks-contract.tsx.tmp/slate-v2/packages/slate-react/test/surface-contract.tsxEvidence:
slate-react package Vitest suite passes: 18 files, 100 tests.Decision:
Actions:
runtime-kernel-trace.ts and moved non-selectionchange event frame,
trace, DOM-input repair trace, and keydown trace payload construction out of
EditableDOMRoot.runtime-repair-engine.ts.createRuntimeSelectionImportController(...).EditableDOMRoot policy ownershipforceRender() ownershipreact-runtime:bridge count from 28 to 27.Commands:
bun --filter slate-react typecheckbun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsx test/selection-controller-contract.test.ts test/editing-kernel-contract.test.ts test/editing-epoch-kernel-contract.test.tsbun --filter slate-react test:vitestbun --filter slate-react buildbun lint:fixbun test:release-disciplineArtifacts:
.tmp/slate-v2/packages/slate-react/src/editable/runtime-kernel-trace.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-selection-engine.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-repair-engine.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts.tmp/slate-v2/packages/slate-react/test/surface-contract.tsx.tmp/slate-v2/packages/slate/test/escape-hatch-inventory-contract.tsEvidence:
slate-react package Vitest suite passes: 18 files, 100 tests.slate-react typecheck and build pass.Decision:
Actions:
Commands:
PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/hovering-toolbar.test.ts playwright/integration/examples/mentions.test.ts playwright/integration/examples/tables.test.ts playwright/integration/examples/images.test.ts playwright/integration/examples/search-highlighting.test.ts playwright/integration/examples/placeholder.test.ts playwright/integration/examples/large-document-runtime.test.ts playwright/integration/examples/richtext.test.ts --project=chromiumbun test:stressbun check:fullArtifacts:
.tmp/slate-v2/test-results/release-proof/persistent-browser-soak.json.tmp/slate-v2/tmp/stress-artifactsEvidence:
bun check:full passes:
bun checkslate-browser release proofDecision:
active goal state can be set to done.