Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ auto-generated per-PR notes; this file is the curated, human-readable history.
flashes its chevron shut and back open (`src/ui/schema.js`).

### Added
- **The workbench now has independent SQL and saved-query Spec JSON editor
modes** (#212). A visible `SQL | Spec` switch keeps separate per-tab drafts,
undo/search state, dirty flags, and injected CodeMirror adapters. Spec mode
adds local JSON formatting, folding, search, parse markers with line/column,
and synchronously registered semantic validators keyed by exact path arrays;
known core field types are checked while unknown extension fields remain
valid. Linked Save atomically commits SQL plus the current valid Spec in one
Library write, with normalized Name/Description and all other fields/order
retained; invalid Spec persists nothing. Spec is a lightweight editing mode:
its toolbar contains only Format, Save, and the SQL | Spec switch, while Run,
Explain, SQL Format, Export, Share, and Share’s global shortcut are SQL-only.
Validation remains continuous through diagnostics and status. Library pencil,
favorite, and Panel writers merge their changes into every valid open draft,
preserving unrelated unsaved and extension fields; invalid JSON alone blocks
the writer, focuses the affected Spec tab, and persists nothing. Reopening an
already-open saved query activates its existing tab. No package or lockfile
change was needed because JSON language support landed in #213.
- **A shared, injected read-only CodeMirror source viewer** (#213) now provides
complete-text rendering, line numbers, local search, selection/copy, and
compartment-based wrapping for text, JSON, SQL, XML/HTML source, and plain
Expand Down
17 changes: 10 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ all bundled — see hard rule 4). Quality is held by tests.
globals). Network goes in `src/net/` with the fetch seam *injected*, never
imported. DOM rendering goes in `src/ui/` as functions that take the `app`
controller — except the editor, which lives in `src/editor/` behind the
injected `EditorPort` seam (#143): only `main.js` imports an adapter, and
everything else talks to `app.editor`. Side-effectful environment access
injected editor seams (#143/#212): only `main.js` imports concrete adapters,
and everything else addresses `app.sqlEditor` or `app.specEditor` explicitly.
SQL execution, schema insertion, export, and SQL formatting must never target
whichever document happens to be visible. Side-effectful environment access
(location, crypto, storage, fetch) is injected through `createApp(env)` so
everything is testable.
3. **No secrets in git.** `config.json` (rendered) is gitignored; only
Expand All @@ -29,7 +31,8 @@ all bundled — see hard rule 4). Quality is held by tests.
graph in local, CI, and release builds, and update the lock only with an
intentional dependency change.
There are **four** bundled runtime dependencies — **CodeMirror 6** (the SQL
editor and read-only source viewer, behind injected seams — #21/#213),
editor, saved-query Spec JSON editor, and read-only source viewer, behind
injected seams — #21/#212/#213),
**Chart.js** (the Chart result view), **@dagrejs/dagre** (the EXPLAIN
pipeline-graph layout), and
**@preact/signals-core** (the reactivity primitive — see
Expand All @@ -40,7 +43,7 @@ all bundled — see hard rule 4). Quality is held by tests.
keep the testable logic pure in `src/core/` (chart axis/role/pivot math in
`src/core/chart-data.js`; DOT→positions in `src/core/dot-layout.js`, both
100%-covered) and make the library call an **injected seam** (`app.Chart` /
`app.Dagre` / `env.Editor` / `env.CodeViewer`, like the fetch/crypto seams)
`app.Dagre` / `env.Editor` / `env.SpecEditor` / `env.CodeViewer`, like the fetch/crypto seams)
so the DOM wrapper stays fully tested rather than dropping below the coverage gate. (The CM6
adapters are unit-tested against the real libraries under happy-dom.)
5. **No UI framework; signals for state, imperative adapters for islands.** State
Expand All @@ -52,8 +55,8 @@ all bundled — see hard rule 4). Quality is held by tests.
high-frequency-pointer surfaces (the editor, the EXPLAIN/schema graphs,
Chart.js, result-grid resize/sort) stay **imperative behind an injected seam** —
signals coordinate state, they don't own every mousemove. The editor is
**CodeMirror 6** behind the `EditorPort` seam (#21, landed ahead of #84 —
the completion source swaps to from-scope data there). When a *second* consumer of a
**CodeMirror 6** behind explicit injected SQL and Spec editor seams (#21/#212;
the SQL completion source swaps to from-scope data in #84). When a *second* consumer of a
complex UI pattern appears, extract a shared primitive (e.g. `EditorPort`,
`GraphSurface`, a result-view registry, `Drawer`) rather than copy it — but
don't build a primitive speculatively for a single caller.
Expand All @@ -72,7 +75,7 @@ Touch these in one change:
| `src/core/*` | pure logic, 100% covered |
| `src/net/*` | OAuth + ClickHouse client, injected fetch |
| `src/ui/*` | hyperscript, icons, render modules, controller |
| `src/editor/*` | `EditorPort` seam + editor adapters (#143; CM6 lands here, #21) |
| `src/editor/*` | injected SQL/Spec editor ports + CodeMirror adapters (#143/#21/#212) |
| `src/state.js` | state model + pure ops |
| `src/main.js` | bootstrap (OAuth callback, share-links) |
| `build/build.mjs` | esbuild → `dist/sql.html` |
Expand Down
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ saved queries, history, and shareable links. It ships as a
**single self-contained HTML file served from ClickHouse itself** (no Node
server, no CDN, no external fonts) — the page makes **zero third-party
requests** and renders in the OS's native UI font. Its four bundled runtime
dependencies — **CodeMirror 6** (the SQL editor and read-only source viewer),
dependencies — **CodeMirror 6** (the SQL editor, saved-query Spec JSON editor,
and read-only source viewer),
**Chart.js** (the chart result view), **@dagrejs/dagre** (the EXPLAIN
pipeline-graph layout), and
**@preact/signals-core** (state reactivity) — are inlined into that one file.
Expand Down Expand Up @@ -48,11 +49,27 @@ The browser never holds a static credential — each user authenticates with you
IdP and ClickHouse sees their JWT. There is **no app-specific backend**: the
only moving parts are ClickHouse's HTTP handlers and your OAuth provider.

## SQL editor
## SQL and Spec editors

The editor is **CodeMirror 6** behind an injected `EditorPort` seam (#143/#21)
— bundled and inlined like the other runtime deps, so the page still makes
zero third-party requests. On top of it:
The workbench uses **CodeMirror 6** behind separately injected SQL and Spec
editor seams (#143/#21/#212) — bundled and inlined like the other runtime deps,
so the page still makes zero third-party requests. A saved-query tab exposes a
visible **SQL | Spec** switch: SQL edits the executable text, while Spec edits
only the complete `query.spec` JSON. Linked Save validates and atomically
commits both drafts; an unsaved tab remains SQL-only until its first Save.

Spec mode provides JSON highlighting, line numbers, bracket matching, folding,
local search, undoable two-space formatting, and continuous path-addressed parse
and semantic diagnostics. Its toolbar is deliberately small: **Format**,
**Save**, and the **SQL | Spec** switch. Blocking errors disable Save and are
never persisted; unknown fields remain valid and survive Save.

Panel controls and Library favorite/pencil edits merge their fields into valid
open Spec drafts, preserving unrelated unsaved and extension fields. Invalid
JSON is the only external-writer block: the affected Spec tab opens with a
**Fix Spec JSON first** message, and nothing is changed or persisted. Run,
Explain, SQL formatting, Export, and Share are SQL-mode actions; switch back to
SQL to use them.

The same bundled CodeMirror presentation/search base also powers an injected
read-only `CodeViewer` seam (#213) for source surfaces. It supports complete
Expand All @@ -61,6 +78,8 @@ numbers, local search, selection/copy, configurable wrapping, detached-document
mounting, and explicit teardown—without inheriting editor history, completion,
schema, drag/drop, or app-state behavior.

The SQL editor provides:

- **Per-tab undo** — each query tab keeps its own edit history; switching tabs
parks and restores it.
- **Find / replace** — `Cmd/Ctrl+F` opens CM6's search panel (app-styled) with
Expand Down Expand Up @@ -576,8 +595,8 @@ src/
stream, storage, chart-data, completions (editor reference data
+ ranking) — no DOM, no globals
net/ oauth-config, oauth, ch-client (injected fetch seam)
editor/ injected CodeMirror islands: the editable EditorPort adapter and
the smaller read-only CodeViewer, sharing presentation/search base
editor/ injected CodeMirror islands: editable SQL + Spec adapters and the
smaller read-only CodeViewer, sharing presentation/search base
ui/ dom (hyperscript), icons, + render modules (login, tabs, schema,
results, saved-history, shortcuts, splitters, toast, app)
state.js state model + pure operations
Expand Down
33 changes: 32 additions & 1 deletion docs/ADR-0001-reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ refreshReference / onDocChange, injected as `env.Editor`), and #21 swapped the
adapter from the hand-rolled textarea to **CodeMirror 6** — the fourth bundled
runtime dependency. Signals still coordinate the state (`hasSelection`,
tab/effect wiring, the app-level `onDocChange` subscriber owns the
`tab.sql`/dirty writes); CM6 owns every keystroke, selection, undo, and
`tab.sqlDraft`/`dirtySql` writes); CM6 owns every keystroke, selection, undo, and
measurement inside the port. Per-tab `EditorState`s give per-tab undo — a
capability the shared-textarea design structurally lacked — and the adapter is
unit-tested against the real CM6 under happy-dom (the coverage gate holds
Expand Down Expand Up @@ -222,3 +222,34 @@ without reconstructing the view, and the adapter supplies the target parent and
document root before CM6 initializes its realm-bound observers. This is the same imperative-island rule
applied at a smaller boundary, and gives later cell/detail consumers a stub-able
`app.CodeViewer` seam without coupling them to CodeMirror imports.

## Addendum — independent SQL and Spec JSON editor seams (#212)

Saved-query authoring now owns two explicit imperative islands:
`app.sqlEditor` and `app.specEditor`, injected by `env.Editor` and
`env.SpecEditor`. The former `app.editor` ambiguity is intentionally gone.
Execution, schema insertion, SQL formatting, and Export always address the SQL
adapter; changing which document is visible cannot redirect a SQL operation to
JSON. Each tab holds independent `sqlDraft` and `specText` documents, parsed
Spec state, diagnostics, mode, and dirty flags, while each adapter parks its own
CodeMirror state so undo, selection, scroll, and search remain local.

Spec parsing, normalization, and synchronous semantic validation live in pure
`core/spec-draft.js`. Validator paths are arrays of string/number segments, not
dotted strings, so array indices and object keys containing dots are exact.
The app owns the registry and feature code owns individual rules. Direct Spec
writers use one state-level patch helper. Panel controls patch the active valid
draft and leave it dirty. Immediately persisted Library pencil/favorite changes
patch every valid open draft while preserving both unrelated unsaved fields and
each draft's existing dirty state: clean stays clean; dirty stays dirty. A
syntactically invalid JSON draft is the only block; the writer reports that tab
before any mutation or persistence. Linked Save validates and persists SQL plus
Spec once, atomically; a failed Save writes nothing.

Spec is intentionally a lightweight editing mode rather than a second
workbench. Its toolbar owns Format, Save, and the SQL | Spec switch. Run,
Explain, SQL formatting, Export, Share, and Share’s global shortcut are owned by
SQL mode. Validation is continuous through diagnostics and status; there are no
manual Validate or Revert commands. The adapter shares only the generic
CodeMirror presentation/search base and JSON language package that had already
landed with #213, so #212 adds no runtime dependency.
Loading