Altinity / Altinity/altinity-sql-browser
Schema graph: multi-select + group-move of nodes (marquee, Shift-click, ⌘A) with single-step undo
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8
- Forks
- 2
- Avg merge
- 1h 34m
- Merged PRs (30d)
- 6
Description
⚠️ Reconciled 2026-06-30 (#88). Settled approach: imperative + pure-core, no framework (CLAUDE.md rule 5). Before implementing, extract the interaction surface so this doesn't grow inside
explain-graph.js:src/core/graph-selection.js(pure — selection set,nodesInRectmarquee hit-test, select-all/connected, batch move + align/distribute, batch undo op) andsrc/ui/graph-surface.js(pan/zoom, pointer capture, marquee rect, keybindings, selection CSS, toolbar — shared with the EXPLAIN pipeline graph where possible). The Implementation notes below still apply, but land in those extracted modules rather than directly inattachSchemaInteractions. See #68 Phase 5.
Problem
In the full-screen schema/data-flow view, layout is hand-tunable by ⌘/Ctrl-dragging a card — but one node at a time. Re-arranging a group of related tables means dragging each individually, which is slow and tedious. There's no way to select several nodes and move them together.
Proposal
Add a selection set of nodes and let the whole set be dragged as one unit, with light group-layout actions. Build entirely on the existing single-node move machinery (no new runtime deps).
Selecting nodes (all additive to today's bindings):
- Marquee: Shift-drag on empty canvas draws a selection rectangle; cards it touches join the selection. (Plain drag and ⌘/Ctrl-drag stay pan, so Shift-drag is the free gesture for marquee.)
- Shift-click a card: toggle it in/out of the selection (doesn't open the detail pane).
- ⌘A: select every node (when the view has focus).
- Select a whole database / connected lineage component: a one-shot action — e.g. Alt/double-click a card, or a small "select DB / select connected" control — so a related cluster can be grabbed at once.
- Deselect: plain-click empty canvas, or Esc.
Moving the group: with ≥1 node selected, ⌘/Ctrl-drag any selected card moves the entire selection by the same delta; edges within and touching the set re-route live. ⌘/Ctrl-drag on an unselected card keeps today's single-node behavior.
Group layout actions (shown only when ≥2 are selected): align (left/right/top/bottom), distribute evenly, and optionally re-tidy (re-run dagre on just the selected subgraph). Layout math stays pure in src/core/.
Persistence & undo: the group move persists per-node like single moves (savedPositions) and is undone/redone as one step (a single ⌘Z reverts the whole drag / align).
Current implementation (reuse what exists)
The single-node move path is fully built and is the template — multi-node is additive on top of it.
- Interaction wiring:
attachSchemaInteractions(...)—src/ui/explain-graph.js:423.onDown(:478) gates plain-press (pan / open detail pane) vs ⌘/Ctrl-drag (move one node);onMove(:496) →placeAt(:443);onUp(:502) records one undo op. - Pure helpers,
src/core/graph-layout.js(all reusable for a group):dragDeltaToSvg(:47, viewBox-aware — the same delta applies to every selected node),straightEdgePoints(:31),incidentEdges(:36),applyPositions(:55, already loops),recordPosition(:65),createMoveHistory(:77, op shape{id,from,to}). - Persistence: positions map
{ "db.table": {x,y} }issavedPositionson the result (app.js:586), reloaded viaapplyPositions(explain-graph.js:257). - Existing single "selected" ring (
markSelected/clearSchemaSelection,.eg-card--selected+.eg-card-ring) insrc/ui/schema-detail.js+styles.css:703is coupled to the detail pane and mutually-exclusive. A multi-select set is a distinct concept and must be reconciled with it. - No marquee / multi-select infrastructure exists yet. Modifier conventions today: plain-click node → detail pane; ⌘/Ctrl-drag node → move; plain/⌘ drag empty canvas → pan; ⌘/Ctrl+wheel → zoom; ⌘Z/⌘⇧Z/⌘Y → undo/redo.
Implementation notes
src/core/graph-layout.js(pure, 100% covered): extendcreateMoveHistoryto a batch op{ moves: [{id,from,to}] }so undo/redo applies to the whole set in one step; addnodesInRect(nodes, rect)for marquee hit-testing and pure align/distribute helpers. ReusedragDeltaToSvg,straightEdgePoints,incidentEdges,recordPosition,applyPositionsas-is.src/ui/explain-graph.jsattachSchemaInteractions: hold aSetof selected ids; add Shift-click toggle, the Shift-drag marquee (draw an SVG rect, hit-test on mouseup), ⌘A, and select-DB/component; inonDown/onMoveloopplaceAtover the selection and straighten the union of incident edges once per frame; record one batch undo op inonUp.src/ui/schema-detail.js+styles.css: reconcile the existing detail-pane ring with the new multi-select highlight — either unify into one selection set (pane shows the most-recently-clicked) or give the move-selection its own class. Add a marquee-rectangle style.- No new runtime deps (DOM/SVG only); tests in the same change (pure helpers to 100%, integration for the wiring) per the CLAUDE.md coverage gate.
Acceptance criteria
- Shift-drag marquee, Shift-click toggle, ⌘A, and select-DB/connected all build a multi-node selection with a clear highlight.
- ⌘/Ctrl-dragging a selected card moves the whole selection; all incident edges re-route live; single-node drag of an unselected card is unchanged.
- Align + distribute (and/or re-tidy) act on the selection.
- The group move/align persists in
savedPositionsand is undone/redone as a single step. - Plain-click empty canvas / Esc clears the selection; detail-pane ring behavior reconciled.
-
npm testgreen at the per-file coverage gate; no new runtime dependency.
Open design questions
- Exact bindings (Shift-drag marquee vs a toggled "select mode"; how "select DB/component" is triggered).
- Marquee hit policy: intersect vs full-containment.
- Unify the detail-pane ring with the move-selection, or keep them visually distinct?
- Scope of "auto-layout": just align/distribute, or also re-dagre the selected subset?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reconciled approach in #88 and read CLAUDE.md rule 5, then inspect src/core/graph-selection.js, src/ui/graph-surface.js, and the existing interaction path in src/ui/explain-graph.js. Compare the relevant graph-layout, schema-detail, styles.css, and test entry points before deciding how the selection and interaction surfaces fit together. Done means the listed selection, group movement, layout, persistence, undo/redo, and coverage-gate acceptance criteria pass without a new runtime dependency.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100