Skip to content

feat(editor): drag a Library/History query into the editor as a ( … ) subquery - #40

Merged
BorisTyshkevich merged 2 commits into
mainfrom
feat/drag-subquery
Jun 25, 2026
Merged

feat(editor): drag a Library/History query into the editor as a ( … ) subquery#40
BorisTyshkevich merged 2 commits into
mainfrom
feat/drag-subquery

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What

Saved-query (Library) and History rows are now draggable. Dropping one onto the SQL editor inserts that query's SQL wrapped in parentheses as a subquery, at the drop position, undoably. No runtime macro, no library coupling — the SQL you see is the SQL you run (the simplest take on the composable-queries idea in #39).

How

Extends the existing schema-identifier drag path rather than inventing a new one:

  • src/ui/editor.js — adds SUBQUERY_MIME next to IDENT_MIME, and a branch in the existing textarea drop handler. The schema-identifier branch is unchanged (still inserts at the caret); the new branch wraps via toSubquery and inserts at the drop point using the existing offsetAt (client coords → text offset, handles padding/scroll/zoom) + replaceRange (undoable), falling back to the caret when the drop lands past the text (e.g. the blank area below the last line).
  • src/core/format.js — pure toSubquery(sql): trims, strips a trailing ; and a trailing FORMAT <name> clause (invalid inside ()), brackets the SQL on its own lines; empty input → '' (no-op).
  • src/ui/saved-history.js.saved-row / .history-row get draggable + a dragstart that puts the SQL on SUBQUERY_MIME (mirrors schema.js's dragProps).

Tests

  • format.js 100% (toSubquery wrap / strip ; / strip trailing FORMAT / keep non-trailing FORMAT / empty).
  • editor.js — drop with SUBQUERY_MIME: inserts at the drop offset (position branch), fallback to caret when past the text, and no-op on empty SQL.
  • saved-history.js — saved + history rows are draggable and carry the right SUBQUERY_MIME payload on dragstart.
  • e2e (editor-insert.spec.js) — real DataTransfer + DragEvent('drop') over the textarea exercises the actual offsetFromXY geometry happy-dom can't.

820 unit tests pass; coverage gate holds.

Verified live (otel)

Dragging a Library row onto the editor dropped (\nSELECT * FROM claude_otel.otel_logs\n) at the drop position with the surrounding text preserved; dropping in the editor padding fell back to the caret. Deployed to otel for review.

🤖 Generated with Claude Code

BorisTyshkevich and others added 2 commits June 25, 2026 18:23
… subquery

Saved-query and history rows are now draggable; dropping one on the SQL editor
inserts that query wrapped in parentheses as a subquery, at the drop position,
undoably. Extends the existing schema-identifier drag path (IDENT_MIME) with a
second MIME (SUBQUERY_MIME) and a position-aware branch in the drop handler
(offsetFromXY → replaceRange, falling back to the caret when dropped past the
text). A pure toSubquery() strips a trailing FORMAT/; and brackets the SQL.

- core/format.js: toSubquery() (100% covered).
- ui/editor.js: SUBQUERY_MIME + drop handler branch.
- ui/saved-history.js: draggable rows + dragstart payload.
- unit + e2e (real DragEvent/DataTransfer) coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
… follow-ups)

- toSubquery: peel trailing `;`/`FORMAT` iteratively (any order, repeated/spaced),
  so a `FORMAT JSON;;` or `;`-then-FORMAT tail can't leave an unparseable subquery.
  (A trailing comment after FORMAT is still left as-is — degrades to a visible SQL
  error, no silent note-dropping.)
- README: document drag-to-insert (schema identifier at caret; Library/History
  query as a ( … ) subquery at the drop point).
- saved-history.js: move dragProps below the imports (readability).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
@BorisTyshkevich
BorisTyshkevich merged commit bb95968 into main Jun 25, 2026
4 checks passed
@BorisTyshkevich
BorisTyshkevich deleted the feat/drag-subquery branch June 25, 2026 16:42
lesandie pushed a commit to lesandie/altinity-sql-browser that referenced this pull request Aug 2, 2026
One drag from the Library list now has three destination-dependent meanings.
The row publishes two independent `dataTransfer` payloads to serve them: the
existing `SUBQUERY_MIME` SQL snapshot (PR Altinity#40, untouched) and a new
`LIBRARY_QUERY_MIME` identity `{workspaceId, queryId}`. The editor consumes
text; Dashboard destinations consume identity.

- Dashboard row / Panels group -> an independent panel-owned copy plus a tile,
  via `applyCommand('add-query-instance')` so placement is seeded from the
  source's own size hints. Repeated drops create independent panels.
- an inferred Variables row -> the SQL text only, into that variable's option
  config. No clone, no tile, no owner, no lineage.

Dashboard assignment never trusts `dataTransfer`: the payload is re-resolved
against the latest committed workspace inside `mutateWorkspace`, so a source
deleted or turned Dashboard-owned mid-drag aborts, a drop after a workspace
switch is rejected, and an ordinary same-workspace change rebases (Altinity#343).

Three decisions worth the diff they cost, all recorded in the ADR-0003 addendum:

- The dirty-variable-tab gate runs INSIDE the transform. A pre-dispatch check
  is a snapshot, not a gate — `mutateWorkspace` queues behind `serializeWrite`
  then awaits IndexedDB, and a keystroke in that window would slip past. A
  variable tab has `savedId === null`, so Altinity#343's reconciler skips it and
  nothing would ever report the divergence.
- Drag feedback causes no repaint. Eligibility is static `data-droptarget`
  markup revealed by one class on the list; a repaint would `replaceChildren()`
  the row under the pointer, and `dragleave` never fires for a removed node.
- Hover auto-expand is decoupled from eligibility, or a variable row (which
  needs its non-eligible group open first) could never be reached by drag.

`reconcileVariableTab` is new: `openVariableTab` calls `showQuerySurface()`,
and a successful drop must not leave the Dashboard.

Acceptance bullets 1-8; bullet 9 (keyboard `Add to dashboard...`) is deferred
to Altinity#483 by owner decision, along with mobile, which is out of scope for the
gesture. Unblocks Altinity#447 phase 3.

Part of Altinity#428

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011sMwR4BHM85MExSBYXzTYj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant