The block-handle grip is now actually grabbable. Native HTML5 drag:
mousedown + drag → dragstart fires → wordsDragState.sourceIndex is
set → the BlockInserter switches to drop-target mode (line goes
prominent, "+" hides) → dragover snaps to the nearest seam → drop
commits `moveBlockTo({fromIndex, toIndex})`.
Keyboard equivalent: focus the grip → Space toggles "grabbed" mode
(grip lights up accent-filled, aria-pressed) → ArrowUp / ArrowDown
swaps with the neighbour (via the existing moveBlock command, so
each press is committed individually) → Space or Escape exits.
Architecture:
- `words-drag-state.svelte.ts` — module-level `$state` shared between
handle (writer) and inserter (reader). Lives in eidos because this
is a visual interaction pattern, not a soma/morfo contract.
- `moveBlockToAt(state, from, to)` in operations.ts — splice out, then
splice in at the post-removal destination. Normalizes + drops caret
at the moved block's new position.
- `moveBlockTo` case in commands.ts. The existing `moveBlock`
(swap-with-neighbor) stays because the click-menu uses it.
- BlockInserter dragover/drop handlers reuse `findSeamForCursor` —
same seam math as the regular insert hover, so the drop indicator
lands exactly where the "+" would have. `seam.insertIndex >
fromIndex ? -1` correction accounts for the source's removal
shifting subsequent indices down by one.
CSS:
- `[data-words-block-inserter][data-mode='drop']` makes the line solid
+ thicker + with an outer glow during drag, and hides the "+"
button entirely (the line IS the drop indicator).
- `[data-words-block-handle][data-grabbed]` paints the grip accent-
filled with a focus ring for keyboard-grab state.
aria: `aria-pressed={keyboardGrabbed}`, descriptive aria-label on the
grip mentioning Space + arrows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>