docs/solutions/ui-bugs/2026-04-30-slate-react-beforeinput-delete-commands-must-refresh-synced-selection.md
slate-react cleanup threaded the typed kernel command into model-owned
beforeinput execution so insert commands no longer reparsed stale raw event
data. That was correct for text insertion, but delete command shape depends on
the current selection after DOM selection import.
deleteContentBackward command could be computed while Slate still
had a collapsed selection.syncSelectionForBeforeInput could then import an expanded DOM selection, but
execution still used the stale prepared single-character delete command.Keep prepared commands authoritative for non-delete input, but refresh delete commands from the synced selection:
const parsedCommand = () =>
getEditableCommandFromBeforeInputType({
data,
inputType: type,
selection,
})
const command =
preparedCommand === undefined || type.startsWith('delete')
? (parsedCommand() ?? preparedCommand ?? null)
: preparedCommand
Lock both sides with tests:
applyModelOwnedBeforeInputOperation({
command: { inputType: 'insertText', kind: 'insert-text', text: 'kernel' },
data: 'event',
inputType: 'insertText',
})
and:
applyModelOwnedBeforeInputOperation({
command: { direction: 'backward', kind: 'delete' },
inputType: 'deleteContentBackward',
selection: expandedSelection,
})
The insert row asserts the prepared command wins over stale event data. The
delete row asserts synced expanded selection upgrades the command to
delete-fragment.
Text insertion authority lives in the typed command payload. Delete command
authority lives in both the input type and the selection shape. beforeinput
selection sync sits between kernel preparation and model execution, so delete
commands must be the narrow exception to "prepared command wins."