Back to Plate

InsertTranspose beforeinput needs a model-owned command

docs/solutions/logic-errors/2026-05-09-inserttranspose-beforeinput-needs-model-owned-command.md

53.0.62.2 KB
Original Source

InsertTranspose beforeinput needs a model-owned command

Problem

Mac Control+T can emit beforeinput with inputType: "insertTranspose". Slate classified that as model-owned text input, prevented native DOM mutation, then had no command to apply.

Symptoms

  • A package proof for insertTranspose left abc unchanged.
  • The browser route also needed a stable synthetic beforeinput row because the real hotkey is Mac/browser-specific.

Solution

Represent transpose as a first-class input command:

  • parse insertTranspose to transpose-character in the editing kernel
  • apply it in model-input-strategy.ts
  • swap adjacent characters around a collapsed selection in mutation-controller.ts
  • keep a no-beforeinput keyboard fallback for Ctrl+T
  • prove both the package command path and the plaintext browser event path

The command swaps the character before the cursor with the character after it. At the end of a text node, it swaps the previous two characters. Selection lands after the swapped pair.

Why This Works

insertTranspose is not ordinary inserted text; it carries the edit intent in inputType, not in data. Treating every insert* event as data-backed text means Slate can correctly prevent native mutation but still silently drop the edit.

The clean owner is the editing kernel plus model-input strategy, not a Playground shortcut handler. That keeps browser input behavior centralized with the rest of beforeinput handling.

Prevention

  • When harvesting editor keyboard tests, check for inputType rows with no data payload.
  • Add one package proof for command parsing/application and one browser proof for the event route when the behavior is browser-visible.
  • Do not treat OS labels or browser skip tags as Slate behavior. Port the invariant, not the upstream harness.