Back to Plate

Slate v2 IME / Mobile / Browser File Ledger

docs/plans/2026-04-11-slate-v2-ime-mobile-browser-file-ledger.md

53.0.635.0 KB
Original Source

Slate v2 IME / Mobile / Browser File Ledger

Historical batch/review note. Live browser/input scenario-proof truth now lives in true-slate-rc-proof-ledger.md. Live legacy-file closure truth now lives in release-file-review-ledger.md. Keep this file for archaeology and batch history, not as the active proof authority.

Purpose

Historical batch/review record for the reopened IME/mobile/browser RC lane.

Use the same blunt rule as the other ledgers:

  • [x] reviewed
  • [ ] still needs deep review

Do not use this file as passive archaeology. Every behavior-bearing legacy file must map to one or more scenario rows.

Ground Rules

  • missing proof on a behavior-bearing row blocks by default
  • only engine-internal helpers with no user-visible browser/input behavior may be omitted
  • older “better-cut” language in release docs does not close this lane by itself

Bootstrap Seed

Pinned slate-v2 diff command result:

sh
git -C /Users/zbeyens/git/slate-v2 diff --name-status --diff-filter=ACDMRTUXB origin/main...HEAD -- \
  packages/slate-react \
  packages/slate-dom \
  packages/slate-browser \
  playwright/integration/examples \
  site/examples/ts

Current result:

  • EMPTY

Read:

  • there are no still-unreviewed browser/input rows hiding in the current origin/main...HEAD diff for those target directories
  • the seed therefore comes from the live proof lanes plus the legacy behavior-bearing patch files themselves

Dual-Axis Behavior / Parity Ledger

