Altinity / Altinity/altinity-sql-browser

Roadmap to 1.0.0

Aperta
#68 5 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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 --noEmit joins the gate, files convert leaf-up one at a time (seams → state.jscore/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 under zoom (Chromium's vh/vw ignore 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 for zoom×rect/viewport, so #69's CI "Safari verified" didn't exercise this — folded into #71. Full zoom removal 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 native param_<name> args (server-side, type-correct); reads only, so CREATE VIEW definitions stay verbatim. Values shared by name + persisted (asb:varValues). Pure detection/classification in src/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 — EditorPort seam: 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 + superseded ui/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.js at 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.documentation alone 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. Shared Drawer chrome 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 sourcesdeferred 2026-07-17 (no urgency): conflicts with
    #280's "no Setup execution in Dashboard v1" pin; needs a rewrite against the
    DashboardDocumentV1 model before reviving. role: "setup" stays reserved in the Spec.
  • #188 — filter-bar clear actions, active count, impact hints, and overflowclosed
    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 via targets).
  • #189 — searchable query-backed multiselect with Apply — PR #364 (merge gate):
    runtime-inferred single/multiselect from the agreed consumer type
    (Array(T) → multiselect) with optional selection.mode override,
    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) + shared renderResultView. 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.runReadInto streaming
    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. Extracted renderKpiCards() (kpi-panel.js) shared by the
    workbench preview and the new src/ui/dashboard-kpi-band.js module. 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-specific panel.cfg.style object,
    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.varValues machinery 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 an Array(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.varValues machinery, 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 boundParams per 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 #335PR #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:
    additive query-spec-v1 revision for variants/size-hints; maxDecodedJsonBytes stays
    10 MiB, IndexedDB-backed phase 2) — shipped (phase-8 PR)

  • #284 — phase 2: atomic StoredWorkspaceV1 persistence + one-shot legacy migration
    (decided: IndexedDB aggregate store behind an injected seam; spec.favorite dual-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 + normative flow@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: the StoredWorkspaceV1
    aggregate became the saved-query source of truth (strict async CRUD replaces the
    flat asb:saved path), 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, the spec.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 normative flow@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@1 fallback 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 committed StoredWorkspaceV1. Dashboard commands (move/resize/layout/
    delete) now run through the shared app.serializeWrite queue (all workspace
    writes share one order), build each candidate inside the queued op from the
    freshest committed baseline (monotonic revisions), project the committed
    result via app.applyCommittedWorkspace, and roll back deterministically on
    failure; both exports flushWorkspaceWrites()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
    mutateWorkspace path, 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, mirrors spec.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.favorite mirroring #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: StoredWorkspaceV2 separates immutable opaque id, immutable human URL key, and mutable name; IndexedDB stores one validated record per workspace with a unique key index, collection list/create/replace/delete operations, separate last-used preference plus injected-clock lastOpenedAt fallback, 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 with dashboards[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 in application/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 issues KILL QUERY via
    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 -> Panels in 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 — and MainSurfaceState splits #425's one-shot
    focus into retained currentMember (tree styling, preserved across a
    View/Edit switch) plus consumed-once pendingFocus. 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 obsolete SQL Browser | Dashboard pair 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 existing SUBQUERY_MIME text
    the editor inserts (PR #40, untouched) and a new LIBRARY_QUERY_MIME
    identity {workspaceId, queryId} that only Dashboard targets read and
    re-resolve inside mutateWorkspace. 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 keyboard Add 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 off main — see the ship log on #429 for
    live status. Phase 1 merged (#490): openSavedQuery resolves before
    it navigates and reports a miss (#443), and the Dashboard viewer trims an
    authored tile.title before 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 own aria-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 through appendDashboard, 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; a filter-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 one UNION ALL request per refresh reading
    option rows POSITIONALLY (tupleElement(tuple(*), n) — a name-keyed read
    collapses SELECT environment, environment). Phase 3 = the
    example rewrite, variableConfigs import/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, the VariableEditor seam, the textarea fallback,
    runOptionQuery and the drawer-local Test. Adds QueryTab.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,
    restores compileOptionProbe/isOptionColumnType/validateOptionColumns
    and wires them into a new WorkbenchSession.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 Workbench action replaces the Dashboard-level
    < Query button.
    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 its queryId IS the stable per-Dashboard
    origin, and loadIntoNewTab/commitSavedQuery already 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 in history.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-target already
    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 full Dashboard / query context 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 the role="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). .sidebar is re-presented via data-nav-mode rather
    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.ts into 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:arch boundary guard + QueryExecutionService
      (app.exec.executeRead/executeScript/kill; app.runReadInto deleted) — PR open
    • Phase 2 — ConnectionSession (auth/config/chCtx lifecycle; app.conn,
      rendering inverted via onAuthLost; scalar auth fields left the App
      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, attachShell effects, 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())
    • 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;
        sessionParamsFor stays 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
    • Phase 5 — composition cleanup: temporary adapters deleted (one documented
      survivor: app.saveVarRecent), ui/workbench/workbench-shell.ts extracted,
      dashboard shell narrowed, session teardown wired into signOut (behavior
      fix), docs/ARCHITECTURE.md rewritten — PR open (closes #276)
  • #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.mjssrc/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-app App stub), 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-zoom are gone from CSS, zoomScale()/viewportZoom()/app.applyViewportZoom are 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 a GraphSurface #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

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. 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

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.