Altinity / Altinity/altinity-sql-browser
Roadmap to 1.0.0
Nessuno ha ancora preso questa issue.
- Lingua principale
- TypeScript
- Stelle
- 8
- Fork
- 2
- Merge medio
- 1h 34m
- PR unite (30g)
- 6
Descrizione
Meta/tracking issue + living build plan. Architecture is settled (#88 / ADR-0001); the phases below are ordered so each refactor lands on a stable base and the most-wanted features ship early. Check items off as they land.
Architecture (settled — #88, ADR-0001)
- State:
@preact/signals-core, migrated slice-by-slice. No UI framework (React/Preact/Solid). A Preact spike on the schema panel (spike/preact-schema, ADR-0001 addendum) confirmed a component model removes the in-place-mutation pain but buys a second render paradigm the roadmap doesn't justify. - Shape: signals (state) · pure logic in
src/core/· imperative adapters behind injected seams for the hard / third-party / high-frequency-pointer surfaces (editor, EXPLAIN + schema graphs, Chart.js, result grid). Extract a shared primitive (EditorPort,GraphSurface, a result-view registry,Drawer) on the second consumer — not speculatively (CLAUDE.md rule 5). - Types: ADR-0002 (2026-07) — incremental strict TypeScript, dev-time only:
tsc --noEmitjoins the gate, files convert leaf-up one at a time (seams →state.js→core/→net/→editor/→ui/), persisted-data types generated from the canonical JSON Schemas; esbuild and the artifact untouched, runtime deps stay four. Phases 0–5 landed via #262/PR #265 (gate, generated schema types, state, contract spine, param pipeline, dashboard runtime + tests); the remainder converts leaf-up with feature work. The no-framework line was re-evaluated at the dashboard milestone and stands (ADR-0001 2026-07 addendum, with a sharpened concrete re-evaluation trigger).
Build order
Phase 0 — baseline before refactors (cheap, do first)
- #69 — add WebKit/Safari to Playwright e2e — done (#90, merged)
- #70 — de-risk the
html{zoom}dependency — fix landed (PR #92). Manual Safari testing showed the only real divergence is viewport units underzoom(Chromium'svh/vwignore it, Safari's track it), which mis-sized the fullscreen graph panels on Safari; the divisor is now measured at runtime and published as--vp-zoom, plus the@supports not (zoom:1)1× fallback. (The pointer/caret/drag math self-calibrates, so it was already correct on Safari — the originally-planned "mis-scaling banner" was dropped as a false-positive.) Finding: Playwright WebKit ≠ real Safari forzoom×rect/viewport, so #69's CI "Safari verified" didn't exercise this — folded into #71. Fullzoomremoval still folds into Phase 4/5.
Phase 1 — finish reactivity (#88)
- tabs / activeTabId · sidePanel
- resultView / running · libraryName / libraryDirty — merged (#89)
- #91 — schema / schemaFilter → signals (replaced Set/Map, not in-place mutation); the schema panel stays imperative
Phase 2 — near-term wins
- #86
- #83
- #87 — streaming Export, uncapped, via File System Access API — merged (PR #103)
- #99 — script export (multi-statement follow-up to #87): directory picker, sequential per-statement files, live log pane — PR #104 open
- #134 — query variables: detect
{name:Type}while editing, show an input strip, gate Run until filled, and substitute via ClickHouse's nativeparam_<name>args (server-side, type-correct); reads only, soCREATE VIEWdefinitions stay verbatim. Values shared by name + persisted (asb:varValues). Pure detection/classification insrc/core/query-params.js; no new dep. Distinct from #39 ({{name}}composable-query CTE-merge — different syntax and purpose). — merged (#138); follow-up filter/variable work now tracked in Phase 7
Phase 3 — Windows
- #100 — detached-tab primitive (
src/ui/detached-view.js) extracted from the schema graph's tab/overlay logic; now shared by the schema graph, the EXPLAIN pipeline graph (gains real-tab support), and a new Data Pane Expand (snapshot grid, sort/copy, in a tab or overlay).app.state.detachedView(a count) tracks open views. — PR open - #101 — cell-detail drawer horizontal resize (drag handle, splitters.js 'drawer' axis) — PR open
- #102 — UI consistency polish: disclosure chevrons, toast dismiss, popover autofocus, open-state tracking — PR open
Phase 4 — editor: CodeMirror 6 + the features it unlocks
Strict sequence — each issue is the foundation of the next: #143 → #21 → #84 → #60.
- #143 —
EditorPortseam: every consumer moves onto an injected port; the current textarea editor becomes its first adapter. No behavior change — makes the CM6 swap reversible. - #21 — adopt CM6 behind the port (the deliberate 4th runtime dep; bundle delta measured: +406 KB raw / +134 KB gzip, approved at the plan gate): ClickHouse-dialect highlighting, per-tab undo, completion/hover parity v0 from
core/completions.js; textarea adapter + supersededui/editor-*/core/editor-*modules deleted. — PR #145 open; manual real-Safari pass still owed post-merge (#70 lesson). - #84 — schema-aware autocomplete on CM6: pure
core/from-scope.js(FROM/alias resolution) + debounced idle-tick column loading feeding the completion source (never on the keystroke path). — PR #146 open (alias→table resolution, statement-scoped unqualified columns, 300ms idle-tick column prefetch;core/from-scope.js+core/completions.jsat 100%). - #60 — version-exact reference docs (umbrella: #313 function docs / #314 formats+engines+types / #315
system.documentation+safe Markdown): capability-gated per-source probing (no blanket 26.6 check;system.documentationalone version-short-circuited), CM6 hover/completion cards + F1 + accessible disambiguation, persistent non-modal reference pane, schema-surface engine/type actions, bounded pure Markdown parser reusing markdown-lite's inline layer. SharedDrawerchrome extracted from the.cd-*scaffold (cell detail + rows viewer + docs pane = 3 consumers; schema-detail keeps its own geometry per the umbrella non-goal). Editor layer now an enforced leaf (check-boundaries: src/editor must not import src/ui). — PR #320 open; live-verified on otel (26.6.1). - #213 — shared injected read-only CodeMirror viewer: text/JSON/SQL/XML + HTML-source/plain-Markdown modes, line numbers/search/wrapping, detached-document realm, idempotent teardown; common presentation only, editable EditorPort behavior unchanged. Foundation for #214. — PR #215 open
Phase 5 — schema / data-flow graph
- extract
GraphSurface(src/ui/graph-surface.js) +src/core/graph-selection.js(pure) — shared by the schema graph and, where possible, the EXPLAIN pipeline graph - #66 — multi-select + group-move (marquee / Shift-click / ⌘A, align/distribute, single-step undo) on
GraphSurface - #42 — cap EXPLAIN AST fan-out on large schemas (pure
core/)
Phase 6 — Dashboard (#149)
Epic: open favorited Library queries as an interactive dashboard in a new tab
(/sql/dashboard). Spec + pinned decisions + sub-phases D1–D8 live in #149. Same architecture
(signals + hyperscript, no framework — reaffirmed against the React mockup); one artifact,
client-side route; auth via one-time postMessage handoff + login fallback. Builds on #134
({name:Type} params / varValues) and the chart core.
- D1 — #150 (merged)
- D2 — #151 (merged)
- D3 — #152 (PR #156 merged)
- #167
- #166 — Panels delivered: visualization registry + Panel result view + saved
panel
field. Panels are visual only (chart/table/logs/text; KPI follows separately). - #211
- #212 — PR #218 open
- #220 — canonical Draft 2020-12 Spec schema + shared validation/tooling service — PR #222 open
- #221 — schema-driven Spec editor autocomplete with native SQL-consistent popup UX — PR #226 open
- D6 — #160 — PR #232 open: query-backed dashboard Filter source, strict single-select MVP.
Adds saved-query Dashboard use (Panel | Filter source), option-query execution,
persisted-selection reconciliation, and fallback diagnostics. No cascading/cache/multiselect. -
#175 — dashboard Setup sources— deferred 2026-07-17 (no urgency): conflicts with
#280's "no Setup execution in Dashboard v1" pin; needs a rewrite against the
DashboardDocumentV1model before reviving.role: "setup"stays reserved in the Spec. -
#188 — filter-bar clear actions, active count, impact hints, and overflow— closed
2026-07-17, dissolved: clear one/all + active count + never-hide-blocking absorbed into
#286 as baseline filter-bar requirements; impact hints and overflow dropped
(deferred-until-demonstrated-need — trivial post-#280 viatargets). - #189 — searchable query-backed multiselect with Apply — PR #364 (merge gate):
runtime-inferred single/multiselect from the agreed consumer type
(Array(T)→ multiselect) with optionalselection.modeoverride,
dedicated draft-until-Apply checklist control, arrays end-to-end through
the typed pipeline/persistence, targets-aware affected-panel planning,
and array-aware option-refresh reconciliation. - D5 — #154 — PR #231 open
- #185 — interactive detached Data view: Expand opens a re-runnable full-screen
surface (Panel switcher + {name:Type} filter row) bound to the result's captured
source; shared filter bar (src/ui/filter-bar.js) + shared streaming exec seam
(app.runReadInto) + sharedrenderResultView. merged (PR #194). - #198 — follow-up (bug): detached rerun is now commit-on-success — streaming chunks
update a lightweight progress status only; the previous committed Table/JSON/Panel
stays visible until success (no metadata-only "0 rows" flash, no per-chunk chart churn),
matching the dashboard's progress-only policy. PR open (#204). - #193 — follow-up: migrate dashboard tiles onto the shared
app.runReadIntostreaming
seam (readonly:2 guard, CAP+1/CAP row-cap split, footer stats, 6-way concurrency both
waves, generation+abort reserved at wave creation, explicit-FORMAT rejection). merged (PR #199). - #240 — Dashboard KPI bands: explicit favorited KPI queries render as a
full-width flat card stream (spanning every layout mode) instead of a
nested KPI grid inside a generic tile; auto-detected KPI results remain
ordinary tiles. ExtractedrenderKpiCards()(kpi-panel.js) shared by the
workbench preview and the newsrc/ui/dashboard-kpi-band.jsmodule. merged (PR #247). - #248 — compact mobile Dashboard presentation at the canonical 768px
breakpoint: one-line icon-first header, responsive one-column layout
override, horizontally scrolling filter strip, and no empty mobile toolbar. PR #250 open. - #252 — compact Line/Area style presets with deterministic dense-series point
visibility, renderer-independent persistence, and shared workbench/Dashboard rendering. merged (PR #253). - #254 — chart field metadata now drives display names, tooltip/axis units
and decimals, descriptions, hidden measures, and NULL gaps through the
shared workbench/Dashboard renderer. merged (PR #255). - #256 — seven complete Line/Area presentation presets now cover curve,
points, scale, legend, grid, and axes through the compact Style selector;
unmatched advanced Spec combinations remain visible as Custom. merged (PR #259). - #258 — the same single Style selector now carries type-specific Bar,
Column, Area, and Pie/Donut presets in one type-specificpanel.cfg.styleobject,
with shared workbench/Dashboard rendering. PR #260 open.
Saved-query model: panel controls visualization; optional dashboard.role controls
dashboard participation (panel | filter | setup). Missing role defaults to panel.
Favorite state controls inclusion and is independent of role.
Design detail lives in #149 and the linked issues — this list tracks order + status only.
(History: this checklist once had D3/D4 swapped relative to #149; #149 is the source of truth
for phase order.)
Phase 7 — filters & variables
Reconciled by #447 (2026-07-26). This phase's framing predates the removal of
the curated-filter/provider model. The query-backed Filter source (#160) and
everything layered on it — #359's shared-source runtime, #360's parameterized
sources, #189's multiselect — are removed, not merely superseded. The surviving
concept is a Dashboard VARIABLE inferred from panel SQL; #165's optional blocks,
#170/#171's validation and recents, and the shared{name:Type}/
state.varValuesmachinery all remain and are what variables are built on. Read
the rows below as history for anything filter-source-shaped.Amended by #468 (2026-07-26, shipped). One piece came back: #189's searchable
multiselect CONTROL, now driven by anArray(scalar T)variable's own
Dashboard-local option SQL rather than by a Filter-role source query. Nothing
else from the curated model returned — no provider, no shared source, no target
list, no persisted selection mode. #447's "Multiselect or Array-valued variable
controls" non-goal is annotated accordingly.
One track: every surface shares the{name:Type}/state.varValuesmachinery, and every
prerequisite is already merged (#134, #152/D3, #84) — the whole phase is implementable now,
independent of the Panels track (the query-backed Filter source #160 is dashboard-only and moved
to Phase 6 with its #166 prerequisite; the workbench keeps free text inputs). Build order: #173
first (7.0 foundation — everything else is a pipeline stage or consumer, absorbs #155; its
typed serializer is what #189 will need for multiselect), then #165 → #170 → #169 → #171 /
#172-v1 → #172-v2; #174 (combobox a11y primitive) is normative from the first dropdown
consumer; its former dashboard clear/overflow/multiselect scope now lives in #286 (clear/count
baseline; #188 dissolved) and #189 (multiselect). Design
detail lives in the issues — this list tracks order + status only.
- #173 — PR open (phase7 branch): two-phase pipeline + typed serializer, absorbs #155; all gate/exec call sites migrated
- #165 — PR open (phase7 branch):
/*[ … ]*/optional blocks +state.filterActive, both surfaces - #170 — PR open (phase7 branch): typed validation (grammar sized by live-server smoke checks; ranges deliberately stricter than the server's silent wraparound)
- #169 — PR open (phase7 branch): relative time + preset dropdown (first combobox consumer, built to #174 §1)
- #171 — PR open (phase7 branch): per-variable recents (MRU 10, global-LRU cap), recorded from
boundParamsper successful statement - #172 — PR open (phase7 branch): enum dropdowns, v1 + v2 both shipped (declared-type parsing incl. implicit auto-numbered members; schema-cache inference, suggestion-only)
- #174 — accessible shared combobox primitive delivered and consumed by relative-time,
recent-value, and Enum fields. Former dashboard clear/overflow/multiselect scope moved
to #188 and #189 (2026-07-17: #188 since dissolved into #286; overflow dropped).
Deferred until demonstrated need: dependent/cascading query-backed option sources, option
dependency graph, and cache policy. File a focused issue only when a real dashboard requires it.
Also revisit on demand (each needs its own small design): filters: one-query-many-params
bundle (was a #149/#160 non-goal) — now the shared Filter-source track: #359 establishes the one-source-runtime boundary (source runs once per wave, provider keyed by source-query id, diagnostics published, options cleared-on-failure + active reconciled), and #360 adds parameterized sources + single-layer dependency scheduling on top (shipped — PR #362, merge gate); paired from/to time-range control shipped as #335 — PR #376 (merge gate): compound Time-section control with staged From/To popover on the shared popover primitive extracted from the #189 multiselect, atomic applyFilters batch commit (one wave), and one wallNow() snapshot per wave published as waveWallNowMs; #334 — PR #390 (merge gate) replaces #335's interim name-pair inference with authoritative saved-query timeRanges metadata, derives independent groups on Dashboard load, and adds synchronized exact-time crosshairs plus atomic mouse-brush range selection across temporal line/area/column charts; explicit SQL
NULL filter options (#165 "SQL NULL" notes).
Phase 8 — Dashboard as a first-class module (#280)
Epic: retire the favorites-derived dashboard in favor of an explicit persisted
DashboardDocumentV1 inside an atomic StoredWorkspaceV1 aggregate (one current workspace,
zero-or-one Dashboard), PortableBundleV1 interchange, presentation variants via JSON Merge
Patch, a normative flow@1 layout, and separate authoring/viewer sessions. Normative spec in
#280; six sequential sub-issues. Starts after #276 completes (phase 4's viewer session builds
on #276 3b DashboardSession); #283 is pure core/schema work and may start in parallel with the
#276 tail. Absorbs and closed: #235 (wave scheduling → #286), #153 (reorder/span → #286,
open-in-window → #288).
-
#283 — phase 1: contracts, codecs, canonical encoding, resource limits (decided:
additivequery-spec-v1revision for variants/size-hints;maxDecodedJsonBytesstays
10 MiB, IndexedDB-backed phase 2) — shipped (phase-8 PR) -
#284 — phase 2: atomic
StoredWorkspaceV1persistence + one-shot legacy migration
(decided: IndexedDB aggregate store behind an injected seam;spec.favoritedual-write
removal path documented) — shipped (phase-8 PR) -
#285 — phase 3: authoring domain — atomic fallible commands, JSON Merge Patch presentation
resolution, whole-workspace validation on every saved-query mutation — shipped (phase-8 PR) -
#286 — phase 4:
DashboardViewerSession+ normativeflow@1(closes #235/#153-reorder;
derived KPI bands). Owner UX override at ship time: tile reorder is pointer-drag-only (redesigned in #332: grip-drag with no modifier or ⌘/Ctrl body-drag + live grafana-grid reflow, ≥2/3-overlap commit; plain body drags select text; read-only never reorders) and
span/height are tuned in the Spec editor (the keyboard-reorder / in-tile sizing controls from
#280's accessibility section were removed by owner decision — a11y-revisitable follow-up;
partly reversed by #535 (2026-07-29): a single in-tile WIDEN button is back on the tile
head, stepping the widths the active style has — the free-form two-dimensional resize stays
the #291 corner drag, and the keyboard-reorder controls stay removed. #537 (2026-07-29)
then cut that head back to two controls: widen keeps its inline button, and duplicate,
expand and remove became rows of a⋯overflow menu — the inline widen additionally
withdrawing below ~260px of tile through a CSS container query, with its menu row remaining). The
migrated filter bar restores the rich relative-time/recent/enum fields (regression fix) over
the viewer's filter model. — shipped (phase-8 PR). Reversed by #294 (2026-07-18): the
visible Clear-all filter button/toolbar wrapping was undone — filters now scroll horizontally
in a fixed-height row at every width (never wrap) with no visible Clear-all control;
clearAllFilters()stays as a tested application-level operation with no UI trigger. -
#287 — phase 5: portable open/import/export, transactional import planner, Append UX
replaced by Import queries / Import Dashboard / Replace workspace + Export Dashboard/workspace
— PR open 2026-07-18. Re-scoped by owner decision: theStoredWorkspaceV1
aggregate became the saved-query source of truth (strict async CRUD replaces the
flatasb:savedpath), broader than the issue's written acceptance; the
external-bundle trust preflight + transport were moved to #288 (Phase 5 file
ops only commit to the workspace, never execute). Follow-ups filed: #299
(Workbench star → Dashboard tile wiring), #300 (corrupt-aggregate surfacing),
#303 (Dashboard filter values lost on reload — regression from the #280
rewrite). Being shipped together 2026-07-18 (one PR): #300 + #303 fully
closed; #299 partial — the star→tile wiring lands, thespec.favorite
dual-write retirement stays deferred. -
#288 — phase 6: bookmarkable workspace route + one-time IndexedDB session-bundle handoff
(closes #153-open-in-window) — shipped 2026-07-18 (PR #306, with #302 File-menu restructure; ADR-0003).
Now also carries the external-bundle trust preflight ("Run Dashboard on " confirm,
never-auto-execute, validate-before-execute, row/byte caps, bounded concurrency) moved here
from #287, since the untrusted-external-view execution path lands with this phase's transport. -
#291 — post-#280 follow-on:
grafana-grid@1, a rowless Grafana-style 12-column tile-grid
layout engine next to the normativeflow@1(deterministic packing from tiles[] order,
per-tile span 1–12 + semantic heights, container-width column clamp, Workbench-only
drag-reorder/corner-resize/delete with immediate propagation, auto-regenerated valid
flow@1fallback on every grid mutation; no rows/folding, no Save/Undo — owner decisions
2026-07-18). Registration-time-lazy in the layout registry (single-file artifact keeps
hard rule 4; no network splitting). — PR open 2026-07-18. -
#341 — post-#280 bug: make editable Dashboard + Workbench two editors over
ONE committedStoredWorkspaceV1. Dashboard commands (move/resize/layout/
delete) now run through the sharedapp.serializeWritequeue (all workspace
writes share one order), build each candidate inside the queued op from the
freshest committed baseline (monotonic revisions), project the committed
result viaapp.applyCommittedWorkspace, and roll back deterministically on
failure; both exportsflushWorkspaceWrites()→loadCurrent()so the
Dashboard doc + shared queries are canonically identical across bundles.
Same-tab serialization only. — Merged (PR #344). Cross-tab consistency
shipped as #343 (PR #377): refresh-before-write through one mandatory
mutateWorkspacepath, BroadcastChannel + focus/visibility invalidation,
linked-tab adopt/conflict/orphan reconciliation, and editable-Dashboard
rebuild — revision CAS explicitly OUT of scope per #343's non-goals
(no successor issue; simultaneous-commit protection stays unplanned). -
#370 — Dashboard tile removal now treats
dashboard.tiles[]as canonical panel-query membership: trash removes one tile, scrubs explicit filter targets, preserves the star while another instance remains, mirrorsspec.favorite, and commits one normalized revision; legacy/imported favorite flags never auto-create tiles. — PR #380 (merge gate).
Superseded for panel-owned queries by #537 (2026-07-29): once #427 made every tile the
sole owner of a dedicated saved-query copy, "removes one tile and leaves the query" left that
copy with zero owners — i.e. projected into Library. The tile header now commits the atomic
tile+query removal the Dashboards tree already used, and refuses outright for a shared,
missing, ambiguous, retargeted or non-panel query. The membership normalisation and
spec.favoritemirroring #370 defined are unchanged; they run inside that transform.
That trash was also grid-only (a CSS scope plus an engine gate), so Report and the two
column presets had no delete at all until #537 made it a menu row. -
#340 — KPI query tiles now share a frameless presentation across every Dashboard style: flow KPI-band members and transparent Grid Tiles/Full-view wrappers move with Command/Ctrl-drag through the shared floating-preview, destination, auto-scroll, cancellation, and exact-once commit path; Grid KPI edit chrome is transient and non-layout-shifting, with keyboard-operable resize. — PR #388 at merge gate.
Phase 9 — multi-workspace and unified workspace routing
Strict sequence: #406 persistence foundation → #407 canonical unified route → #408 selector/management UI → #409 stale-write protection. The repository phase deliberately retains last-write-wins for simultaneous cross-tab commits; #409 owns conflict detection.
- #406 — multi-workspace persistence:
StoredWorkspaceV2separates immutable opaqueid, immutable human URLkey, and mutablename; IndexedDB stores one validated record per workspace with a unique key index, collection list/create/replace/delete operations, separate last-used preference plus injected-clocklastOpenedAtfallback, additive workspace import, and stable-key Dashboard links. PR #410 at merge gate. - #407 — unified
/sql?ws=...&surface=...&mode=...routing for Workbench and Dashboard surfaces. - #408 — workspace selector/management UI and File-menu simplification.
- #409 — cross-tab conflict detection and stale-write protection.
Phase 10 — multi-Dashboard workspace + Dashboard/Library UX (#430)
Epic: #430 turns the workspace's single Dashboard into a real multi-Dashboard
product surface — selection state, a Databases/Dashboards tree, Library queries
separated from Dashboard-owned copies, copy-on-drag assignment, and tree
editing. #430 specifies ONE continuous pass and ONE final PR closing #424–#429;
#424 was shipped separately by owner request (it is pure storage and has no UI
change, so it stands alone safely). The remaining phases still belong together.
-
#424 —
StoredWorkspaceV3: the stored workspace holds an ordered
dashboards[]collection instead of zero-or-one Dashboard. Manifest-compiled
v3 schema, a pure deterministic V2 to V3 migration at the decode boundary
(v2 stays readable; every write is v3; a legacy record upgrades on its next
ordinary commit), per-Dashboard semantic validation against the shared query
collection withdashboards[i]...paths, workspace-unique Dashboard ids with
Dashboard-local tile/filter/layout ids, and the collection bounded by the
shared portable capacity. One centralized compatibility selector
(src/workspace/workspace-dashboards.ts) keeps the UI on exactly one
Dashboard, so favourite-driven membership and every route are behaviourally
unchanged; the Dashboard route commits BY Dashboard id. Export carries every
stored Dashboard and Import workspace ingests every bundled Dashboard (its
single-Dashboard picker retired). Owner decisions at plan time: import-all
over the picker, and lazy record upgrade (no write on the read path). -
#427 — separate Library queries from Dashboard-owned query copies (this is
what finally retires #424's temporary favourite/membership coupling) -
#425 — Dashboard selection state + full-size Dashboard work surface: one
persistent shell owns#root(header slot, sidebar, mobile nav) with the
query column and the Dashboard as sibling HOSTS, exactly one exposed — so a
Dashboard owns the complete editor-plus-results area while the sidebar stays
visible, and the Query surface survives a round trip by identity (editor
contents/selection/scroll, active tab, result view, editor/results split)
rather than being rebuilt.openDashboard({dashboardId, mode, focus})is the
one ID-addressed entry point (missing/duplicate ids report through
diagnostics and change nothing);showQuerySurface/openSavedQuery
centralise the way back, and every existing query-opening path returns to
Query mode. Selection is SESSION state inapplication/main-surface.ts—
never persisted, cleared on sign-out, re-validated against every committed
workspace (a deleted or ambiguous selection falls back to Query mode, never
to another Dashboard) — and the single writer of the route's surface/mode,
with URLs unchanged. New surface toolbar: Back to query · title · View|Edit.
Focus lands on one tile (by TILE id) or one curated filter (by filter id)
with a bounded navigation highlight, at the deterministic point each node
exists. Fixes it exposed: a surface switch no longer issuesKILL QUERYvia
workbench.destroy(), a same-workspace Back no longer inerts the mounted
Query controls, sign-out now disposes the Dashboard and advances the
renderer generation, and Export/Import Dashboard address the SELECTED entry.
The favourite star is gated while a non-first Dashboard is selected
(#434 retires that with #427). ADR-0003 addendum records the amended
surface lifecycle. PR #436 at merge gate. -
#426 — Databases/Dashboards switcher + read-only Dashboard hierarchy tree:
an equal-role tab row over TWO PERSISTENT hosts in the upper-left pane, so a
role switch only hides a host and the schema search text/focus, expansion,
lazily-loaded columns and scroll survive by construction (as do the sidebar
width and the upper/lower splitter). Every stored Dashboard is reachable as
Dashboard -> Filters -> Panelsin collection order, with panel counts
(filters excluded), search across all six documented fields with
presentation-time forced expansion that never writes the user's expansion
sets, a full ARIA tree (roving tabindex, Up/Down/Left/Right/Home/End/Enter/
Shift+Enter) and a keyboard-reachable trailing action menu for every
pointer-only operation. Gestures are mutually exclusive through a pure
injected-timer click ARBITER (core/tree-click-arbiter.ts) rather than the
schema tree's post-hoc detector — a panel row's query-open is deferred for
the double-click window and cancelled outright by the second click, so no
Query-surface flash and no duplicate history entry; the chevron stays the
instant expansion path. (Revised by #429 phase 2: the Dashboard row no
longer arbitrates at all — its name opens View, Shift-click opens Edit, and its
chevron became a real disclosure BUTTON. The arbiter and the deferred
query-open survive on PANEL rows only.) Repeated member navigation inside an
open Dashboard is delivered IN PLACE through the existing generation-stamped
SurfaceCommandPort(focusMember(): ok | pending | missing) — no rebuild,
no re-run, no history entry — andMainSurfaceStatesplits #425's one-shot
focusinto retainedcurrentMember(tree styling, preserved across a
View/Edit switch) plus consumed-oncependingFocus. Strictly read-only: it
never clones, repairs or rewrites a reference, renders a diagnostic row for a
broken one while keeping Dashboard navigation available, and treats a curated
filter with no option source as the TRANSITIONAL state it is before #427 —
not an error. Header cleanup: the obsoleteSQL Browser | Dashboardpair and
a vestigial never-attached Dashboard-nav seam are gone, and Back to query
returns to the compact toolbar (#437 had dropped it because the header pair
covered it — with the pair gone a phone would otherwise have no route back).
Owner decision at ship time: the mobile segment is relabelled Explore,
since that pane now holds both roles. PR open. -
#428 — Library assignment to Dashboards. Drag path shipped (#484);
keyboard chooser shipped (#516).
One Library drag publishes two payloads: the existingSUBQUERY_MIMEtext
the editor inserts (PR #40, untouched) and a newLIBRARY_QUERY_MIME
identity{workspaceId, queryId}that only Dashboard targets read and
re-resolve insidemutateWorkspace. Dropping on a Dashboard row or its
Panels group mints an independent panel-owned copy plus a tile (via
applyCommand('add-query-instance'), so placement follows the source's size
hints); dropping on an INFERRED Variables row copies SQL text only. An
orphaned variable and the Variables group itself both reject. The
dirty-variable-tab gate runs inside the write transaction, not before it.
Acceptance bullet 9 — the keyboardAdd to dashboard…chooser — is
implemented by #483 / PR #516 through the same application command;
mobile is out of scope for the drag gesture entirely. -
#429 — Dashboard-tree management and click operations. Rescoped
2026-07-27: it now ABSORBS #472, #443, #449, #431, #476 and #481 (all six
closed) and is the single owner of the Dashboard row's gesture split, the
Dashboard-document create/rename path, and panel-row edit/delete. The
curated-filter half is gone: #447 phase 1 shipped orphan-config delete + the
per-variable SQL editor, and deleting an ACTIVE inferred variable is a #447
non-goal. Six phases, one PR each offmain— see the ship log on #429 for
live status. Phase 1 merged (#490):openSavedQueryresolves before
it navigates and reports a miss (#443), and the Dashboard viewer trims an
authoredtile.titlebefore the fallback so a whitespace-only title never
reaches a heading or an accessible name (#476). Phase 2 merged (#492):
the Dashboard row is three independent targets (#472) — its name opens View
and Shift-click opens Edit, both immediately; expansion belongs to a real
chevron BUTTON with its ownaria-expanded, Expand/Collapse accessible name,
Enter/Space and focus ring; the trailing⋯does neither. #426's delayed
single/double arbitration is gone from Dashboard rows (panel rows keep it
unchanged), and the row's now-redundant Open in View menu item went with it
while Open in Edit stayed, being reachable only through hidden Shift
modifiers. #438 gates the focus-tile gestures in phases 4/5 on the flow engine
only — phase 2 adds no tile-focus gesture. Phase 3 merged (#495): exactly one command
creates a Dashboard now — File ▸ New dashboard… and the empty-workspace
placeholder both prompt and commit throughappendDashboard, never the
compatibility slot (#481) — and each Dashboard row's new pencil edits
title/description on a dialog prefilled from the committed document.
Phases 4 and 5 were re-cut by #494, which supersedes the old
panel-edit / panel-delete split and folds the⋯-menu removal in with
them. Phase 4 in review (#505): no production ROW renders an overflow
menu any more — a Dashboard row carries a pencil and a trash, a Panel row
a pencil that edits its dedicated query's metadata and a trash that removes
the tile and that query atomically, all proven against the #427
exactly-one-owner rule and confirmed before they destroy anything. Phase 5
is #494's remainder — the Open-in-Dashboard focus button alone — and stays
blocked on #438, since that button is a known no-op on flow-layout KPI
tiles until #438 lands. #494's row-level conclusion is untouched by #537
(2026-07-29), which gave the Dashboard TILE HEAD a⋯: that is a
width-constrained head carrying five controls beside a title, not a
two-control row in a fixed-width side pane. See the ADR-0003 addendum. -
#447 — inferred Dashboard Variables, replacing the curated filter model.
Supersedes the curated-filter/provider direction and closes #450. Phase 1
merged (#458): the whole option-provider layer removed (filter-role
queries, provider discovery by output-column name, duplicate-provider
arbitration, shared-source dedup, arrays/multiselect, per-filter targets), and
variables now INFERRED from{name:Type}in panel-owned SQL, matched exactly
and case-sensitively, with optional Dashboard-local option SQL under
variableConfigs. stored-workspace v5 / Dashboard document v2 /
portable bundle v2, legacy records migrating on read; afilter-role
record is rejected rather than migrated. #335/#334 survive, re-based onto
variables. Phase 2 merged (#462): typed direct-input controls, the
option-backed single-select, and oneUNION ALLrequest per refresh reading
option rows POSITIONALLY (tupleElement(tuple(*), n)— a name-keyed read
collapsesSELECT environment, environment). Phase 3 = the
example rewrite,variableConfigsimport/export coverage, docs and E2E —
unblocked: #428's PR #484 discharges global bullet 12 (Library SQL onto
a variable), so assignment is no longer phase 3's to build.
See the issue's ship-log comment for the per-phase handoff. -
#457 — the variable drawer replaced by a main-editor tab. Clicking a
Variables row opens that variable's option SQL as a dedicated
Variable: <name>tab, identified by(dashboardId, variableName), reusing
the real editor / tab strip / toolbar / Run / results instead of the
second SQL editing surface #447 phase 1 built. Deletes
ui/variable-editor.ts, theVariableEditorseam, the textarea fallback,
runOptionQueryand the drawer-local Test. AddsQueryTab.doc, a
document-kind Save dispatch, and resets variable tabs on a workspace switch
(a Dashboard id is unique per workspace, not globally). Merged (#467).
Follow-ups: #465 (re-host Test's shape check on the tab's Run — done,
restorescompileOptionProbe/isOptionColumnType/validateOptionColumns
and wires them into a newWorkbenchSession.runVariableSql, ahead of the
run()/runScript()split so a multi-statement variable query can never
reach the script runner unvalidated), #496 (reject the probe's 1001st
sentinel row before History/source success bookkeeping — in review
(#534)), #466 (no close-guard for any dirty tab — still open). -
#471 — a per-tile
Open in Workbenchaction replaces the Dashboard-level
< Querybutton. Every query-backed tile opens its OWN query document in
the main editor, in both View and Edit mode; re-opening selects the tab it
already has. No new tab-identity model was needed: #427 already gives each
tile a dedicated owned copy, so itsqueryIdIS the stable per-Dashboard
origin, andloadIntoNewTab/commitSavedQueryalready resolve by id — #464
is left owning only the VISIBLE collision badges and origin tooltips.
Consequences: a Grid-Tiles KPI tile's overlay head now reveals in View mode,
and the mobile bottom nav stops hiding itself on the Dashboard surface
(showing only Editor), because #426's phone route died with the button and
a tile-less Dashboard has no per-tile action. Also fixed the hole that removal
exposed: Back out of a tile used to rebuild the workspace's FIRST Dashboard at
the top of the page, so each Dashboard history entry now carries its own id,
current member and scroll offset inhistory.state(never the URL — #425's
contract stands). Follow-up: #475, rescoped — flow's KPI band members DO get the
action now (anchored inside the card, the reach-through.is-nav-targetalready
uses); what remains there is delete/grip/resize/title chrome. Merged (#477). -
#464 — disambiguate duplicate query names in editor tabs by stable
origin. Narrowed by #471, which shipped the identity half: tab reuse and
Save targeting are already by document id, never by name. What remains is
presentation — compact source badges on the COLLIDING tabs only, minimally
unique Dashboard abbreviations, and fullDashboard / querycontext in the
tooltip and accessible name. In review (#539). -
#487 — a drag-foldable desktop left navigation with a rail and focused
drawers. The vertical frame border becomes the only fold control: dragging
the two-pane sidebar narrow enough converts it to a 48px icon rail, and a
rail icon opens its section as a DOCKED full-height drawer that pushes the
work surface rather than floating over it. No chevron/hamburger/pin buttons.
Shipping in four phases (structure recorded in the issue body; the
<!-- ship-log -->comment is the state of record):
1 — pure layout core (core/left-nav-layout.ts: thresholds, the hysteresis
mode reducer, keyboard resolver, ARIA range) plus the two new browser
preferences, inert (SHIPPED, #571);
2 — navigation section registry, so one set of section hosts serves both the
wide sidebar and the drawer and no second search/expansion/scroll store
exists (SHIPPED, #573);
3 — the rail, the docked focused drawer and therole="separator"resize
control — the feature turns on,openFocusedSection()lands as the
deterministic seam #428's drag-hover path needs, and the centre-width clamp
lands with it rather than after it (a safety constraint must not trail the
feature it protects)..sidebaris re-presented viadata-nav-moderather
than a separate drawer module, and a dedicated separator module replaces
splitters.ts's old axis outright (IN REVIEW, #574);
4 — centre reflow through the existing resize observers, verification that
the clamp holds alongside the right inspector, the #428 hover launcher, e2e
and a real-browser threshold pass.
Cross-cutting
-
#512 — authentication lifecycle and document-session preservation: Phases 1–3 merged in #523, #524, and #527.
-
#276 — refactor
app.tsinto explicit application services and route sessions
(modular monolith; sequential PRs, one per phase — plan + considered API
deviations recorded in the issue's first comment):- Phases 0–1 —
check:archboundary guard +QueryExecutionService
(app.exec.executeRead/executeScript/kill;app.runReadIntodeleted) — PR open - Phase 2 —
ConnectionSession(auth/config/chCtx lifecycle;app.conn,
rendering inverted viaonAuthLost; scalar auth fields left theApp
contract) — PR open - Phase 3 — route-scoped sessions (split into two PRs by plan review):
- 3a —
WorkbenchSession(app.workbench: run/script/cancel orchestration,
private RunState + AbortController,attachShelleffects,destroy();
boundary guard generalized to a rule list) — PR open - 3b —
DashboardSession(tile/filter wave runtime, per-slot cancellation,
DashboardRuntimeInput, filter-bar dispose seam,destroy())
- 3a —
- Phase 4 — supporting services (grouping in the issue's Phase-4 comment;
five sub-PRs after plan review split 4B):- 4A —
SchemaCatalogService(app.catalog; writable refData/completions
accessors preserved for the CM6 harness;invalidate()for Phase 5) — PR open - 4B1 —
WorkbenchParameterSession(app.params) - 4B2 —
ExportService+ExportSink(app.exports) - 4C —
QueryDocumentSession+SavedQueryService(app.queryDoc/app.saved;
sessionParamsForstays app.ts-local — Phase 5 decides its home) - 4D —
SchemaGraphSession+AppPreferences(app.graph/app.prefs)
— 4B1–4D shipped as ONE combined PR (user decision) — PR open
- 4A —
- Phase 5 — composition cleanup: temporary adapters deleted (one documented
survivor:app.saveVarRecent),ui/workbench/workbench-shell.tsextracted,
dashboard shell narrowed, session teardown wired into signOut (behavior
fix),docs/ARCHITECTURE.mdrewritten — PR open (closes #276)
- Phases 0–1 —
-
#262 — ADR-0002 phases 0–5 (scope grew from phase 0 by maintainer decision — one PR instead of per-phase issues): the TypeScript gate, generated persisted-data types (
build/emit-schema-types.mjs→src/generated/json-schema.types.ts; hand-rolled emitter, see ADR-0002 addendum), and strict conversion of 16 modules —state.ts, the saved-query/panel/dashboard contract spine, the param/filter/execution pipeline, and the dashboard runtime — plus their unit tests. PR #265. Build/artifact untouched; runtime deps stay four. Remaining.js(app.js glue, net/, editor adapters, rest of core/ui) still converts leaf-up folded into normal feature work — inventory, test-helper debt (fake-appAppstub), and scaffolding retirement tracked in #267. -
full
html{zoom}removal — the 1.2× scale went away first (--zoom: 1, PR #147), then the dead zoom-bridging scaffolding itself was torn out (#148):html{zoom}/--zoom/--vp-zoomare gone from CSS,zoomScale()/viewportZoom()/app.applyViewportZoomare deleted,fixedAnchor/splitter/grid-column-resize/schema-detail/cell-drawer/Chart.js all operate on native coordinates with no scale argument. One coordinate system (native browser CSS px) app-wide now. Still wants aGraphSurface#66 / real-Safari manual pass for confidence, but that's no longer a special architectural gate — it's ordinary release QA.
Release-blockers (1.0 must not ship without)
- #70 (
html{zoom}de-risk) ✅ fix via #92 (Safari panel sizing, manually verified) · #71 (supported browsers / ClickHouse versions / IdP matrix) — #69 (WebKit e2e) ✅ done via #90
\n\n## Dashboard layout sizing correction (#549)\n\nDraft PR #549 follows up on #538 with the corrected #535 behavior: grafana-grid@2 persists independent Grid, Full, and Report sizing; 2/3 Columns are session-only 300px previews; and Widen changes width only in Grid or a temporary preview. Full and Report have fixed authored widths and no Widen action.\n
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Questa è una roadmap di monitoraggio, non una singola attività di implementazione, e non indica un singolo punto di ingresso né un singolo test. Inizia scegliendo un elemento della fase non spuntato, quindi leggi l’issue referenziata da quell’elemento e qualsiasi ADR o file elencato, come src/ui/graph-surface.js e src/core/graph-selection.js. È completato quando l’elemento selezionato soddisfa i propri criteri dell’issue ed è spuntato qui.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- clickhouse, typescript
- Ambito
- full-stack
- Tipo di issue
- Funzionalità
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Tranquilla
- Chiarezza
- Da chiarire
- Idoneità per principianti
- 25/100