|
| 1 | +--- |
| 2 | +module: Table |
| 3 | +date: 2026-03-31 |
| 4 | +problem_type: logic_error |
| 5 | +component: editor_transforms |
| 6 | +symptoms: |
| 7 | + - "Shift+Arrow from one table cell into another briefly showed a native text range before cell selection took over" |
| 8 | + - "Cross-cell Shift+Down and Shift+Right felt delayed even though the final cell selection was correct" |
| 9 | +root_cause: async_timing |
| 10 | +resolution_type: code_fix |
| 11 | +severity: medium |
| 12 | +tags: |
| 13 | + - table |
| 14 | + - selection |
| 15 | + - keyboard |
| 16 | + - shift-arrow |
| 17 | + - timing |
| 18 | + - keydown |
| 19 | + - slate |
| 20 | +--- |
| 21 | + |
| 22 | +# Table Shift+Arrow selection must own keydown before native selection |
| 23 | + |
| 24 | +## Problem |
| 25 | + |
| 26 | +Single-cell `Shift+Arrow` expansion across table cell boundaries produced the correct final cell selection, but only after a visible intermediate native range. |
| 27 | + |
| 28 | +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. |
| 29 | + |
| 30 | +## Root cause |
| 31 | + |
| 32 | +Table selection only owned multi-cell `Shift+Arrow` at keydown time. |
| 33 | + |
| 34 | +The one-cell case relied on apply-time repair after native selection had already moved once, which produced a visible intermediate text range. |
| 35 | + |
| 36 | +## Fix |
| 37 | + |
| 38 | +Take ownership of the one-cell boundary-crossing case in `onKeyDownTable` before native selection applies, and remove the apply-time repair path. |
| 39 | + |
| 40 | +- Keep the existing eager multi-cell `Shift+Arrow` path. |
| 41 | +- Add a one-cell path that checks whether the moving focus edge is about to leave the current cell. |
| 42 | +- For `Shift+Up` and `Shift+Down`, reuse the same visual-line boundary logic as plain `moveLine`. |
| 43 | +- For `Shift+Left` and `Shift+Right`, only intercept when the focus is already at the cell start or end. |
| 44 | +- When the boundary test passes, call `moveSelectionFromCell(..., { fromOneCell: true })` immediately and prevent the native event. |
| 45 | +- Delete `overrideSelectionFromCell` and stop calling it from `withApplyTable`. |
| 46 | + |
| 47 | +That moves the ownership seam fully to keydown-time interception. |
| 48 | + |
| 49 | +## Verification |
| 50 | + |
| 51 | +These checks passed: |
| 52 | + |
| 53 | +```bash |
| 54 | +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 |
| 55 | +pnpm install |
| 56 | +pnpm turbo build --filter=./packages/table |
| 57 | +pnpm turbo typecheck --filter=./packages/table |
| 58 | +pnpm lint:fix |
| 59 | +``` |
| 60 | + |
| 61 | +The new coverage proves: |
| 62 | + |
| 63 | +- `onKeyDownTable.spec.tsx` eagerly expands `Shift+Down` and `Shift+Right` from one cell into the adjacent cell |
| 64 | +- `onKeyDownTable.spec.tsx` keeps `Shift+Down` native while the focus can still move within the current multi-block cell |
| 65 | + |
| 66 | +## Prevention |
| 67 | + |
| 68 | +If a keyboard interaction should never show an intermediate native selection state, do not repair it later in `apply`. |
| 69 | + |
| 70 | +Own it at the keydown seam instead of carrying a second repair path that can drift from the real behavior. |
| 71 | + |
| 72 | +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. |
| 73 | + |
| 74 | +## Related Issues |
| 75 | + |
| 76 | +- Related learning: [2026-03-29-table-arrow-navigation-must-own-moveline-and-visual-line-boundaries.md](/Users/hyeongjin/Workspace/plate/.claude/docs/solutions/logic-errors/2026-03-29-table-arrow-navigation-must-own-moveline-and-visual-line-boundaries.md) |
0 commit comments