docs/solutions/logic-errors/2026-03-31-table-shift-arrow-selection-must-own-keydown-before-native-selection.md
Single-cell Shift+Arrow expansion across table cell boundaries produced the correct final cell selection, but only after a visible intermediate native range.
The flash was most obvious on Shift+Down and Shift+Right, where the browser first painted a normal text selection and table code repaired it one tick later.
Table selection only owned multi-cell Shift+Arrow at keydown time.
The one-cell case relied on apply-time repair after native selection had already moved once, which produced a visible intermediate text range.
Take ownership of the one-cell boundary-crossing case in onKeyDownTable before native selection applies, and remove the apply-time repair path.
Shift+Arrow path.Shift+Up and Shift+Down, reuse the same visual-line boundary logic as plain moveLine.Shift+Left and Shift+Right, only intercept when the focus is already at the cell start or end.moveSelectionFromCell(..., { fromOneCell: true }) immediately and prevent the native event.overrideSelectionFromCell and stop calling it from withApplyTable.That moves the ownership seam fully to keydown-time interception.
These checks passed:
bun test packages/table/src/react/onKeyDownTable.spec.tsx packages/table/src/lib/withApplyTable.spec.ts packages/table/src/lib/withTable.spec.tsx packages/table/src/lib/transforms/moveSelectionFromCell.spec.tsx
pnpm install
pnpm turbo build --filter=./packages/table
pnpm turbo typecheck --filter=./packages/table
pnpm lint:fix
The new coverage proves:
onKeyDownTable.spec.tsx eagerly expands Shift+Down and Shift+Right from one cell into the adjacent cellonKeyDownTable.spec.tsx keeps Shift+Down native while the focus can still move within the current multi-block cellIf a keyboard interaction should never show an intermediate native selection state, do not repair it later in apply.
Own it at the keydown seam instead of carrying a second repair path that can drift from the real behavior.
When plain-arrow and shifted-arrow movement share the same visual boundary rule, put that boundary check in one helper so the two seams cannot drift.