docs/plans/2026-04-25-slate-v2-editing-epoch-kernel-regression-closure-plan.md
Keep the Slate v2 north star:
Slate model + operations
Lexical-style editor.read / editor.update lifecycle
ProseMirror-style transaction and DOM-selection authority
Tiptap-style extension ergonomics
React 19.2 live-read / dirty-commit runtime
slate-browser generated proof gates
Do not pivot to Lexical, ProseMirror, Tiptap, or legacy Slate React.
Pivot harder inside the current architecture: replace the current event-frame coordination with a true editing epoch kernel.
Harsh take: the current files have the right names but not enough authority.
EditableConformanceKernel, SelectionReconciler, MutationController,
DOMRepairQueue, and input strategies still let one native user action leak
across multiple independent handlers. The screenshot failure is exactly that:
repeated native word-delete creates a model/DOM/selection timing split that
single-row tests do not catch.
Observed browser path:
http://localhost:3100/examples/richtext.Option+Backspace / word-delete.This is not a toolbar-only bug and not a one-off rendering glitch.
The failing class is:
native key transport
-> destructive structural/text mutation
-> beforeinput/input/selectionchange race
-> model-owned operation
-> incomplete selection-source transition
-> stale DOM selection import or incomplete repair
-> visible DOM/model/caret drift
Current code has an event frame, but not a full native-action epoch.
Known weak points:
selectionchange is throttled and can import after a model-owned mutation.keydown can classify and import selection before beforeinput mutates.beforeinput can apply model-owned delete and schedule only generic
repair-caret.selectionSource: model-owned before repair.input repair is separate from the destructive-operation owner.The fix is not another local Backspace patch. The fix is to make one kernel own:
native action epoch
-> event sequence
-> intent
-> command
-> target and selection source
-> transaction
-> commit
-> DOM repair
-> stale event quarantine
-> trace
Steal:
editor.update as the write lifecycle.preventDefault.Reject:
$function public style.Steal:
EditorView authority for DOM observation and selection import/export.Reject:
Steal:
Reject:
Introduce EditableEditingEpochKernel.
It replaces "one trace per handler" with "one authoritative epoch per native editing action."
Every epoch captures:
keydown, beforeinput, input,
selectionchange, composition*, paste, drop, repairkeydown can start an epoch.beforeinput joins the active epoch when it belongs to the same native
action.input joins the active epoch when it is the browser's follow-up for the
same native action.selectionchange joins or is quarantined by the active epoch.repair closes the epoch only after DOM selection and visible DOM are
coherent.Model-owned, always:
deleteContentBackwarddeleteContentForwarddeleteWordBackwarddeleteWordForwarddeleteSoftLine*deleteHardLine*deleteByCutdeleteByDraginsertParagraphinsertLineBreakNative-owned, only as a capability:
insertTextSemantic/proof-only:
Never trusted:
selectionchange after model-owned repairGoal: make the screenshot failure reproducible before fixing architecture.
Work:
slate-browser scenario for repeated word-delete:
Option+Backspace repeatedly on macOS Chromiumhttp://localhost:3100 if available.Acceptance:
Commands:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "word-delete|epoch|persistent|soak" --workers=1 --retries=0
SLATE_BROWSER_SOAK_BASE_URL=http://localhost:3100 SLATE_BROWSER_SOAK_ITERATIONS=5 bun ./scripts/proof/persistent-browser-soak.mjs
Goal: make one owner responsible for the whole native editing action.
Work:
editable/editing-epoch-kernel.ts.keydownbeforeinputinputselectionchangerepaircomposition*pastedropAcceptance:
epochId.selectionchange during a model-owned epoch is quarantined or consumed by
that epoch, never imported independently.repair cannot close while DOM selection is outside the editor or points at
stale DOM.Focused tests:
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts --bail 1
bun test ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
Goal: browser-native delete is no longer trusted as structural truth.
Work:
preventDefault:
selectionSource: model-ownedpreferModelSelection: trueAcceptance:
deleteWordBackward and deleteWordForward produce model-owned epoch traces.Focused tests:
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts --bail 1
bun test ./packages/slate-react/test/target-runtime-contract.tsx --bail 1
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "word-delete|Backspace|Delete|epoch|persistent" --workers=1 --retries=0
Goal: stale native selection cannot overwrite model-owned repair.
Work:
selectionchange authority with epoch-aware import.DOMRepairQueue return a result object:
Acceptance:
Focused tests:
bun test ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "selectionchange|repair|word-delete|persistent" --workers=1 --retries=0
Goal: native DOM-owned text remains a performance capability, not a hidden editing authority.
Work:
insertText only when capability
checks pass.Acceptance:
Focused tests:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
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
Goal: stop relying on user bug reports as the test generator.
Work:
slate-browser scenario generation with destructive editing families:
Acceptance:
test-results/release-proof.check:full fails if the destructive editing gauntlet fails.Commands:
bun run --cwd packages/slate-browser test:core --bail 1
bun --filter slate-browser test:proof
SLATE_BROWSER_SOAK_ITERATIONS=5 bun test:persistent-soak
bun test:release-proof
Goal: recover proven browser timing discipline from ../slate, not old API
shape.
Work:
Editable.Transforms.Acceptance:
Commands:
rg -n "deleteWordBackward|selectionchange|beforeinput|restore|composition|Chrome|Safari|Firefox" ../slate/packages/slate-react/src
rg -n "deleteWordBackward|selectionchange|beforeinput|restore|composition|Chrome|Safari|Firefox" .tmp/slate-v2/packages/slate-react/src
Goal: make release claims honest across browser engines and devices.
Work:
Acceptance:
test:mobile-device-proof:raw is the device-lab gate.Commands:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "word-delete|Backspace|Delete|epoch|persistent|generated" --workers=4 --retries=0
bun test:mobile-device-proof
bun test:mobile-device-proof:raw
Goal: fix correctness without sacrificing the React-perfect runtime.
Rules:
Measure:
Commands:
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
bun run bench:core:normalization:compare: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
Acceptance:
Goal: docs, checks, and release language match reality.
Work:
active goal state while execution is active..agents/AGENTS.md if a new release gate becomes permanent.check fast.check:full release-grade.Acceptance:
Transforms.*editor.updateDo not start with a broad refactor.
Use tracer bullets:
Option+Backspace.selectionchange import during model-owned repair.EditorCommit.tx.resolveTarget.Transforms.This plan is complete when:
bun test:integration-local passesbun test:release-proof passesactive goal state is status: done or status: blockedbun completion-check passesCreate the RED row:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent word-delete" --workers=1 --retries=0
The row should fail on current behavior before kernel changes. If it does not fail, use the in-app browser path and persistent-profile soak to capture the real transport difference before changing implementation.
Actions:
Alt+Backspace
word-delete at the end of the first paragraph.http://localhost:3100 so Playwright
exercised the edited package source instead of the stale running bundle.selectionSource: model-owned and preferModelSelection: true.selectionchange handling to canonicalize to
model-owned before recording/importing and to clear the repair origin after
the repair event is processed.Evidence:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete" --workers=1 --retries=0
# RED before runtime fix: repair-induced selectionchange entries reported selectionSource dom-current.
# GREEN after runtime fix: 1 passed.
bun test ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 26 pass, 0 fail.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance" --workers=2 --retries=0
# 2 passed.
bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 2 successful.
bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 7 successful.
bun run lint:fix
# No fixes applied.
bun run lint
# Checked 1587 files. No fixes applied.
Failed or scoped probes:
dev-browser --connect http://127.0.0.1:9222
# Failed: no CDP endpoint.
dev-browser --connect
# Failed: no auto-discoverable debug Chrome.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Backspace|Delete|Arrow|word|navigation|caret editable|persistent native word-delete" --workers=4 --retries=0
# 14 passed. Existing/unresolved unrelated failures:
# - command metadata rows report lastCommit.command null for native text input/movement
# - clipboard paste row did not dispatch native paste in this local browser run
Hypothesis:
selectionchange can still be traced/imported as dom-current.Decision:
Changed files:
/Users/zbeyens/git/slate-v2/playwright/integration/examples/richtext.test.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/selection-controller.ts/Users/zbeyens/git/slate-v2/packages/slate-react/test/selection-controller-contract.tsRejected tactics:
selectionchange whenever model selection is
preferred. It fixed the RED row but broke mixed toolbar selection and paste.Checkpoint:
epochId to traces and make destructive
keydown/beforeinput/repair share one epoch instead of relying on event-frame
source repair.Actions:
selectionchange rule in
docs/solutions/ui-bugs/2026-04-25-slate-react-repair-induced-selectionchange-must-stay-model-owned.md.Decision:
Next action:
epochId spine unless command metadata or clipboard is
promoted to the next P0.Actions:
active goal state from status: blocked back to
status: pending.blocked is not used for verified partial slices.Decision:
blocked means no autonomous progress is possible. This lane has a runnable
next move, so the honest state is pending.Next action:
epochId spine.Actions:
active goal state and confirmed the active lane is pending.active goal state as the Stop-hook continuation prompt.Decision:
epochId spine; command
metadata and clipboard failures remain open but are not the first owner unless
a focused probe promotes them.Next action:
epochId trace ownership so destructive keydown, beforeinput, repair,
input, and selectionchange belong to one native-action epoch.Actions:
EditableEditingEpochKernel as a focused epoch primitive above the
existing event-frame trace machinery.epochId to kernel trace entries.keydown.beforeinput, model-owned repair, and repair-induced
selectionchange to the active destructive epoch.selectionchange.Commands:
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts --bail 1
# 4 pass, 0 fail.
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 30 pass, 0 fail.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete" --workers=1 --retries=0
# RED after first assertion: repair-induced selectionchange from follow-up typing had null epoch; narrowed assertion to destructive repair-induced selectionchanges.
# GREEN after assertion correction: 1 passed.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance" --workers=2 --retries=0
# 2 passed.
bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 2 successful.
bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 7 successful.
bun run lint:fix
# Checked 1589 files. Fixed 4 files.
bun run lint
# Checked 1589 files. No fixes applied.
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 30 pass, 0 fail after lint:fix.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance" --workers=2 --retries=0
# 2 passed after lint:fix.
bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force && bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# build: 2 successful; typecheck: 7 successful.
Artifacts:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/editing-epoch-kernel.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/editing-kernel.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/test/editing-epoch-kernel-contract.ts/Users/zbeyens/git/slate-v2/playwright/integration/examples/richtext.test.tsEvidence:
epochId.Hypothesis:
input, mutation metadata, and broader destructive families can
still be represented as event frames rather than native-action epochs.Decision:
Owner classification:
Rejected tactics:
frameId as an epoch. It remains per-handler evidence, not the
native-action owner.Checkpoint:
input event trace ownership is still not explicit; command metadata
and native paste rows remain unresolved; generated destructive gauntlets are
still too narrow.input participation and adding a
contract that a destructive epoch cannot leak into unrelated later input.input ownership and destructive-family
coverage are explicit.Actions:
input kernel traces from onDOMInput.input participation to model-owned input so a
later native text input cannot inherit a stale destructive epoch.Commands:
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 32 pass, 0 fail.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance" --workers=2 --retries=0
# 2 passed.
bun run lint:fix
# Checked 1589 files. No fixes applied.
bun run lint
# Checked 1589 files. No fixes applied.
bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 2 successful.
bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 7 successful.
Evidence:
input now appears in the trace stream with ownership and selection-source
state.Decision:
Checkpoint:
Actions:
keydown handling:
Backspace, Delete, word delete, line delete, and range delete now
preventDefault, run the editor command, and request model-owned caret
repair from keydown.beforeinput destructive handling as fallback for paths that do not
have a keydown predecessor.Commands:
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 33 pass, 0 fail.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|keeps caret editable after browser Backspace|keeps caret editable after browser Delete" --workers=2 --retries=0
# 5 passed after fixing the stale beforeinput-only assertion.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance" --workers=2 --retries=0
# 2 passed.
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1 && bun run lint:fix && bun run lint && bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force && bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# First run: typecheck failed because `isDestructiveEditableCommand` was not a type guard.
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1 && bun run lint:fix && bun run lint && bunx turbo build --filter=./packages/slate-react --filter=./packages/slate-dom --force && bunx turbo typecheck --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 33 pass, lint clean, build 2 successful, typecheck 7 successful.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance|keeps caret editable after browser Backspace|keeps caret editable after browser Delete" --workers=2 --retries=0
# 6 passed.
Scoped failure:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|Backspace|Delete" --workers=2 --retries=0
# 5 destructive rows passed.
# Existing command metadata row still failed: lastCommit.command null for text input.
Evidence:
Decision:
Checkpoint:
Actions:
editor.update() transaction.Commands:
bun test ./packages/slate/test/transaction-contract.ts --test-name-pattern "preserves command metadata when a command runs inside an open update"
# 1 pass, 23 filtered out.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "records core command metadata for text input and delete" --workers=1 --retries=0
# 1 passed.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "persistent native word-delete|runs generated mixed editing conformance|keeps caret editable after browser Backspace|keeps caret editable after browser Delete|records core command metadata for text input and delete" --workers=2 --retries=0
# 7 passed.
bun test ./packages/slate/test/transaction-contract.ts --test-name-pattern "routes insertText through command middleware|preserves command metadata when a command runs inside an open update" && bun test ./packages/slate/test/read-update-contract.ts ./packages/slate/test/write-boundary-contract.ts --bail 1 && bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts --bail 1
# 2 pass / 22 filtered out; 8 pass; 33 pass.
bun run lint:fix
# Checked 1589 files. No fixes applied.
bun run lint
# Checked 1589 files. No fixes applied.
bunx turbo build --filter=./packages/slate --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 3 successful.
bunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-react --filter=./packages/slate-dom --force
# 8 successful.
Evidence:
lastCommit.command now reports insert_text after native text
input and delete after native Backspace in the richtext row.editor.update(), which is the public runtime shape.Decision:
Checkpoint:
withTransaction captured command context only at transaction start;
commands invoked inside an already-open update could not stamp the commit.transaction-contract.ts file still has stale write-boundary
tests outside the focused rows; clipboard/native paste remains open from the
earlier local run; destructive rows still need cross-browser proof.Actions:
localhost:3100 dev server.editor.update, triggering the write-boundary guard and the
Next.js runtime overlay.insert-data command execution and model-owned DataTransfer input so
clipboard insertion enters the public editor.update write boundary.Commands:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "plain text paste" --workers=1 --retries=0
# First attempt failed because localhost:3100 was not running.
bun serve
# Next dev server ready on http://localhost:3100.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "plain text paste" --workers=1 --retries=0
# RED: native paste and fallback left text unchanged; page showed
# "editor writes must run inside editor.update".
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "plain text paste" --workers=1 --retries=0
# 1 passed after wrapping DataTransfer insertion in editor.update.
bun test ./packages/slate-react/test/editing-epoch-kernel-contract.ts ./packages/slate-react/test/editing-kernel-contract.ts ./packages/slate-react/test/selection-controller-contract.ts ./packages/slate-react/test/dom-repair-policy-contract.ts ./packages/slate-react/test/target-runtime-contract.tsx ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
# 36 pass.
bun run --cwd packages/slate-browser test:core --bail 1
# 27 pass.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "plain text paste|persistent native word-delete|runs generated mixed editing conformance|keeps caret editable after browser Backspace|keeps caret editable after browser Delete|records core command metadata for text input and delete" --workers=2 --retries=0
# 8 passed.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "plain text paste|persistent native word-delete|keeps caret editable after browser Backspace|keeps caret editable after browser Delete|records core command metadata for text input and delete" --workers=4 --retries=0
# 28 passed.
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
# 3 successful.
bunx turbo typecheck --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
# 9 successful.
bun run lint:fix
# Checked 1589 files. Fixed 1 file.
bun run lint
# Checked 1589 files. No fixes applied.
Evidence:
editor.update write
boundary.Decision:
Checkpoint:
Actions:
createSlateBrowserDestructiveEditingGauntlet helper that
covers:
pasteText and assertBlockTexts scenario steps to the slate-browser
Playwright harness.Commands:
bun run --cwd packages/slate-browser test:core --bail 1
# First run failed because the new test incorrectly read `candidate.replay`
# from raw reduction candidates instead of creating a replay summary.
bun run --cwd packages/slate-browser test:core --bail 1
# 28 pass.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "generated destructive paste" --workers=1 --retries=0
# First run failed before execution because the built `slate-browser/playwright`
# export did not include the new helper yet.
bunx turbo build --filter=./packages/slate-browser --force
# 1 successful.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "generated destructive paste" --workers=1 --retries=0
# 1 passed.
SLATE_BROWSER_SOAK_BASE_URL=http://localhost:3100 SLATE_BROWSER_SOAK_ITERATIONS=5 bun ./scripts/proof/persistent-browser-soak.mjs
# Passed 5 persistent-profile iterations and emitted
# test-results/release-proof/persistent-browser-soak.json.
bun --filter slate-browser test:proof
# 18 pass.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "generated destructive paste|generated mixed editing conformance|persistent native word-delete" --workers=2 --retries=0
# 3 passed.
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated destructive paste|generated mixed editing conformance|persistent native word-delete" --workers=4 --retries=0
# 12 passed.
bun test:release-proof
# First run failed because the escape-hatch inventory counts were stale after
# adding new generated proof bridge rows.
bun test:release-proof
# 129 release-discipline tests pass, 18 slate-browser proof tests pass, mobile
# scoped proof passes with raw-device claims still explicitly out of scope.
bunx turbo build --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
# 3 successful.
bunx turbo typecheck --filter=./packages/slate-browser --filter=./packages/slate-dom --filter=./packages/slate-react --force
# 9 successful.
bun run lint:fix
# Checked 1589 files. No fixes applied.
bun run lint
# Checked 1589 files. No fixes applied.
Evidence:
richtext-persistent-browser-destructive-editing-soak and
richtext-persistent-browser-warm-toolbar-soak.bun test:release-proof is green with explicit raw-mobile scoping.Decision:
Checkpoint:
../slate and the
current runtime comments/ordering, then update docs/release claims before the
final integration-local closure sweep.check
loop.Actions:
beforeinputdocs/research/decisions/slate-v2-editing-epoch-legacy-timing-recovery-audit.md.Commands:
rg -n "beforeinput|selectionchange|restore|composition|delete|Backspace|clipboard|paste|setSelection|isUpdatingSelection|toSlateRange|Android|Safari|Firefox|COMPAT" ../slate/packages/slate-react/src/components/editable.tsx ../slate/packages/slate-react/src/plugin/with-react.ts ../slate/packages/slate-dom/src/plugin/dom-editor.ts
rg -n "beforeinput|selectionchange|restore|composition|delete|Backspace|clipboard|paste|setSelection|isUpdatingSelection|toSlateRange|Android|Safari|Firefox|COMPAT" /Users/zbeyens/git/slate-v2/packages/slate-react/src /Users/zbeyens/git/slate-v2/packages/slate-dom/src -g "*.ts" -g "*.tsx"
Evidence:
Editable.Decision:
Checkpoint:
active goal state, then
run the final closure gates.Actions:
EditableConformanceKernel.bun test:integration-local
remains the closeout gate.docs/slate-v2 front-door
read order.active goal state so Batch 5 no longer contradicts itself and
the current owner is final verification closure.Commands:
rg -n "regression-free|Chromium-only|proved in Chromium|not yet release-blocking|semantic mobile.*raw|raw.*semantic|integration-local.*check|test:integration-local.*check|persistent-browser.*not|destructive.*not" docs/slate-v2 docs/research docs/plans/2026-04-24-slate-v2-absolute-architecture-closure-plan.md docs/plans/2026-04-25-slate-v2-editing-epoch-kernel-regression-closure-plan.md active goal state
Evidence:
active goal state Batch 5 wording is fixed.Decision:
Checkpoint:
bun test:integration-local can still fail; raw Android/iOS
device artifacts are still outside this environment.bun test:integration-local, bun test:release-proof,
persistent soak, build, typecheck, lint.Actions:
bun check:full closure sweep after the public claim/gate sync.check, release-proof, and persistent soak
before failing in bun test:integration-local.ReactEditor.findPath during
render or control events.Commands:
bun check:full
sed -n '1,260p' site/examples/ts/check-lists.tsx
sed -n '1,260p' site/examples/js/check-lists.jsx
sed -n '1,260p' site/examples/ts/images.tsx
rg -n "ReactEditor\\.findPath|findPath\\(editor" site/examples packages/slate-react/test playwright -g "*.tsx" -g "*.ts" -g "*.jsx"
find test-results -path '*error-context.md' -print
Evidence:
bun check:full failed only at bun test:integration-local.check-lists.test.ts checkbox rows across all projects.images.test.ts image rows across all projects.inlines.test.ts read-only inline arrow trace row across all projects.persistent-annotation-anchors.test.ts annotation sidebar path assertion
across all projects.paste-html.test.ts mobile rich paste row./Users/zbeyens/git/slate-v2/test-results/release-proof/persistent-browser-soak.json.Decision:
Checkpoint:
findPath as a
blessed app pattern.bun check:full.ReactEditor.findPath control paths with
renderElement path props, then run focused checklist/images rows.Actions:
ReactEditor.findPath(editor, element) usage with
the renderElement path prop in checklist, image, code-highlighting, and
embed examples.editor.move(...) commands in
editor.update(...), matching the write-boundary hard cut.window.getSelection().anchorNode.textContent.alpha substring inside a merged leaf.insertFragment, preventing mobile rich paste from creating top-level text
nodes and invalid root selections.Commands:
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/check-lists.test.ts --project=chromium --workers=1 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/images.test.ts --project=chromium --workers=1 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/inlines.test.ts --project=chromium --grep "arrow keys skip" --workers=1 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/persistent-annotation-anchors.test.ts --project=chromium --workers=1 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/paste-html.test.ts --project=mobile --grep "rich HTML paste" --workers=1 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "arrow keys skip" --workers=4 --retries=0
PLAYWRIGHT_BASE_URL=http://localhost:3100 bunx playwright test ./playwright/integration/examples/check-lists.test.ts ./playwright/integration/examples/images.test.ts ./playwright/integration/examples/inlines.test.ts ./playwright/integration/examples/persistent-annotation-anchors.test.ts ./playwright/integration/examples/paste-html.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
Evidence:
rg -n "ReactEditor\\.findPath|findPath\\(editor" site/examples packages/slate-react/test playwright -g "*.tsx" -g "*.ts" -g "*.jsx"
returns no matches.Decision:
Checkpoint:
editor.update, assert semantic selection/annotation behavior, and prevent
invalid top-level text fragments.PLAYWRIGHT_BASE_URL=http://localhost:3100 proof uses the
dev server; final production-like bun check:full still has to pass.bun run lint:fix, bun typecheck:root, final
bun check:full, remaining perf guardrails.bun check:full.ReactEditor.findPath in user examples.Actions:
Commands:
bun check:full
bun test:integration-local
bun test:release-proof
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare: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
Evidence:
bun check:full passed.bun test:integration-local: 536 passed.bun check:full passed:
biome check .: 1589 files checked.bun test:release-proof passed:
bun check:full production build and persistent-profile soak passed:
/Users/zbeyens/git/slate-v2/test-results/release-proof/persistent-browser-soak.json.bun run bench:core:observation:compare:local exited 0.bun run bench:core:huge-document:compare:local exited 0.bun run bench:react:rerender-breadth:local exited 0; edited leaf renders
stayed scoped and sibling/deep ancestor renders stayed at zero.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
exited 0; v2 remains substantially faster for ready / full-document replace
/ full-document fragment insert, with typing and select workloads recorded
as remaining perf debt.Accepted scope:
slate-browser proof guard prevents semantic/mobile-viewport rows from
satisfying raw-device claims.Decision:
Checkpoint:
bun check:full for release closure, focused Playwright
greps for editor-kernel iteration, and the perf guardrails above for runtime
follow-up.Transforms.* as primary DX, or userland
ReactEditor.findPath patterns.