docs/plans/2026-04-22-slate-v2-backspace-caret-testing-plan.md
Close the missing browser-editing coverage that let Backspace remove the visible cursor from the editor and leave the user unable to continue typing.
The plan is TDD-first:
No horizontal mega-suite. One user behavior at a time.
The current test suite is still too optimistic about deletion.
It proves many model and direct-sync paths, but the important user claim is:
If a row does not prove follow-up typing after deletion, it does not close this bug class.
Already strong:
richtext.test.tsMissing or weak:
richtext after browser-selected caretFor every Backspace/Delete user-path row, assert all four layers:
Then type a follow-up character and assert it lands at the same logical caret.
Required post-delete assertions:
await editor.assert.text(expectedText);
await editor.assert.selection(expectedSelection);
await editor.assert.domSelection(expectedDOMSelection);
await editor.type("Z");
await editor.assert.text(expectedTextAfterFollowUpTyping);
await editor.assert.selection(expectedSelectionAfterTyping);
For rows not yet migrated to slate-browser, equivalent local helpers in
richtext.test.ts must still assert model text, DOM text, model selection, DOM
selection, and follow-up typing.
Primary browser rows:
.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/playwright/integration/examples/highlighted-text.test.ts.tmp/slate-v2/playwright/integration/examples/large-document-runtime.test.ts.tmp/slate-v2/playwright/integration/examples/shadow-dom.test.ts.tmp/slate-v2/playwright/integration/examples/editable-voids.test.tsHelper owner:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.tsProduct owners if rows fail:
.tmp/slate-v2/packages/slate-react/src/editable/keyboard-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/packages/slate-react/src/editable/selection-reconciler.ts.tmp/slate-v2/packages/slate-react/src/editable/dom-repair-queue.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx only if the
coordinator or wrapper wiring is the measured ownerAdd one Chromium row to richtext.test.ts:
Name:
test('keeps caret editable after browser Backspace at selected text end', ...)
Setup:
/examples/richtextBackspaceAssertions:
nullZ lands at that caretExpected first failure:
nullIf the row passes immediately:
Earliest command:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Backspace at selected text end"
Do not guess.
Classify the failure:
keyboard-input-strategy if native Backspace handling prevents browser
selection repair or routes wrong delete intentmodel-input-strategy if deletion updates model but loses collapsed
selectionselection-reconciler if model selection is correct but DOM/caret is wrongdom-repair-queue if post-commit repair is needed after model-owned deleteEditableDOMRoot only if the coordinator wiring prevents the right owner from
runningMinimal fix rules:
Green command:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Backspace at selected text end"
Add rows one at a time.
Rows:
Each row must include follow-up typing.
Focused command:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Backspace"
Mirror the Backspace suite for Delete/forward-delete.
Rows:
Do not use semantic handle proof for these rows unless the specific behavior is not native browser transport.
Focused command:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Delete"
Use highlighted-text.test.ts.
Rows:
Assertions:
Focused command:
bunx playwright test ./playwright/integration/examples/highlighted-text.test.ts --project=chromium --grep "delete|Backspace"
Use current inline/void examples if supported by the final API surface.
Rows:
If an example/test only exists for dead legacy behavior, hard-cut or rewrite it instead of preserving stale expectations.
Focused command:
bunx playwright test ./playwright/integration/examples/editable-voids.test.ts --project=chromium
Current large-doc rows use semantic handles for direct-sync delete. Add native or shell-path rows where honest.
Rows:
Keep semantic-handle rows only for model-path proof. User-path rows need native keyboard transport.
Focused command:
bunx playwright test ./playwright/integration/examples/large-document-runtime.test.ts --project=chromium --grep "delete|Backspace"
Rows:
Focused command:
bunx playwright test ./playwright/integration/examples/shadow-dom.test.ts --project=chromium --grep "Backspace|Delete|line"
After Chromium rows are green:
Do not blanket skip.
For each failing project:
Commands:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=firefox --grep "Backspace|Delete"
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=webkit --grep "Backspace|Delete"
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=mobile --grep "Backspace|Delete"
| Lane | Current confidence | Needed proof |
|---|---|---|
| Insert after browser selection | Good | Keep existing caret rows |
| Backspace after browser selection | Bad | Native Backspace + model/DOM/caret + follow-up typing |
| Delete after browser selection | Bad | Native Delete + model/DOM/caret + follow-up typing |
| Expanded range delete | Medium | Native Backspace/Delete rows, not only semantic handles |
| Decorated text delete | Weak | Highlighted-text delete/backspace rows |
| Inline/void delete | Weak | Void/inline deletion + follow-up typing |
| Large-doc delete | Medium | Add native user-path rows beside semantic-handle rows |
| Shadow DOM delete | Weak | Backspace/Delete inside Shadow DOM |
| IME deletion | Not active owner | Add only after basic deletion rows are stable |
| Mobile deletion | Unknown | Expand after Chromium owner is closed |
Required focused gate:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "Backspace|Delete|visual caret|browser-selected end"
Required expanded Chromium gate:
bunx playwright test ./playwright/integration/examples/richtext.test.ts ./playwright/integration/examples/highlighted-text.test.ts ./playwright/integration/examples/large-document-runtime.test.ts ./playwright/integration/examples/shadow-dom.test.ts ./playwright/integration/examples/editable-voids.test.ts --project=chromium
Required package gates after product changes:
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
Final release-quality gate:
bun test:integration-local
bun test:integration-local can close the lane only if failures/skips are
classified explicitly. A green focused Chromium suite is not full browser
editing closure.
Stop only when:
Do not stop at one fixed Backspace row. That would repeat the exact mistake that let this bug through.