r218 AI Summary

**r218 = VSIL Editor refinement pass.** Three integrated quality-of-life features built on the multi-select foundation of r213–r217.

What shipped

1. **Marquee drag-select on the canvas** — click-drag to rubber-band-select. Plain drag replaces selection; Shift+drag toggles. Document-level mouseup listener ensures the commit fires even when mouseup lands on a child layer box.

2. **`[` / `]` keyboard shortcuts** — move the selected layer (or selection block) up/down in the layer stack. Wired through `useShortcuts` alongside r217's Cmd+A and Esc.

3. **Multi-select block move** — when multiple layers are selected, `[` / `]` moves the whole block as a contiguous unit, preserving internal order. Algorithm partitions layers into selected + unselected, finds the boundary, and rebuilds the list with the selected block at the new position.

Single critical fix (4 hours to find)

The marquee commit was failing because `onMouseUp` on the canvas doesn't always fire — if the mouseup lands on a child element (a layer box), the event goes to that child, not the canvas. **Fix**: add a document-level mouseup listener that's active while a marquee is in progress. The commit fires regardless of where mouseup lands.

The "singleton + listener" pattern

r218 introduces a module-level `reorderListener: fn | null` slot. The selection hook is the canonical source of truth (singleton `currentLayers`). When `reorderSelected` mutates the layers, it invokes the listener. The LayersPanel registers a listener on mount (composing with the prior listener for test compatibility) and restores the previous listener on unmount. This keeps the panel's local React state in sync without coupling it to the selection hook.

Tests

Total: **489/489 ✅**.

Files

5 source files modified (~380 lines), 1 new test file (33 unit tests), 1 new smoke test (19 Playwright tests), 4 screenshots.

What's next