docs/solutions/logic-errors/2026-04-24-slate-browser-proof-must-separate-model-owned-handles-root-selection-and-usable-focus.md
Batch 8 browser closure had a green-ish architecture but still failed across checklist focus, large-document delete, mobile paste, and WebKit shadow DOM rows. The failures looked unrelated until they were classified by ownership.
bun test:integration-local failed with 481 passed, 7 failed.slate-browser package dist
was rebuilt.press() focused
the editor and restored the model selection before sending the shortcut.move-selection command, which made ownership traces lie.Make browser-handle model operations explicitly model-owned before implicit selection-sensitive mutations:
setEditableModelSelectionPreference({
inputController,
preferModelSelection: true,
selectionSource: 'model-owned',
})
Apply that before runCommand(...) reads live selection and before
selectRange(...) calls Transforms.select(...).
In slate-browser, split selection containment from usable keyboard focus:
press() must preserve an existing usable DOM selection and focus only when
no root selection or usable keyboard focus existsFor shadow DOM, read selection through the editor root when possible:
ShadowRoot.getSelection() when the root exposes itselectionchange on the shadow root when setting shadow selectionBefore browser proof that imports slate-browser/playwright, rebuild the
touched package graph:
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
For internal controls, cut command classification at the owner boundary:
const internalTarget = isInteractiveInternalTarget(editor, event.target)
const command = internalTarget
? null
: getEditableCommandFromKeyDown({ event, selection })
The fixes preserve the ownership split.
Browser handles are semantic proof tools, so their commands should not import browser DOM selection unless the row is explicitly testing DOM import. App-owned controls can be inside the editor root without being valid text-input targets, so keyboard helpers need a real focus check. Shadow DOM has different selection visibility across engines, so WebKit proof has to avoid treating non-contained document selection as editor state. Rebuilding package output makes Playwright test the code that the site actually imports.
Keyboard proof needs the same ownership discipline. A helper that focuses
unconditionally can erase the user's DOM selection before the browser event.
An internal-control keydown can bubble through the editor root, but it should
not synthesize a Slate movement command; the trace should classify the target
as internal-control and leave selected-content mutation ownership alone.
slate-react runtime, slate-browser harness, slate-dom bridge, core, or
accepted platform limitation.targetOwner: internal-control, selection policy none) and absence of
Slate movement commands.dist.