You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Drag path shipped in PR #484 (merged 2026-07-27). The only unmet acceptance
bullet is 9 — the keyboard-accessible Add to dashboard… command — which was
deferred to #483 by owner decision the same day. So there is no remaining work
tracked in this issue: it stays open only as the umbrella for that deferred
bullet, and closes when #483 lands (or immediately, if the bullet is considered #483's to own).
Assignment is therefore pointer-only on main today. That is the accessibility
gap #483 exists to close, and the reason bullet 9 was not silently dropped.
This issue owns one item of#447 phase 3 — the drag/drop of Library SQL onto
Variables rows. It does not own that phase. Corrected 2026-07-27; the original
wording ("owns #447 phase 3") over-claimed and would have read as though merging #484 completed the phase.
#447 phase 3's other six items stay with #447: the paste/copy workflow in the
variable editor, rewritten shipped examples on inferred variables, import/export
coverage for variableConfigs, docs and E2E coverage, the final dead-code audit for
removed filter terminology, and reconciling or closing superseded #450.
#465 is not a prerequisite. It restores the two-String-column shape check on an individual variable tab's Run action; assignment here only copies SQL and preserves the current validation/runtime behaviour.
The old curated-filter/provider design no longer exists. There are no filter-role queries, persisted filter objects, source-query IDs, target lists, or filter configuration dialogs.
Goal
A Library query drag has three destination-dependent meanings:
Library query -> Dashboard row / Panels group -> create an independent panel-owned query copy and tile
Library query -> existing Variables row -> copy its SQL text into that Dashboard variable's option SQL
Library query -> main SQL editor -> insert the existing `( … )` subquery text at the drop position
The third path is already implemented by PR #40 and must remain behaviourally unchanged. This issue extends the same Library drag source with stable identity for Dashboard assignment without breaking the existing editor drop.
Drag payload and source rules
Only rows in the lower Library list may be assigned to Dashboards. A Library query has zero Dashboard owners according to buildQueryOwnershipIndex.
History rows remain draggable only for the existing editor-subquery insertion and are never Dashboard-assignment sources.
A Library drag must publish two independent payloads:
the existing SUBQUERY_MIME SQL snapshot used by the SQL editor;
a new internal identity payload used only by Dashboard/Variables targets.
Dashboard assignment must never trust SQL or a complete saved-query object from dataTransfer;
Dashboard assignment re-resolves the query by ID from the latest committed workspace inside mutateWorkspace;
editor insertion continues to use the SQL snapshot and toSubquery(...) exactly as today;
initiating a drag must not open the Library query;
normal click, star, rename, delete, and existing editor drop behaviour remain usable;
workspace switch, missing source, or a source that is no longer a Library query cancels safely.
Do not cancel merely because the same workspace changed after drag start. Re-read the latest workspace and apply the semantic operation when the source and target still exist.
Supported destinations
Panel assignment
Accept on:
Dashboard row -> panel assignment
Panels group -> panel assignment
Both destinations have the same persistence semantics.
Variable option-SQL assignment
Accept on an individual inferred Variables row:
Variables
country : String <- valid destination
The Variables group itself is not a destination because it does not identify which variable receives the SQL.
An orphaned configuration that is no longer inferred is not a valid assignment destination. It can still be edited or deleted through its existing UI.
Existing editor insertion
The main SQL editor remains an accepted destination through SUBQUERY_MIME:
insert at the actual drop position;
wrap with the existing ( … ) subquery formatting;
make the edit undoable;
perform no workspace mutation.
Rejected destinations
Do not accept Dashboard assignment on:
an individual panel row;
the Variables group row;
an orphan-only variable row;
unrelated tree rows or schema objects;
Dashboard tiles/canvas content;
History rows as sources;
Dashboard-owned query rows as sources.
Moving/copying members between Dashboards and tree reordering remain out of scope.
Drag feedback
During a Library drag:
highlight eligible Dashboard rows, Panels groups, and inferred Variables rows;
show a distinct active-target state on dragenter/dragover;
do not present the Variables group itself as eligible;
clear all assignment drag state on drop, cancellation, Escape, workspace switch, or drag end;
preserve the editor's existing drop feedback;
auto-expand a collapsed Dashboard after a short bounded hover delay so Panels and Variables rows become reachable;
cancel the hover timer when leaving;
auto-expansion is UI state only and must not navigate to the Dashboard or mutate workspace content;
dropping must not trigger Dashboard-name navigation, row click actions, disclosure clicks, or trailing action menus.
Panel assignment
Dropping on a Dashboard row or Panels group performs one atomic mutation over the latest StoredWorkspaceV5.
verify the active/latest workspace has the payload's exact workspaceId;
resolve the source query by exact ID;
verify it still has zero Dashboard owners;
resolve exactly one target Dashboard by stable ID;
mint collision-free query and tile IDs against the latest document;
clone with the shipped cloneQueryForDashboardOwner({ source, newId }) helper;
append the clone to workspace.queries[];
append { id: newTileId, queryId: newQueryId } to the target Dashboard's tiles[];
do not create redundant tile title/description overrides;
normalise the target Dashboard through the active layout implementation and regenerate fallback layout data where required;
increment only the target Dashboard revision, exactly once;
preserve every non-target Dashboard and the source Library query unchanged;
validate the complete workspace candidate;
commit through app.mutateWorkspace(...).
The source remains in Library and is never modified, including its favourite state.
Repeated drops are allowed. Every successful drop creates a new independent query ID and tile ID, even when the same source is dropped repeatedly into one Dashboard.
After success:
repaint Library and Dashboard tree projections;
expand the target Dashboard and Panels group;
focus/select the new panel row;
do not automatically open Dashboard View/Edit or execute the query.
Owner revision (2026-07-27). The drop DOES now open the new panel's owned
query copy in the SQL editor, and focuses the new row in the tree. Landing on an
unchanged-looking Dashboard read as "did anything happen?". The Dashboard itself
is still never opened in View/Edit and nothing is executed.
Variable option-SQL assignment
Dropping on an inferred Variables row copies only the source query's SQL text into the target Dashboard's variableConfigs[variableName] entry.
Required semantics:
identify the target by exact (dashboardId, variableName);
re-derive the target variable from the latest target Dashboard's panel queries;
require that the exact variable name is still inferred at commit time;
create no saved-query clone, tile, role, source ID, owner, target list, or provider mapping;
leave the Library query byte-for-byte/canonical-value unchanged;
later edits to the Library query do not affect the copied variable SQL;
increment only the target Dashboard revision, exactly once;
commit through the existing withVariableConfig / Dashboard-variable-config mutation path or an equivalent id-addressed transform over mutateWorkspace.
A blank/whitespace-only Library query is not a deletion gesture: reject the drop as a no-op with a clear message. Removing variable option SQL remains the explicit blank-Save/delete behaviour of the variable editor.
Copy the SQL as authored. The existing local option-SQL rules remain authoritative:
parameterised/cascading SQL is stored but diagnosed as unsupported;
non-embeddable or otherwise locally invalid SQL is stored for correction but excluded from option execution;
A drop must never silently overwrite an unsaved variable-tab draft.
no open tab: commit normally;
matching clean variable tab: commit and reconcile it to the committed SQL;
matching dirty variable tab: reject the assignment, keep the draft unchanged, and focus/select that tab with a clear message.
The variable tab is identified by (dashboardId, variableName), as shipped by #457.
After a successful variable assignment:
repaint the Variables row/control state;
keep the Dashboard tree expansion state;
focus/select the target variable row;
do not automatically switch to the Query surface or run the option SQL.
Owner revision (2026-07-27). The drop DOES now open that variable's Variable: <name> tab, so the assigned option SQL is there to edit and run. The
option SQL is still not executed automatically.
Commit-window addendum (2026-07-27). The dirty-tab rule below is enforced
inside the mutateWorkspace transform, which closes the queue-and-load window.
It cannot close the window during workspace.commit(...) itself — the write is
already durable by then. That case is REPORTED rather than hidden: the outcome
carries draftDiverged, and the drop shows a non-dismissing toast offering Discard draft. Refusing to adopt while reporting a clean success was the
failure mode this rule exists to prevent.
Existing editor-subquery path
The existing PR #40 behaviour is part of this issue's integration contract, not new implementation scope.
Adding the identity payload must not change:
Library and History rows inserting into the main SQL editor;
SUBQUERY_MIME handling;
trailing semicolon/FORMAT cleanup performed by toSubquery;
drop-position insertion;
undo/redo behaviour;
editor-only operation with no persistence side effects.
The same Library drag may therefore be interpreted differently by different targets: the editor consumes SQL text; Dashboard destinations consume stable identity.
Concurrency and stale-state rules
Use the current read-before-write model from #343:
enter mutateWorkspace queue
-> load latest committed workspace
-> re-resolve source and target
-> apply one semantic transform
-> validate whole candidate
-> atomically commit
-> project committed truth
Do not introduce or claim repository compare-and-swap. Simultaneous same-instant multi-tab commits remain outside the repository contract.
Abort without mutation when:
the active workspace ID differs from the payload;
the source query was deleted or became Dashboard-owned;
the target Dashboard disappeared or has duplicate IDs;
the target variable is no longer inferred;
generated IDs cannot be made collision-free;
validation or persistence fails;
a dirty target variable tab would be overwritten.
Never commit an orphan panel query without its tile, a tile without its query, or a partial variable update.
Keyboard-accessible alternative
DEFERRED to #483 (owner decision, 2026-07-27). This section is the spec for
that issue; PR #484 implements the drag path only. Mobile is also out of scope
for the whole gesture, so no isMobile branches were added — touch drag is
already a non-goal below.
Each Library row must expose an equivalent command:
Add to dashboard…
The chooser contains:
Dashboard selection;
destination kind: Panel or Variable;
for Variable, one of that Dashboard's currently inferred exact variable names.
The chooser calls the same application commands as drag/drop and produces identical data.
Do not recreate the removed curated-filter configuration form. There is no parameter picker, target-panel picker, default picker, selection-mode picker, or provider mapping.
The existing editor-subquery drag remains separate from this assignment chooser.
Suggested boundaries
Pure/application mutation layer
copyLibraryQueryToPanel
copyLibraryQuerySqlToVariable
Both operate over an explicit latest workspace and return a complete candidate or a typed abort.
Library drag adapter
Publishes both MIME payloads, suppresses row opening during drag, and exposes the keyboard command.
Dashboard-tree drop controller
Owns eligible targets, hover expansion, visual state, payload decoding, and command dispatch. It never mutates workspace documents directly.
Existing SQL editor adapter
Remains unchanged except for regression coverage proving the second MIME payload does not interfere with SUBQUERY_MIME.
Tests
Payload/source
Library rows publish both identity and subquery MIME payloads;
History rows publish only the existing subquery payload;
Status
Drag path shipped in PR #484 (merged 2026-07-27). The only unmet acceptance
bullet is 9 — the keyboard-accessible
Add to dashboard…command — which wasdeferred to #483 by owner decision the same day. So there is no remaining work
tracked in this issue: it stays open only as the umbrella for that deferred
bullet, and closes when #483 lands (or immediately, if the bullet is considered
#483's to own).
Assignment is therefore pointer-only on
maintoday. That is the accessibilitygap #483 exists to close, and the reason bullet 9 was not silently dropped.
Required foundations have shipped:
This issue owns one item of #447 phase 3 — the drag/drop of Library SQL onto
Variables rows. It does not own that phase. Corrected 2026-07-27; the original
wording ("owns #447 phase 3") over-claimed and would have read as though merging
#484 completed the phase.
#447 phase 3's other six items stay with #447: the paste/copy workflow in the
variable editor, rewritten shipped examples on inferred variables, import/export
coverage for
variableConfigs, docs and E2E coverage, the final dead-code audit forremoved filter terminology, and reconciling or closing superseded #450.
#465 is not a prerequisite. It restores the two-
String-column shape check on an individual variable tab's Run action; assignment here only copies SQL and preserves the current validation/runtime behaviour.The old curated-filter/provider design no longer exists. There are no filter-role queries, persisted filter objects, source-query IDs, target lists, or filter configuration dialogs.
Goal
A Library query drag has three destination-dependent meanings:
The third path is already implemented by PR #40 and must remain behaviourally unchanged. This issue extends the same Library drag source with stable identity for Dashboard assignment without breaking the existing editor drop.
Drag payload and source rules
Only rows in the lower Library list may be assigned to Dashboards. A Library query has zero Dashboard owners according to
buildQueryOwnershipIndex.History rows remain draggable only for the existing editor-subquery insertion and are never Dashboard-assignment sources.
A Library drag must publish two independent payloads:
SUBQUERY_MIMESQL snapshot used by the SQL editor;Suggested identity payload:
Requirements:
dataTransfer;mutateWorkspace;toSubquery(...)exactly as today;Do not cancel merely because the same workspace changed after drag start. Re-read the latest workspace and apply the semantic operation when the source and target still exist.
Supported destinations
Panel assignment
Accept on:
Both destinations have the same persistence semantics.
Variable option-SQL assignment
Accept on an individual inferred Variables row:
The Variables group itself is not a destination because it does not identify which variable receives the SQL.
An orphaned configuration that is no longer inferred is not a valid assignment destination. It can still be edited or deleted through its existing UI.
Existing editor insertion
The main SQL editor remains an accepted destination through
SUBQUERY_MIME:( … )subquery formatting;Rejected destinations
Do not accept Dashboard assignment on:
Moving/copying members between Dashboards and tree reordering remain out of scope.
Drag feedback
During a Library drag:
dragenter/dragover;Panel assignment
Dropping on a Dashboard row or Panels group performs one atomic mutation over the latest
StoredWorkspaceV5.Suggested application command:
Required transformation:
workspaceId;cloneQueryForDashboardOwner({ source, newId })helper;workspace.queries[];{ id: newTileId, queryId: newQueryId }to the target Dashboard'stiles[];app.mutateWorkspace(...).The source remains in Library and is never modified, including its favourite state.
Repeated drops are allowed. Every successful drop creates a new independent query ID and tile ID, even when the same source is dropped repeatedly into one Dashboard.
After success:
Variable option-SQL assignment
Dropping on an inferred Variables row copies only the source query's SQL text into the target Dashboard's
variableConfigs[variableName]entry.Required semantics:
(dashboardId, variableName);withVariableConfig/ Dashboard-variable-config mutation path or an equivalent id-addressed transform overmutateWorkspace.A blank/whitespace-only Library query is not a deletion gesture: reject the drop as a no-op with a clear message. Removing variable option SQL remains the explicit blank-Save/delete behaviour of the variable editor.
Copy the SQL as authored. The existing local option-SQL rules remain authoritative:
No configuration dialog is required.
Open variable-tab interaction
A drop must never silently overwrite an unsaved variable-tab draft.
The variable tab is identified by
(dashboardId, variableName), as shipped by #457.After a successful variable assignment:
Existing editor-subquery path
The existing PR #40 behaviour is part of this issue's integration contract, not new implementation scope.
Adding the identity payload must not change:
SUBQUERY_MIMEhandling;FORMATcleanup performed bytoSubquery;The same Library drag may therefore be interpreted differently by different targets: the editor consumes SQL text; Dashboard destinations consume stable identity.
Concurrency and stale-state rules
Use the current read-before-write model from #343:
Do not introduce or claim repository compare-and-swap. Simultaneous same-instant multi-tab commits remain outside the repository contract.
Abort without mutation when:
Never commit an orphan panel query without its tile, a tile without its query, or a partial variable update.
Keyboard-accessible alternative
Each Library row must expose an equivalent command:
The chooser contains:
The chooser calls the same application commands as drag/drop and produces identical data.
Do not recreate the removed curated-filter configuration form. There is no parameter picker, target-panel picker, default picker, selection-mode picker, or provider mapping.
The existing editor-subquery drag remains separate from this assignment chooser.
Suggested boundaries
Pure/application mutation layer
copyLibraryQueryToPanelcopyLibraryQuerySqlToVariableBoth operate over an explicit latest workspace and return a complete candidate or a typed abort.
Library drag adapter
Publishes both MIME payloads, suppresses row opening during drag, and exposes the keyboard command.
Dashboard-tree drop controller
Owns eligible targets, hover expansion, visual state, payload decoding, and command dispatch. It never mutates workspace documents directly.
Existing SQL editor adapter
Remains unchanged except for regression coverage proving the second MIME payload does not interfere with
SUBQUERY_MIME.Tests
Payload/source
Targeting
Panel mutation
Variable mutation
Existing editor regression
( … )subquery at the drop position;Concurrency/accessibility
Acceptance criteria
Non-goals