ReviewScenarioLegacy file(s)Current owner / equivalentProof laneBrowser scopeExpected outcomeActual outcomeArtifact linksOwnerStatus
[x]placeholder IMEpackages/slate-react/src/components/editable.tsx, packages/slate-react/src/components/string.tsxEditable, EditableText, placeholder exampleplaceholder-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, proof:agent-browser:ios:placeholder-input:local, proof:appium:ios:placeholder-input:local, proof:appium:android:placeholder-input:localautomated-direct: chromium + firefox + Android Chrome emulator, automated-proxy: webkit-desktop, plus setup-green / behavior-red: iOS Simulator Safariempty placeholder composition behaves safely enough to support the strong RC claimChromium direct proof is green on the FEFF-backed placeholder path, Firefox now has a stronger direct composition lane via Playwright keyboard.insertText, desktop WebKit has an explicit direct-input ceiling probe showing that insertText commits cleanly but does not expose composition-specific input events there, Android direct typing proof is green, and direct Appium iOS Safari setup is green. Current iOS behavior proof remains red through Appium/XCUITest value (blockTexts: "", repeated input:undefined:[null], `slateSelection: 0.0:00.0:0), and native-context coordinate tapping still fails to surface an iPhone keyboard. The current agent-browser` iOS provider is also not trustworthy on these routes because it often exposes only the Next shell without the editor nodeplaceholder-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, 2026-04-03-jsdom-contenteditable-composition-is-not-a-trustworthy-ime-proof.md, 2026-04-12-appium-ios-safari-loads-local-slate-routes-but-xcuitest-value-does-not-drive-contenteditable.md, package.jsonplaywright + site/examples + browser-mobile transports
[x]no-FEFF placeholder IMEpackages/slate-react/src/components/editable.tsx, packages/slate-react/src/components/string.tsxEditable, EditableText, placeholder-no-feff exampleplaceholder-ime.test.ts, firefox-direct-ime.test.ts, ime-proxy.test.ts, proof:agent-browser:ios:placeholder-no-feff-input:local, proof:appium:ios:placeholder-no-feff-input:local, proof:appium:android:placeholder-no-feff-input:localautomated-direct: chromium + firefox + Android Chrome emulator, automated-proxy: webkit-desktop, plus setup-green / behavior-red: iOS Simulator Safarino-FEFF empty placeholder path composes like the relied-on legacy path or is explicitly justified as a changed contractChromium composition proof is green on the line-break no-FEFF path, Chromium delayed per-key typing is now also green, Firefox has direct composition proof on the same surface via Playwright keyboard.insertText, desktop WebKit keeps the proxy lane, and Android direct no-FEFF typing proof is green. Direct Appium iOS Safari setup is green on the route, but typing remains red with the same XCUITest value no-op shape as FEFF (blockTexts: "", repeated input:undefined:[null], `slateSelection: 0.0:00.0:0). The current agent-browser` iOS provider is route-shell-broken here, so it should not be treated as behavior evidenceplaceholder-ime.test.ts, firefox-direct-ime.test.ts, ime-proxy.test.ts, 2026-04-12-appium-ios-safari-loads-local-slate-routes-but-xcuitest-value-does-not-drive-contenteditable.md, package.jsonplaywright + site/examples + browser-mobile transports
[x]inline-edge IMEpackages/slate-react/src/components/editable.tsx, packages/slate-react/src/components/string.tsxEditable, EditableText, inline-edge exampleinline-edge-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, proof:agent-browser:ios:inline-edge-input:local, proof:appium:android:inline-edge-input:localautomated-direct: chromium + firefox + iOS Simulator Safari + Android Chrome emulator, automated-proxy: webkit-desktop, plus direct-input ceiling: webkit-desktopinline-edge composition lands on the intended text leaf with legacy-equivalent behaviorChromium direct proof is green after semantic selection setup, Firefox now has a stronger direct composition lane via Playwright keyboard.insertText, desktop WebKit has an explicit direct-input ceiling probe showing that insertText commits cleanly but stays below composition-specific proof, and the packaged iOS Simulator/Appium edge-input lanes are green once the runner collapses DOM selection onto the leading zero-width text leaf before typinginline-edge-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, 2026-04-04-inline-edge-ime-proofs-should-set-selection-semantically-before-composition.md, package.jsonplaywright + site/examples + browser-mobile transportsopen
[x]void-edge IMEpackages/slate-react/src/components/editable.tsx, packages/slate-react/src/components/string.tsxEditable, EditableText, void-edge examplevoid-edge-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, proof:agent-browser:ios:void-edge-input:local, proof:appium:android:void-edge-input:localautomated-direct: chromium + firefox + iOS Simulator Safari + Android Chrome emulator, automated-proxy: webkit-desktop, plus direct-input ceiling: webkit-desktopvoid-like IME behavior matches the relied-on spacer semanticsChromium direct proof is green on the real spacer structure, Firefox now has a stronger direct composition lane via Playwright keyboard.insertText, desktop WebKit keeps an explicit direct-input ceiling probe instead of fake direct composition proof, and the packaged iOS Simulator/Appium edge-input lanes are green with the same zero-width selection-collapse primitive used before typingvoid-edge-ime.test.ts, firefox-direct-ime.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, 2026-04-04-void-like-zero-width-ime-proofs-need-the-real-void-spacer-structure.md, package.jsonplaywright + site/examples + browser-mobile transportsopen
[x]blur/focus selection recoverypackages/slate-react/src/components/editable.tsx, packages/slate-react/test/react-editor.spec.tsxEditable, ReactEditor, rich-inline reset/refocus flowsurface-contract.tsx, rich-inline.test.ts, test:slate-browser:desktop-parity:localheadless + automated-direct: chromium, firefox, webkit-desktopfocus restore behaves at least as safely as legacy across the required browser matrixcurrent focus init and mid-transform safety are green in headless proof, and the rich-inline blur/focus reset row is now green on Chromium, Firefox, and desktop WebKit; real iOS Safari remains unprovedsurface-contract.tsx, rich-inline.test.ts, package.jsonpackages/slate-react + playwrightopen
[x]transient DOM-point gap / mutation-repair / fail-closed focus restorepackages/slate-react/src/components/restore-dom/restore-dom.tsx, packages/slate-react/src/components/restore-dom/restore-dom-manager.ts, packages/slate-react/src/components/editable.tsxmounted Editable, ReactEditor, DOMBridgesurface-contract.tsx, main IME/browser rows, structural split/join rowheadless, automated-direct: chromium, firefox, android emulator, plus automated-proxy: webkit-desktoptransient DOM bridge gaps fail closed without hidden runtime errors and without regressing user-visible behaviorcurrent focus-time transient DOM-point failure is covered and fails closed; main IME rows are green without restore-dom; structural Enter/Backspace churn is now green on Chromium and Android through editor-owned keydown paths; the deleted restore-dom family is now treated as a rerender-era guard that is no longer a standalone current blockersurface-contract.tsx, 2026-04-09-slate-react-focus-restore-must-fail-closed-on-transient-dom-point-gaps.md, 2026-04-12-restore-dom-was-a-rerender-era-guard-not-a-current-v2-runtime-need.md, 2026-04-12-structured-enter-and-backspace-need-editor-owned-keydown-paths.mdpackages/slate-react + packages/slate-domclosed
[x]zero-width selection normalizationpackages/slate-react/src/components/string.tsx, packages/slate-react/src/components/editable.tsxDOMBridge, slate-browser selection helpers, zero-width matrix examplezero-width-matrix.test.ts, bridge.ts tests, test:slate-browser:desktop-parity:localautomated-direct: chromium, firefox, webkit-desktop plus headless bridge proofDOM/Slate round-trip around zero-width leaves stays normalized instead of leaking sentinel offsetscurrent bridge proof is green, and the zero-width matrix row is now green on Chromium, Firefox, and desktop WebKit; real iOS Safari remains unprovedzero-width-matrix.test.ts, 2026-04-03-zero-width-dom-selection-bridges-must-normalize-both-directions.md, 2026-04-04-slate-browser-playwright-helpers-must-normalize-zero-width-selection-and-wait-for-selection-sync.md, package.jsonpackages/slate-dom + slate-browseropen
[x]post-composition undo / redopackages/slate-react/src/components/editable.tsxwithHistory(createEditor()), browser/history proof stack, IME proof surfacesplaceholder-ime.test.ts, inline-edge-ime.test.ts, void-edge-ime.test.ts, test:slate-browser:ime:localautomated-direct: chromiumundo and redo immediately after composition are directly proved on the placeholder/inline-edge/void-edge rowsdedicated IME history proof now exists on the FEFF placeholder path, no-FEFF placeholder path, inline-edge path, and void-edge path in Chromium; broader platform/browser IME parity is still blocked elsewhereplaceholder-ime.test.ts, inline-edge-ime.test.ts, void-edge-ime.test.ts, package.jsonpackages/slate-history + playwrightopen
[x]Android composition / diff / flushpackages/slate-react/src/hooks/android-input-manager/android-input-manager.ts, packages/slate-react/src/hooks/android-input-manager/use-android-input-manager.ts, packages/slate-react/src/components/editable.tsxmissing direct equivalent; current owner hypothesis is mounted Editable plus browser proof lanesandroid-tests.test.ts, test:slate-browser:android-proxy:local, proof:appium:android:local, proof:appium:android:placeholder-input:local, proof:appium:android:inline-edge-input:local, proof:appium:android:void-edge-input:local, proof:appium:android:split-join:local, proof:appium:android:empty-rebuild:local, proof:appium:android:remove-range:local, android-split-join.test.ts, android-empty-rebuild.test.ts, android-remove-range.test.ts, android-special-structural.test.tsautomated-proxy: mobile Chromium, automated-direct: Android Chrome emulator IME rows plus split/join, empty/delete-rebuild, remove-range, and special structural rows, plus remaining-android-specific: autocorrect / glide / voiceAndroid-specific composition/diff/flush behavior is directly proved or explicitly justified as engine-internalthe Android hub exists, the mobile Playwright proxy lane is green on placeholder/inline-edge/void-edge plus IME undo/redo, Appium now has direct green packaged rows for placeholder, inline-edge, void-edge, split/join, empty/delete-rebuild, and remove-range on Android Chrome emulator, and the matching Chromium rows for split/join, empty, remove, and the structural special subcases are also green. The remaining Android-specific open slice is now the keyboard-feature lane only. Direct local probes can show keyboardShown: true and switch to NATIVE_APP, but expose zero Gboard candidate nodes, and hardware keycodes only yield literal cant insertion, so autocorrect / glide / voice are explicitly tooling-blocked on the current local stackandroid-tests.test.ts, android-split-join.test.ts, android-empty-rebuild.test.ts, android-remove-range.test.ts, android-special-structural.test.ts, package.json, 2026-04-11-appium-android-setup-proof.md, 2026-04-12-structured-enter-and-backspace-need-editor-owned-keydown-paths.md, 2026-04-12-structural-break-proof-rows-need-selection-sync-before-follow-up-typing.md, 2026-04-12-appium-android-chrome-can-show-keyboard-state-without-exposing-gboard-candidates.md, packages/slate-react/src/hooks/android-input-manager/android-input-manager.tsplatform parity lanetooling-blocked / external
[x]iOS Safari / WebKit composition / focuspackages/slate-react/src/components/editable.tsxEditable, ReactEditor, DOMBridgedesktop parity lane, explicit WebKit direct-input ceiling lane, composition proxy lane, proof:agent-browser:ios:local, proof:agent-browser:ios:placeholder-input:local, proof:agent-browser:ios:inline-edge-input:local, proof:agent-browser:ios:void-edge-input:local, plus missing real-device laneautomated-direct: webkit-desktop for focus/selection, direct-input ceiling: webkit-desktop, automated-proxy: webkit-desktop for composition, automated-direct: mixed iOS Simulator Safari rows, tooling-blocked: broader iOS Safari composition/focusiOS Safari / WebKit composition and focus behavior is directly proved or explicitly justified as non-behavioraldesktop WebKit has direct proof for zero-width normalization and blur/focus recovery, an explicit direct-input ceiling probe showing that Playwright insertText does not expose composition-specific input events there, and a green browser-level proxy composition lane on the IME surfaces. On iOS Simulator Safari, direct Appium route/setup is green, inline-edge and void-edge have direct green packaged rows, but placeholder and no-FEFF placeholder typing remain behavior-red through XCUITest value. Broader iOS Safari composition/focus remains tooling-blocked, and the external plan now lives in 2026-04-12-ios-safari-broader-composition-focus-external-evidence-plan.mdrich-inline.test.ts, zero-width-matrix.test.ts, webkit-direct-ime-ceiling.test.ts, ime-proxy.test.ts, 2026-04-11-slate-browser-agent-browser-ios-setup-proof.md, 2026-04-12-appium-ios-safari-loads-local-slate-routes-but-xcuitest-value-does-not-drive-contenteditable.md, package.jsonplatform parity lanetooling-blocked / non-RC
[x]Firefox composition / selection recoverypackages/slate-react/src/components/editable.tsxEditable, EditableBlocks, ReactEditor, DOMBridge, drag/drop cleanup example, table multi-range example, nested-editable focus exampledesktop parity lane plus direct composition lane plus proxy backstop plus dedicated drag/drop, table multi-range, and nested-editable lanesautomated-direct: firefox for selection recovery, IME composition, dragged-node-unmount cleanup, table multi-range preservation, and nested-editable focus bounce, automated-proxy: firefox + webkit-desktop backstopFirefox composition, selection recovery, dragged-node-unmount cleanup, table multi-range preservation, and nested-editable focus bounce are directly proved or explicitly justified as non-behavioralFirefox now has direct browser proof for blur/focus selection recovery, zero-width normalization, direct composition on placeholder, no-FEFF placeholder, inline-edge, and void-edge via Playwright keyboard.insertText, plus dedicated drag/drop, multi-range table, and nested-editable focus lanes. Local Firefox/browser parity is exhausted; the remaining work is external Android keyboard-feature evidence and broader iOS evidence, not another Firefox holerich-inline.test.ts, zero-width-matrix.test.ts, firefox-direct-ime.test.ts, drag-drop-cleanup.test.ts, table-multi-range-firefox.test.ts, firefox-nested-editable-focus.test.ts, ime-proxy.test.ts, package.json, 2026-04-12-firefox-drag-drop-proof-needs-example-owned-drop-mutation-and-document-level-drag-cleanup.md, 2026-04-12-firefox-table-multi-range-proof-needs-native-table-selection-and-a-multi-range-sync-guard.md, 2026-04-12-firefox-nested-editable-focus-proof-needs-a-real-nested-contenteditable-surface.mdplatform parity lanelocal-closed

Legacy Slate React High-Risk Files

Current Slate v2 Runtime Files

Current Browser Proof Files

Current Docs / Risk Framing Files

Current Read

  • Chromium IME/browser proof is real and worth keeping
  • the strong zero-regression RC claim is still blocked by platform/browser proof holes
  • the remaining behavior-bearing rows are now:
    • Android keyboard-feature behavior beyond ordinary typing and structural rows
    • broader iOS Safari / WebKit composition/focus beyond the currently green packaged lanes
  • the local Firefox/browser lane is exhausted
  • all three now have a cleaner manual-device path because the IME surfaces emit debug JSON and HTML dumps through ?debug=1
  • ReactEditor and DOMBridge are now reviewed as current owners
  • the remaining red is external/tooling-limited platform proof, not unread core owner files
  • row-specific manual capture scaffolds now exist under:

Exact Legacy Compat Behaviors Still Unproved

Android

  • beforeinput is non-cancelable on Android, so legacy routed it through the Android input manager instead of the normal DOM path
  • Android IMEs can re-apply their own selection after Slate sets one, which is why legacy forced selection twice across animation-frame and timeout phases
  • Gboard spellchecker state needed an extra forced selection change after the animation-frame selection update
  • the current local Appium + Chrome stack can prove keyboard visibility but not expose or accept Gboard candidates, so autocorrect / glide / voice remain external-evidence rows

Primary legacy evidence:

Safari / WebKit

  • Shadow-root selectionchange in WebKit needed the odd execCommand('indent') / deselect workaround path
  • Safari can dispatch beforeinput(insertFromComposition) before compositionend, so composition state had to be cleared early
  • Safari blur could leave selection ranges behind even after the editable lost focus
  • Safari paste InputEvents could miss the Slate fragment payload, requiring the clipboard fallback path

Primary legacy evidence:

Firefox

  • Firefox selection reads needed special handling because the “normal selection way” could lie
  • nested editable focus had to be bounced back to the main editor to avoid keyboard-navigation breakage
  • Firefox multi-range table selection could be broken by calling setDomSelection
  • Firefox drop flows needed global dragend listening because dragged nodes could unmount before their own dragend

Primary legacy evidence: