feat(editor): consistent schema gestures, SQL formatter, undo-friendly inserts - #3
Merged
Conversation
…serts
Schema tree (consistent click model; was: column single-click inserted, so a
double-click pasted twice):
- single-click = expand (db/table) / nothing (column)
- double-click = insert — db: name (cursor); table: `SELECT * FROM db.t LIMIT 100`
(prepended as a top line, not replacing); column: name (cursor)
- shift-click = db: `SHOW CREATE DATABASE db`; table: `SHOW CREATE db.t`
(ClickHouse SHOW CREATE handles table/MV/dictionary); column: `col::type`
(top-line for the statements, cursor for identifiers)
Editor:
- programmatic edits now go through execCommand('insertText') (with a manual-
splice fallback), so drag/double-click/format inserts join the native undo
stack — ⌘Z / ⌘⇧Z work for them, not just typing. New `insertTopLine` +
`replaceEditor` helpers; Tab-insert routed through the same path.
Format query: new ⌘⇧F → `SELECT formatQuery(<editor sql>)` on the server,
replaces the editor with the result (errors surface via a toast).
Shortcuts: drop ⌘T/⌘W (let the browser keep new-tab/close-tab; the +/× buttons
remain); add Format, Undo, Redo to the modal list.
Verified in-browser: every gesture inserts the right text; execCommand('undo')
reverts a programmatic insert; shortcuts modal shows the new list. 346 tests
pass; editor/schema/shortcuts at 100%, app.js within gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
⌘⇧F / Ctrl+Shift+F collides with browser find/Lens muscle memory (Ctrl+Shift+F is Google Lens "search page" on Chrome Win/Linux). Rebind Format to ⌘/Ctrl+Shift+Enter — it pairs with Run (⌘/Ctrl+Enter), and Shift+Enter has no browser default on macOS, Windows, or Linux. The Enter handler now branches on Shift (format when signed in, else run). Shortcuts modal updated to ⌘⇧↵. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
The shortcuts modal now has a "Schema tree" section listing the click / double-click / shift-click gestures (expand, insert, insert DDL/col::type), reusing the existing row+chip styling under a small divider+subheading — so the gestures are discoverable next to the keyboard shortcuts. Terse by design; the per-row tooltips still carry the detail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
…Query)
Shift-click on a table/db now fetches the real DDL via SHOW CREATE and pretty-
prints it through formatQuery(), inserting the formatted CREATE statement as a
top line — instead of inserting the literal "SHOW CREATE …" command. Two server
round-trips by design (show + format); if formatting fails it falls back to the
raw DDL, and a SHOW CREATE failure surfaces via a toast. New `insertCreate`
action; schema shift-click handlers call it ('db.table' for tables,
'DATABASE db' for databases).
352 tests pass; schema 100%, app.js within gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
A share link carries the SQL in the URL hash (#<base64>). When the recipient wasn't signed in, the OAuth redirect to the IdP dropped the hash (redirect_uri is path-only and fragments don't round-trip), and the in-memory tab was wiped by the full-page navigation — so they landed on a blank /sql after signing in. bootstrap now stashes the decoded query in sessionStorage (`oauth_shared_sql`), which survives the same-tab redirect like oauth_state/verifier, and restores it when the hash is gone, clearing it once consumed on a signed-in render. Verified in-browser: not-signed-in load stashes the query + shows login; the post-redirect load (no hash, signed in) restores it into the editor and clears the stash. 353 tests pass; main.js at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 18, 2026
…d layout (#308) * feat(#291): grafana-grid@1 schema + pure layout plugin + registry registration (wave 1) - schemas/dashboard-layout-grafana-grid-v1.schema.json: rowless 12-col engine, items {span 1..12, height compact|medium|large}, no preset; manifest entry + regenerated types/validators (fallback slot stays pinned to flow@1) - src/dashboard/layouts/grafana-grid-layout.ts: DashboardLayoutPlugin + computeGrafanaGridLayout (deterministic row-major packing, responsive column clamp 12/6/4/2), deriveGrafanaGridPlacement, flow<->grid span conversion, deriveFlowFallback (validated flow@1) - workspace-semantics: isSupportedLayout generalized to the engine map; new narrow isFlowLayout for fallback/flow-only call sites so a grafana-grid doc can never be accepted as a flow fallback; semantics validation now validates a non-flow primary against its own schema AND still requires a flow@1 fallback - defaultLayoutRegistry registers grafana-grid@1 behind a lazy load() - tests: grafana-grid-layout (100/100/100/100), layout-registry, workspace- semantics, schema-build extended Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * feat(#291): engine-aware dashboard authoring commands (wave 2) - change-layout switches engines: flow->grid seeds items from flow placements (span 1->4, 2->6, 3->12; height carried) and snapshots the flow layout as fallback; grid->flow restores the fallback (optionally applying a preset); missing fallback -> dashboard-command-layout-fallback-missing diagnostic - update-placement validates/writes through the ACTIVE engine plugin (grid span 1..12, flow 1..3) via new resolveLayoutPluginSync - add-time seeding under grid uses deriveGrafanaGridPlacement (always explicit) - regenerateGridFallback shared primitive: every tile/placement mutation under grid (commands, tile-membership, saved-query-mutation) deterministically regenerates a valid flow@1 fallback - UI never manages fallback itself - tests: commands engine-switch matrix, fallback regen per mutating command, per-engine placement bounds, membership/mutation grid cases Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * feat(#291): rowless grafana-grid UI - viewer wiring, grid render, edit interactions (wave 3) - dashboard-viewer-session: layout routed through the registry-resolved active plugin; DashboardViewState.layout becomes a discriminated union ({engine:'flow', ...FlowLayoutModel} | {engine:'grafana-grid', grid}); containerWidth dep feeds the responsive column clamp (12/6/4/2) - src/ui/dashboard.ts: single-CSS-grid reconciliation path (.dash-gg-grid, 8px gap, span + semantic-height classes 118/210/296px), KPI tiles placed like any tile; layout select extended to 4 flow presets + Grafana grid driving engine-switching change-layout; edit mode (!readOnly): whole-card drag-reorder (move-tile), corner-drag resize with pure snap math -> one update-placement per drag, tile delete (remove-tile); immediate propagation - no Save/Undo (owner decision) - fixed pre-existing runCommand bug: plugin was hardcoded to flow for validation + normalize; now resolves the active engine (post-command layout for normalize, so engine switches normalize through the result) - grafana-grid-layout: GRID_GAP_PX/GRID_HEIGHT_PX + snapGridSpan/ snapGridHeight pure resize math (100% covered) - styles.css: grid host/tile/edit-affordance chrome, both themes Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * test(#291): real-browser e2e harness for the grafana-grid layout (wave 3b) Narrow static harness (dashboard-mobile.html pattern, no createApp): packing positions vs the pure model, semantic height px, container-width column clamp 12/6/4/2, no horizontal overflow at 360px, corner-drag resize live preview + single terminal dispatch, hover-revealed chrome + view-mode absence. 6/6 green on chromium and webkit (firefox full-parallel goto flake is pre-existing; spec passes 6/6 at --workers=1). Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * fix(#291): review round - real-browser layout fixes + engine-dispatch cleanup Verified findings from the 8-angle review + adversarial verification: - remove the 768px !important grid-template override that fought the JS container-width clamp (span>1 tiles rendered on implicit phantom tracks in the 470-768px band; reproduced in real chromium) - measure the grid's CONTENT-box width (clientWidth minus computed padding) for both breakpoint tiers and resize column math (pure contentBoxWidth) - corner-drag resize pins the tile to its explicit colStart for the drag (span clamped to remaining columns) so a mid-row tile can never self-wrap and desync the persisted placement from the pointer - renderDashboard removes the previously-installed window resize listener (reloadDashboardRoute re-entry stacked handlers + pinned stale sessions) - bare same-engine change-layout (no items) preserves existing placements instead of silently wiping them via the wholesale branch - one BUILTIN_SYNC_PLUGINS table now feeds both resolveLayoutPluginSync and defaultLayoutRegistry (single registration point for engine #3) - delete dead resolveActiveLayoutPlugin (stale semantics, zero prod callers); drop redundant GrafanaGridLayoutModel.order; regenerateGridFallback owns the type guard + tile-ref mapping (3 call sites collapsed, no flow-path alloc); setPlacementForActiveEngine dedupes the per-engine write branch - e2e harness updated to the pinned-resize contract + new mid-row drag test Gates: npm test 3657 passed, build OK, check:arch OK, full e2e chromium+webkit 104 passed. Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * chore(#291): CHANGELOG entry + reconcile roadmap #68 and issue body Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * test(#291): fix firefox CI flake in the mid-row resize-clamp e2e Firefox does not deliver real mouse moves ~100000px outside the viewport, so the extreme rightward drag never reached the wiring and the span stayed at 1. Dispatch that single extreme pointermove synthetically on window (where the resize wiring listens); real mouse input still drives down/pin/up and the terminal dispatch. 21/21 green locally on chromium+firefox+webkit at --workers=1. Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz * feat(#291): tile heights become numeric row units 1-16 (~5x taller max) Owner override on the served build: the corner-drag vertical range was capped at the 3-tier 296px max. grafana-grid item height is now an integer 1..16 in row units (px = 32 + 88*units; units 1/2/3 land on the legacy tiers, unit 16 = 1440px ~5x the old cap). The schema accepts the legacy compact|medium|large strings (anyOf) and normalize() upgrades them to 1/2/3; flow fallback maps units 1->compact, 2->medium, >=3->large; the resize snap is the exact inverse of the px formula (per-unit stops). Height tier classes replaced by a computed inline height; e2e heights scenario now covers units 1/2/3/10 (120/208/296/912px). Gates: npm test 3673 passed, build OK, check:arch OK, grid+mobile e2e 30 passed chromium+webkit, grid spec 7 passed firefox --workers=1. Part of #291. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLMprUnwnz4oaz3cnLRKcz --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 tasks
BorisTyshkevich
added a commit
that referenced
this pull request
Aug 5, 2026
… retire dashboardTreeRevision app.currentWorkspace/app.mainSurface become signal-backed accessor pairs (peeking getter, notifying setter) so a mutation is its own notification — the #426/#427 bug class (a write site forgetting to bump the invalidation counter) becomes structurally impossible. app.committedWorkspace (ReadonlySignal<StoredWorkspaceV5|null>) and app.treeNavigation (a computed structural key over kind/dashboardId/currentMember) are the two tracked reads app-shell.ts's tab-count/tree/Library effects subscribe through, replacing state.dashboardTreeRevision. app.currentWorkspace's setter is asymmetric (no null): a transitional null publication is a named departure operation owned by a new closure-private surface-retirement coordinator in app.ts (retireToWorkspaceLoading/ -Missing/-Failure/-Login), which batches the publication atomically with disposing any live shell so a surface never repaints against transitional state — five independent review passes each found a different call site where this raced disposal, hence one coordinator with exclusive mutation authority rather than five patches. SurfaceStatePort/DashboardApp/TabsApp/ DashboardTreeApp are narrowed to readonly on currentWorkspace (the fourth, DashboardTreeApp, was documented read-only but not type-readonly before this change). app.reloadDashboardRoute() (a post-commit fold-and-reassign that would double-publish once the aggregate is signal-backed) is deleted; afterLibraryChange's Dashboard branch calls the render-only app.renderCurrentSurface() instead. Tests: tests/unit/surface-lifecycle-arch.test.ts (a static-source scan backing the coordinator's compile/scan-layered "no lifecycle bypass" claim) and tests/unit/surface-accessor-contracts.test.ts (@ts-expect-error fixtures for the asymmetric setter and the four narrowed ports) are new, plus a new app.test.ts describe block covering the issue's Tests #1/#3/#5 and the plan's invariant map (delivery-only no-ops, adversarial-id collision-freedom, failure-path status/null ordering, the four-arm live-shell no-repaint sweep, one-commit-exactly-once settlement, and the mixed-snapshot batch-ordering regression). fake-app.ts's makeApp() installs real per-call signals on the returned object (object spread evaluates an accessor pair into a plain value, so the fake needs the same defineProperty treatment createApp() gets natively) for reactivity parity in fixtures. No persisted/schema change, no user-visible behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A batch of editor/schema UX improvements.
Schema tree — consistent click model
Previously column single-click inserted the name, so a double-click pasted it twice (while tables inserted once on double-click). Now uniform:
db(cursor)SHOW CREATE DATABASE db(top line)SELECT * FROM db.table LIMIT 100(top line)SHOW CREATE db.table(top line)col(cursor)col::type(cursor)SHOW CREATEis used withoutTABLEso it covers tables/MVs/dictionaries.Editor — real undo/redo for programmatic inserts
Programmatic edits (drag, double-click insert, format, Tab) now go through
execCommand('insertText')with a manual-splice fallback, so they land on the native undo stack — ⌘Z / ⌘⇧Z work for them, not just for typing. NewinsertTopLine/replaceEditorhelpers.Format query (⌘⇧F)
Runs
SELECT formatQuery(<editor SQL>)on the server and replaces the editor with the formatted output; failures surface via a toast.Shortcuts
Verification
In-browser (Chrome) against the built bundle: each gesture inserts the right text;
execCommand('undo')reverts a programmatic insert (undo works); shortcuts modal shows the new list.npm test→ 346 pass; editor/schema/shortcuts 100%, app.js within its gate.🤖 Generated with Claude Code
https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef