docs/plans/2026-04-22-slate-v2-editing-navigation-gauntlet-review-plan.md
Prove or falsify that v2 Editable is usable for real browser editing
navigation, not just isolated model edits.
The first tracer is one raw-browser gauntlet around richtext marked-leaf navigation:
If this fails, the previous green suite was too fragmented.
The likely architecture smell is that v2 split Editable into strategy modules
without first defining one central browser editing state machine. Splitting a
monolith is not enough if selection ownership remains distributed across
keyboard strategy, beforeinput strategy, selectionchange, DOM repair, and handle
helpers.
keyboard-input-strategy over-owns arrow movement and prevents browser
default where native DOM selection should own movement.syncDOMSelectionToEditor / syncSelectionForBeforeInput lets stale DOM
or model-owned selection modes fight each other after arrow movement.Editable had ugly but cohesive restore/selection timing; v2 may
have copied branches without preserving one timing model..tmp/slate-v2/playwright/integration/examples/richtext.test.ts../slate/packages/slate-react/src/components/editable.tsxThis lane is complete only when the gauntlet is green or the exact blocker is named with evidence.
Status: fixed and verified.
User-visible bug:
ArrowDown.ArrowRight.ArrowDown, but
Slate's model selection stayed in paragraph 1.ArrowRight used stale model selection and snapped the
visible caret back to paragraph 1.Root cause:
ArrowDown is browser-native in the current Slate hotkey table.ArrowRight is Slate/model-owned.selectionchange is debounced through scheduleOnDOMSelectionChange.handleKeyDown flushed only onDOMSelectionChange, not the scheduled
debounce, and then immediately ran model-owned key handling.Fix:
syncEditorSelectionFromDOM(...) in
.tmp/slate-v2/packages/slate-react/src/editable/selection-reconciler.ts.EditableDOMRoot now imports current in-editor DOM selection before
model-owned key handling.selectWithHandle helper now also places DOM selection, so tests
cannot hide model/DOM selection drift behind handle-only setup.Evidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "ArrowDown then ArrowRight"
bunx playwright test ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "restores outer"
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Arrow|word movement|line extension|browser-selected|visual caret|Backspace|Delete|ArrowDown"
bun test:integration-local
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
13 passed360 passedNo, the current slate-react browser editing architecture is not the absolute
best yet.
The broad direction is still right:
EditableBut the browser editing layer is still too weak. The bug proved it.
The current split into:
keyboard-input-strategymodel-input-strategynative-input-strategyselection-reconcilerdom-repair-queueclipboard-input-strategytarget-policyis better than one giant file, but it is not yet a real browser editing state machine. It lets ownership decisions happen in too many places. The result is local fixes that fight each other:
That is not clean enough for an editor.
Recover:
Editable event orderingDo not recover:
decorateLegacy's advantage here was not elegance. It was that selectionchange, keydown, DOM selection restoration, and browser fallbacks lived in one timing model. V2 split the file before the timing model was explicit.
Steal:
InputState as a single place for input/selection provenancelastSelectionOrigincaptureKeyDown as a bounded browser-key fallback layerDo not steal:
The important lesson is the order: flush DOM observer and selection state before commands that depend on editor state.
Steal:
Do not steal:
The important lesson is that selection update is not a side-effect scattered around event handlers. It is part of the editor update lifecycle.
Steal:
Do not steal:
The important lesson is that visual movement is not "just selectionchange". Vertical movement needs a view-coordinate owner.
Steal:
syncSelection() at the start of input handlingDo not steal:
The important lesson is humility: contenteditable is hostile. The input controller must be explicit and boring.
EditableDOMRoot remain a coordinator with hidden policy branches.decorate or chunking as primary runtime concepts.Rewrite the browser editing layer around one explicit owner:
EditableInputController
It should own:
The strategy files can stay, but they should be workers under this controller, not peers independently mutating state.
Required state shape:
type SelectionProvenance =
| "dom-current"
| "model-owned"
| "internal-control"
| "composition"
| "shell-backed"
| "unknown";
type InputIntent =
| "native-selection-move"
| "model-selection-move"
| "text-insert"
| "delete"
| "format"
| "clipboard"
| "composition"
| "history"
| "internal-control";
Golden rule:
Editor.getLiveSelection(editor), the
controller must either import DOM selection or explicitly prove model-owned
selection is the active source of truth.Editable tests around focus,
selectionchange, input, beforeinput, keydown, composition, and restore-dom
familyEditable as public APICurrent tests are too row-isolated.
Keep focused rows, but add one mandatory gauntlet per browser editing class:
Navigation Gauntlet
Mutation Gauntlet
Formatting Gauntlet
mod+bClipboard Gauntlet
Composition Gauntlet
Embedded Target Gauntlet
Every gauntlet must assert:
The browser handle can set up state, but a gauntlet must include at least one DOM-only selection setup or native click path. Otherwise it is not browser proof.
We should not restore the legacy Editable file wholesale.
We should recover its timing discipline and rewrite v2 around a single input
controller. The current architecture is directionally right but still too weak
because ownership is distributed. The next big lane should not be another
local patch; it should be the EditableInputController rewrite with gauntlet
tests as the acceptance gate.
Recover browser-editing timing discipline without regressing the v2 runtime architecture.
The controller must make this invariant true:
Before any operation reads or mutates Slate selection, the runtime has one explicit selection source of truth: current DOM selection, model-owned selection, composition-owned selection, shell-backed selection, or app/native-owned internal target.
No strategy module should independently decide this.
The EditableInputController plan above is the minimum durable repair.
If this is a rewrite and compatibility can lose, the better plan is stronger:
Stop treating browser keyboard navigation as editor truth.
The browser can remain the transport for text input, IME, pointer selection, clipboard, accessibility, and platform services. It should not be the source of truth for editor cursor movement.
Create four runtime owners:
EditableInputController
EditableSelectionController
EditableCaretEngine
EditableMutationController
This is closer to VS Code's cursor/view-model split than legacy Slate. It keeps Slate's document model but stops delegating critical cursor semantics to contenteditable.
Transforms.select(...).ReactEditor.focus(...).preferModelSelectionForInputRef.current = ....selectionchange as a generic authoritative selection source.Editable props that imply legacy behavior:
decorate as primary overlay APIrenderChunkEditableRoot/legacy renderer exportseditor.selectioneditor.childreneditor.markseditor.operationseditor.apply / onChange as the normal extension surface.Keep compat only if explicitly named:
slate-react/compatcreateSlateDecorateCompatSourceThe browser still owns transport where the platform is genuinely better:
But after transport enters the runtime, Slate owns the model state and the caret state.
The caret engine must support:
Core API it needs:
Editor.getLiveText(editor, path);
Editor.getLiveNode(editor, path);
Editor.getPathByRuntimeId(editor, id);
Editor.getRuntimeId(editor, path);
Editor.getDirtyRuntimeIds(editor, commit);
Editor.getLastCommit(editor);
DOM bridge API it needs:
toDOMPoint(point);
toSlatePoint(domPoint);
getClientRects(point | range);
findClosestPointByRect(rect, direction);
syncDOMSelection(selection);
This must be a runtime service, not a React hook hidden inside text components.
React should render structure and subscribe to runtime stores. It should not own urgent cursor truth.
Use React for:
Keep outside React state:
React 19.2 helps by making the rendered tree cheaper and safer:
But the browser-editing truth must live in the runtime controller, not React component state.
The current suite must become an executable state-machine spec.
Required test layers:
Controller unit tests
Caret engine contract tests
Browser gauntlets
Platform matrix
Perf guardrails
Editor.getSnapshot() readsslate-browser should become the browser-editing proof platform, not just a
bag of Playwright helpers.
The package should own:
Add or harden these modules:
packages/slate-browser/src/
core/
proof.ts
selection.ts
scenario.ts
trace.ts
reducer.ts
browser/
selection.ts
geometry.ts
transport.ts
zero-width.ts
playwright/
editor.ts
scenario.ts
assertions.ts
trace.ts
ime.ts
transports/
contracts.ts
playwright.ts
appium.ts
agent-browser.ts
Define a serializable scenario format:
type SlateBrowserScenario = {
name: string;
surface: { example: string; scope?: string };
setup: ScenarioStep[];
steps: ScenarioStep[];
assertions: ScenarioAssertion[];
capabilities?: BrowserCapabilityRequirement[];
};
type ScenarioStep =
| { kind: "select-dom"; range: SelectionSnapshot }
| { kind: "select-model"; range: SelectionSnapshot }
| { kind: "click"; selector?: string; clickCount?: number }
| { kind: "key"; key: string }
| { kind: "type"; text: string; transport?: "native" | "semantic" }
| {
kind: "paste";
html?: string;
text: string;
transport?: "native" | "semantic";
}
| { kind: "compose"; text: string; transport?: "native" | "synthetic" }
| { kind: "blur" }
| { kind: "focus" }
| { kind: "wait"; ms: number };
type ScenarioAssertion =
| { kind: "model-text"; value: string | RegExp }
| { kind: "visible-text"; value: string | RegExp }
| { kind: "model-selection"; value: SelectionSnapshot | null }
| { kind: "dom-selection"; value: DOMSelectionSnapshot | null }
| { kind: "caret-rect-near"; value: SelectionRectSnapshot }
| { kind: "focus-owner"; value: "editor" | "internal-control" | "outside" }
| { kind: "trace"; value: Partial<EventTraceEntry>[] };
Scenarios should live beside tests or under:
playwright/integration/scenarios/
Every serious browser regression gets a scenario artifact, not only a bespoke test body.
Every scenario run should optionally capture:
type EventTraceEntry = {
eventType: string;
inputType?: string;
key?: string;
targetKind: "editor" | "internal-control" | "nested-editor" | "outside";
before: {
selectionSource: SelectionSource;
modelSelection: SelectionSnapshot | null;
domSelection: DOMSelectionSnapshot | null;
};
intent: InputIntent;
action:
| "native-default"
| "model-command"
| "ignored"
| "internal-control"
| "composition"
| "clipboard";
after: {
selectionSource: SelectionSource;
modelSelection: SelectionSnapshot | null;
domSelection: DOMSelectionSnapshot | null;
repair: "none" | "sync-selection" | "repair-caret" | "force-render";
};
};
On failure, write:
test-results/.../slate-browser-trace.json
test-results/.../slate-browser-scenario.json
test-results/.../slate-browser-repro.md
The failure output should answer:
slate-browser should not guess platform truth per row.
It should expose:
type BrowserCapability =
| "native-keyboard-contenteditable"
| "native-beforeinput"
| "native-composition"
| "clipboard-read"
| "clipboard-write"
| "clipboard-event-payload"
| "dom-selection-range"
| "visual-caret-rect"
| "shadow-dom-selection";
Capabilities are detected per project/browser:
const capabilities = await editor.capabilities.detect();
Rows can require or narrow:
await editor.capabilities.require("visual-caret-rect");
await editor.capabilities.narrow("clipboard-read", "webkit denies readText");
No more ad hoc if (project.name === 'mobile') unless the helper itself is
encoding a known transport capability.
Add deterministic scenario generation:
type ScenarioSeed = {
documentShape:
| "richtext"
| "decorated"
| "inline"
| "void"
| "shell"
| "shadow";
operations: readonly InputIntent[];
seed: number;
};
Start with bounded generators:
On failure:
This is the self-improving loop: failures become durable scenarios.
slate-browser helpers must separate setup modes:
selection.selectModel(...): semantic setup onlyselection.selectDOM(...): raw browser setupselection.selectUser(...): click/drag/native user-path setupAssertions must name layer:
assert.modelSelection(...)assert.domSelection(...)assert.visualCaret(...)assert.focusOwner(...)No helper named simply select(...) should hide whether it changed model,
DOM, or both.
Add named reusable gauntlets:
editor.gauntlets.navigation();
editor.gauntlets.mutation();
editor.gauntlets.formatting();
editor.gauntlets.clipboard();
editor.gauntlets.composition();
editor.gauntlets.internalTargets();
editor.gauntlets.largeDocument();
Each gauntlet must run:
Fast required suite:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "gauntlet|ArrowDown"
bun test ./packages/slate-browser/test/core
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
Full browser editing suite:
bun test:integration-local
Nightly / pre-release:
bunx playwright test ./playwright/integration/scenarios --project=chromium --project=firefox --project=webkit --project=mobile
Perf:
bun run bench:react:rerender-breadth:local
REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:local
slate-browser is strong enough when:
The rewrite is complete only when:
selectionchange imports selection only for classified DOM-owned gesturesLexical has a stronger update lifecycle than current v2, but it ties the model to its node/update ontology. ProseMirror has battle-tested input timing, but its schema/view architecture is not Slate's JSON-first model.
The best Slate v2 shape is:
That is the architecture worth building. Anything less keeps producing arrow-key whack-a-mole.
Primary code owners:
.tmp/slate-v2/packages/slate-react/src/editable/**.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-browser/src/playwright/** only for test helper
honesty.tmp/slate-v2/playwright/integration/examples/**Do not touch:
.tmp/slate-v2/packages/slate-history/** unless a focused failing history row
proves ownership.tmp/slate-v2/packages/slate-hyperscript/**.tmp/slate-v2/packages/slate/** unless a focused row proves core selection
transform ownershippreferModelSelectionForInputRef mutation outside the controller.decorate, or old
Editable public runtime.Create:
editable/input-controller.tseditable/input-state.tseditable/input-intent.tsCore types:
type InputIntent =
| "native-selection-move"
| "model-selection-move"
| "text-insert"
| "delete"
| "format"
| "history"
| "clipboard"
| "composition"
| "internal-control"
| "shell-selection";
type SelectionSource =
| "dom-current"
| "model-owned"
| "composition-owned"
| "shell-backed"
| "internal-control"
| "unknown";
type EditableInputControllerState = {
activeIntent: InputIntent | null;
selectionSource: SelectionSource;
isComposing: boolean;
isDraggingInternally: boolean;
isUpdatingSelection: boolean;
latestElement: DOMElement | null;
pendingDOMSelectionImport: boolean;
};
Controller responsibilities:
Strategy modules become pure workers:
keyboard-input-strategy: receives classified intent and current selection;
does not decide selection source.model-input-strategy: performs model mutations and returns repair requests.native-input-strategy: decides native/default viability, not selection
source.clipboard-input-strategy: performs copy/cut/paste after controller has
classified target and transport.selection-reconciler: converts/syncs selections, but does not decide when
to trust DOM vs model.dom-repair-queue: executes repairs requested by controller.Actions:
preferModelSelectionForInputRef.currentstate.isUpdatingSelectionIS_FOCUSEDTransforms.selectReactEditor.focusProof commands:
rg -n "preferModelSelectionForInputRef|isUpdatingSelection|IS_FOCUSED|Transforms\\.select|ReactEditor\\.focus" .tmp/slate-v2/packages/slate-react/src
bun test:integration-local
Exit:
Status: complete.
Command:
rg -n "preferModelSelectionForInputRef|isUpdatingSelection|IS_FOCUSED|Transforms\\.select|ReactEditor\\.focus" packages/slate-react/src
Findings:
preferModelSelectionForInputRef.current is mutated from:
editable/model-input-strategy.tseditable/keyboard-input-strategy.tseditable/clipboard-input-strategy.tseditable/selection-reconciler.tscomponents/editable.tsxTransforms.select(...) is called from:
hooks/android-input-manager/android-input-manager.tseditable/model-input-strategy.tseditable/keyboard-input-strategy.tseditable/dom-repair-queue.tseditable/browser-handle.tseditable/clipboard-input-strategy.tseditable/selection-reconciler.tscomponents/editable-text-blocks.tsxcomponents/editable.tsxReactEditor.focus(...) is called from:
editable/model-input-strategy.tseditable/clipboard-input-strategy.tsIS_FOCUSED and state.isUpdatingSelection are owned partly by:
editable/selection-reconciler.tscomponents/editable.tsxDecision:
Next owner:
EditableInputControllerState / related files and route
existing mutable state through a controller object while preserving behavior.Actions:
EditableInputControllerState.Proof commands:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Arrow|Backspace|Delete|visual caret|ArrowDown"
Exit:
Status: complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/input-state.ts.InputIntent, SelectionSource,
EditableInputControllerState, and EditableInputController.EditableDOMRoot mutable event state through
createEditableInputControllerState() and createEditableInputController().Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-state.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Arrow|Backspace|Delete|visual caret|ArrowDown"
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
1 + 15 + 6 passed10 passedDecision:
isInteractiveInternalTarget under the controller owner.Next owner:
Actions:
isInteractiveInternalTarget into controller.internal-control before any DOM selection import.Proof rows:
Proof command:
bunx playwright test ./playwright/integration/examples/editable-voids.test.ts ./playwright/integration/examples/check-lists.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
Exit:
Status: complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.isInteractiveInternalTarget under the controller owner.input-controller.ts..tmp/slate-v2/packages/slate-react/src/editable/target-policy.ts.editable/input-controller.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/input-router.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-reconciler.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/target-policy.tsEvidence:
bunx playwright test ./playwright/integration/examples/editable-voids.test.ts ./playwright/integration/examples/check-lists.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
48 passed1 + 15 + 6 passedDecision:
Next owner:
Actions:
syncEditorSelectionFromDOM.model-owned, do not import DOMinternal-control, do not import DOMProof rows:
Proof command:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "Arrow|word movement|line extension|triple click|marked leaf"
Exit:
Status: complete for the current DOM-to-model import helper.
Actions:
syncEditorSelectionFromDOM(...) from
editable/selection-reconciler.ts to editable/input-controller.ts.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-reconciler.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "Arrow|word movement|line extension|triple click|marked leaf"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
20 passed1 + 15 + 6 passedDecision:
Next owner:
Actions:
classifyKeyboardIntent.classifyBeforeInputIntent.classifyClipboardIntent.classifyCompositionIntent.Keyboard intent examples:
ArrowDown: native-selection-moveArrowRight: model-selection-moveAlt+Shift+ArrowDown: model-selection-movetext-inserthistoryinternal-controlProof:
Exit:
Status: complete for behavior-neutral event family classification.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts:
classifyKeyboardIntentclassifyBeforeInputIntentclassifyClipboardIntentclassifyCompositionIntentEditableDOMRoot to record inputController.state.activeIntent
before existing strategy handlers run.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/paste-html.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "Arrow|Backspace|Delete|visual caret|ArrowDown|paste|cuts|composition|undoes|redoes"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
26 passed1 + 15 + 6 passedDecision:
Next owner:
Actions:
nonesync-selectionrepair-caretforce-renderskip-dom-syncdomRepairQueue.Proof rows:
Exit:
Status: partial.
Actions:
EditableRepairRequest.applyEditableRepairRequest(...).requestRepair(...) instead of direct strategy-level:
preferModelSelectionForInputRef.current = trueforceRender()syncDOMSelectionToEditor()Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Arrow|word movement|line extension|browser-selected|visual caret|Backspace|Delete|ArrowDown"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
13 passed1 + 15 + 6 passedDecision:
Next owner:
Status: partial.
Actions:
applyModelOwnedTextInput(...) to return an
EditableRepairRequest.requestEditableRepair(...).model-input-strategy repair paths.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "browser input|browser-selected|visual caret|decorated middle|directly synced|Backspace|Delete|ArrowDown"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
19 passed1 + 15 + 6 passedDecision:
Next owner:
Status: partial.
Actions:
requestRepair({ kind: 'repair-caret' }).Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.tsEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/highlighted-text.test.ts --project=chromium --grep "selected range|selected text|deleting a decorated selected range|deletes selected range"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
7 passed1 + 15 + 6 passedDecision:
Next owner:
Status: partial.
Actions:
requestRepair({ kind: 'repair-caret' }).deleteByCompositiondeleteByCutdeleteByDragdeleteContentdeleteContentBackwarddeleteContentForwardChanged files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.tsEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "Backspace|Delete|deletes backward|deletes forward|deleting a decorated"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
11 passed1 + 15 + 6 passedDecision:
Next owner:
Status: partial.
Actions:
requestRepair({ kind: 'repair-caret' }).Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.tsEvidence:
bunx playwright test ./playwright/integration/examples/paste-html.test.ts ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "paste|fragment|rich HTML|plain text"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
7 passed1 + 15 + 6 passedDecision:
Next owner:
Status: partial.
Actions:
applyEditableInput(...) deferred operation repair through
controller-owned requestRepair(...).requestRepair(...).Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "browser input|undoes inserted text|repairs DOM after Mac keyboard undo|directly synced|ArrowDown|visual caret"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
11 passed1 + 15 + 6 passedDecision:
Next owner:
Status: complete.
Actions:
requestEditableRepair({ kind: 'force-render' }).requestRepair({ kind: 'force-render' }).Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsxEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "undo|redo|history|repairs DOM after Mac keyboard undo"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
5 passed1 + 15 + 6 passedDecision:
Next owner:
Status: complete.
Controller-owned repair categories:
Remaining repair leaks are clipboard-specific and composition-specific, which belong to Phase 6.
Actions:
Proof rows:
Exit:
Status: complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.insertFromComposition commit through that writer.focus ownership.preferModelSelectionForInputRef.currentReactEditor.focus(...)domRepairQueue.repairCaretAfterModelOperation()Changed files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/clipboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/composition-state.ts.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.tsEvidence:
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bunx playwright test ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "IME|composition"
bunx playwright test ./playwright/integration/examples/highlighted-text.test.ts --project=chromium --grep "copy|cut"
bunx playwright test ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/paste-html.test.ts ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "copy|cut|paste|clipboard|rich HTML"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
15 passed15 passed1 passed8 passed1 + 15 + 6 passedRejected tactic:
Another next build process is already running). This was
harness-owned, not product-owned. The rows were rerun in one Playwright
process and passed.Decision:
EditableDOMRoot selectionchange logic, which belong to Phase 7.Next owner:
preferModelSelectionForInputRef.current writes behind
controller/selection ownership where safeTransforms.select(...) and ReactEditor.focus(...)
calls by ownerActions:
preferModelSelectionForInputRef writes outside controllerstate.isUpdatingSelection writes outside controller unless in
reconciler internalsTransforms.select in strategies where controller should own final
selectionProof:
rg -n "preferModelSelectionForInputRef\\.current|Transforms\\.select|ReactEditor\\.focus" packages/slate-react/src/editable packages/slate-react/src/components/editable.tsx
Exit:
Status: complete for the current controller rewrite slice.
Actions:
setEditableModelSelectionPreference(...) in
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.preferModelSelectionForInputRef.current = ... writes outside
the controller with the controller helper.state.isUpdatingSelection writes inside selection reconciler internals;
those writes are DOM export guards and are not dead timing drift.Remaining direct selection/focus calls and owner classification:
components/editable.tsx
selectionchange import reads preferModelSelectionForInputRef.current
and calls Transforms.select(...).EditableSelectionController
replaces the inline selectionchange block.editable/input-controller.ts
syncEditorSelectionFromDOM(...) reads the guard and imports DOM selection.applyEditableRepairRequest(...) owns ReactEditor.focus(...) and DOM
repair calls.editable/selection-reconciler.ts
Transforms.select(...) or update
state.isUpdatingSelection.editable/dom-repair-queue.ts
editable/browser-handle.ts
editable/keyboard-input-strategy.ts
EditableCaretEngine should own
keyboard movement, but select-all is not current timing drift.editable/model-input-strategy.ts
EditableMutationController takes this
path fully.editable/clipboard-input-strategy.ts
Transforms.select(...).EditableMutationController.Evidence:
rg -n "preferModelSelectionForInputRef\\.current|ReactEditor\\.focus|domRepairQueue\\.repairCaretAfterModelOperation|IS_COMPOSING" packages/slate-react/src/editable packages/slate-react/src/components/editable.tsx
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts ./playwright/integration/examples/check-lists.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "Arrow|triple|void|nested|internal|restores outer|click|selectionchange"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
48 passed1 + 15 + 6 passedDecision:
Next owner:
Add these as active rows:
richtext navigation gauntlet:
richtext mutation gauntlet:
highlighted-text marked-leaf gauntlet:
paste-html clipboard gauntlet:
editable-voids embedded target gauntlet:
Each gauntlet asserts:
Status: complete for the richtext/controller tracer.
Actions:
getDOMSelectionLocation(...) to
.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.keeps navigation and mutation chained through browser editing state.RED findings:
ArrowUp, DOM selection was in block 0 while Slate selection
stayed in block 1. The following model-owned ArrowRight snapped the DOM
back to the stale model selection.Delete used stale collapsed model
selection in the chained row.native-selection-move broke editable-void internal input/nested editor
typing, producing reversed inserted text.selectWithHandle, which
sets model and DOM selection together. The native-navigation chain remains
DOM-owned.Fixes:
classifyKeyboardIntent(...) now classifies plain character keydown as
text-insert, not native-selection-move.beforeinput path so internal controls and nested editors keep
their model-owned selection protection.Changed files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "navigation and mutation chained" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts ./playwright/integration/examples/check-lists.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "navigation and mutation chained|Arrow|word movement|line extension|triple|void|nested|internal|restores outer|click|selectionchange|Backspace|Delete|visual caret|paste|undoes|redoes"
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
bun run bench:react:rerender-breadth:local
REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:local
Results:
1 passed9 passed across Chromium, Firefox, and WebKit100 passed1 + 15 + 6 passed00012.61ms vs 267.99ms / 293.47ms0.11ms vs 15.01ms / 0.89ms5.77ms vs 171.12ms / 49.10ms5.20ms vs 170.58ms / 42.66ms2.59ms vs 157.83ms / 33.19ms0.54ms vs 185.47ms / 35.32ms21.01ms vs 183.26ms / 54.27ms23.38ms vs 114.50ms / 112.32ms22.79ms vs 106.58ms / 117.65msDecision:
bun test:integration-local.Next owner:
Required gates:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
bun test:integration-local
bun run bench:react:rerender-breadth:local
REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:local
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Completion criteria:
Status: complete for the controller/gauntlet closure lane.
Evidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
bun test:integration-local
Results:
84 passed364 passedDecision:
editing-navigation-gauntlet-review lane is closed.EditableSelectionControllerEditableCaretEngineEditableMutationControllerNext owner:
EditableSelectionControllerEditableMutationControllerEditableCaretEngine behind current keyboard behaviorGoal:
First slice:
input-controller.ts.input-controller.ts for compatibility.Status: first slice complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.ts.syncEditorSelectionFromDOM(...)setEditableModelSelectionPreference(...)EditableSelectionController type anchor.input-controller.ts so existing callers do not move
yet.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
9 passedDecision:
selectionchange block in
EditableDOMRoot; extract it only as a behavior-preserving function first.Next owner:
components/editable.tsx into selection-controller.ts behind a function
with explicit inputs.Status: complete.
Actions:
applyEditableDOMSelectionChange(...) to
.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.ts.EditableDOMRoot native selectionchange body into that
function.rerunOnDirtyNodeMap.EditableDOMRoot imports after extraction.Changed files:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
9 passedDecision:
selection-reconciler.ts; the next safe
selection owner is to extract the explicit DOM sync/export callback without
changing behavior.Next owner:
syncDOMSelectionToEditor / explicit DOM export helper from
selection-reconciler.ts into selection-controller.ts, then run the same
browser floor.Status: complete.
Actions:
syncEditableDOMSelectionToEditor(...) to
.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.ts.useEditableSelectionReconciler(...).syncDOMSelectionToEditor through
that helper.selection-reconciler.ts; that path remains coupled to the layout effect and
is not part of this no-behavior extraction.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-reconciler.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
9 passedDecision:
selection-controller.ts now owns the explicit DOM import/export helpers.Next owner:
EditableMutationController foundation.
Status: first slice complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/mutation-controller.ts.EditableRepairRequestapplyEditableRepairRequest(...)input-controller.ts so existing callers do not
move yet.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/mutation-controller.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
9 passedDecision:
mutation-controller.ts without changing behavior.Next owner:
mutation-controller.ts or make model-input-strategy.ts consume the
mutation controller directly, whichever creates less churn.Status: complete.
Actions:
model-input-strategy.ts to
mutation-controller.ts:
applyModelOwnedHistoryIntent(...)applyModelOwnedNativeHistoryEvent(...)applyModelOwnedDeleteIntent(...)applyModelOwnedExpandedDelete(...)applyModelOwnedLineBreak(...)applyModelOwnedDataTransferInput(...)applyModelOwnedTextInput(...)model-input-strategy.ts so existing
keyboard imports remain stable.input-controller.ts to keep the public
internal import shape stable during the rewrite.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/input-controller.ts.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/mutation-controller.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium --project=firefox --project=webkit --grep "navigation and mutation chained|restores outer|keeps nested editor input focused" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
9 passedDecision:
keyboard-input-strategy.ts still computes movement directly.Next owner:
EditableCaretEngine foundation.
Transforms.move/collapse behavior firstStatus: first slice complete.
Actions:
.tmp/slate-v2/packages/slate-react/src/editable/caret-engine.ts.keyboard-input-strategy.ts into applyEditableCaretMovement(...):
Transforms.move/collapse behavior and repair request
shape.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/caret-engine.ts.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.tsEvidence:
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "navigation and mutation chained|Arrow|word movement|line extension|read-only inline" --workers=4 --retries=0
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
24 passedDecision:
Next owner:
Status: complete.
Decision:
keyboard-input-strategy.ts.Why:
EditableCaretEngine should own cursor movement:
setExplicitShellBackedSelection(Boolean(largeDocument)).Evidence read:
rg -n "selectAll|select-all|ControlOrMeta\\+A|Meta\\+A|Ctrl\\+A|select all|isSelectAllHotkey" packages/slate-react/src playwright/integration/examples packages/slate-react/test scripts/benchmarks/browser/react -g "*.ts" -g "*.tsx" -g "*.mjs"
sed -n '88,120p' packages/slate-react/src/editable/keyboard-input-strategy.ts
sed -n '1,80p' packages/slate-react/src/large-document/large-document-commands.ts
No code movement:
packages/slate-react/test/large-doc-and-scroll.tsxlarge-document-runtime shell-backed paste rowshuge-document-legacy-compare selectAllMsNext owner:
slate-browser/playwright helper surface and
the richtext gauntlet's duplicated assertionsStatus: first slice complete.
Actions:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts:
SlateBrowserTraceEntrySlateBrowserScenarioStepSlateBrowserScenarioResulteditor.trace.snapshot(label, stepIndex?).editor.scenario.run(name, steps).focusselectpresstypeinsertTextassertTextassertSelectionassertDOMSelectionsnapshotEditorSnapshot after every step.Changed files:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.tsEvidence:
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "traced slate-browser scenario" --workers=1 --retries=0
bun --filter slate-browser test
bun run test:slate-browser
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
1 passedtest:slate-browser: passedDecision:
Next owner:
editor.scenario.run(...) for the richtext navigation +
mutation gauntlet, while keeping the existing assertions and browser coverage.
Then add artifact writing only after the scenario-shaped gauntlet is stable.Status: complete.
Actions:
custom scenario steps so high-fidelity Playwright assertions can be
preserved while scenario infrastructure records traces.editor.scenario.run(...).native-navigation-chaininsert-and-backspaceselected-delete-type-undoeditor.scenario.run(name, steps, { tracePath })richtext-navigation-mutation-gauntlet.jsonChanged files:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.tsEvidence:
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "navigation and mutation chained" --workers=1 --retries=0
find test-results -name 'richtext-navigation-mutation-gauntlet.json' -print -maxdepth 4
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "navigation and mutation chained|Arrow|word movement|line extension|read-only inline" --workers=4 --retries=0
bun --filter slate-browser test
bun run test:slate-browser
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
1 passedtest-results/**/richtext-navigation-mutation-gauntlet.json24 passedtest:slate-browser: passedDecision:
Next owner:
slate-browserStatus: complete.
Actions:
SlateBrowserScenarioReductionCandidate.createScenarioReductionCandidates(...)..tmp/slate-v2/packages/slate-browser/test/core/scenario.test.ts.Changed files:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/packages/slate-browser/test/core/scenario.test.tsEvidence:
bun --filter slate-browser test
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-browser --force
bunx turbo typecheck --filter=./packages/slate-browser --force
Results:
11 passDecision:
Next owner:
Status: complete.
Actions:
SlateBrowserScenarioMetadataSlateBrowserNormalizedScenarioMetadatanormalizeScenarioMetadata(...).editor.scenario.run(name, steps, { metadata }).insertText at a stable point, while desktop keeps native keyboard + DOM
selection proof.Changed files:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/packages/slate-browser/test/core/scenario.test.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/playwright/integration/examples/markdown-shortcuts.test.tsEvidence:
bun --filter slate-browser test
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "navigation and mutation chained" --workers=1 --retries=0
find test-results -name 'richtext-navigation-mutation-gauntlet.json' -print -maxdepth 4
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=mobile --grep "traced slate-browser scenario" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts --project=chromium --grep "can add list" --workers=1 --retries=0
bun test:integration-local
bun run lint:fix
bun run lint
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
Results:
12 pass1 passed1 passed368 passedFinal decision:
EditableInputController: intent/event classificationEditableSelectionController: explicit DOM import/export helpersEditableMutationController: repair and model-owned mutation dispatchEditableCaretEngine: model-owned caret movementpackages/slate only with
focused headless proof.