Incremental reactivity via @preact/signals-core + architecture decisions (part of #88) - #89
Merged
Conversation
Add src/core/signal.js — a ~70-line signal()/effect()/batch() reactive core (100% covered) — and convert the tabs slice (state.tabs + state.activeTabId) to signals end-to-end to evaluate incremental, framework-free reactivity. - state.js: tabs/activeTabId are signals; activeTab()/tabsForSaved/pruneTabLinks read .value (insulating all 16 activeTab() callers from the change). - tabs.js: selectTab/newTab/loadIntoNewTab/closeTab just mutate the signals; refresh() is deleted. - app.js: one effect() reads the tab signals and repaints the strip + editor + results + Save button — the old refresh(), but self-triggering and impossible to forget. Result-data repaints still call renderResults directly. - Test churn: per-file gate stays green (1033 tests). tabs.test.js loses its "selectTab triggers repaint" assertions — that responsibility moved to the app-level effect — and asserts state transitions instead. Spike only (branch spike/signals); not wired beyond the tabs slice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQ6VFCwwUWnU3YgHQK2K5k
Second slice, chosen because sidePanel has NO accessor helper (unlike tabs' activeTab()), so every reader changes — the worst case for churn. - state.js: sidePanel is a signal. - saved-history.js: 6 reads → .value; switchTo() sets .value (filter cleared first, since the effect runs synchronously on assignment) and drops its manual renderSavedHistory() call. - app.js: an effect() repaints the side panel on sidePanel change; the history-record bridge read → .value. - Tests: saved-history.test.js (19), state.test.js (2), app.test.js (1) — all mechanical .value. Gate green (1033 tests). Finding: with no helper, churn is purely mechanical and concentrated in the panel's own test file; no behavioral test rewrites were needed (unlike tabs, where the repaint responsibility relocated). Confirms the pattern generalizes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQ6VFCwwUWnU3YgHQK2K5k
Records the reactivity decision, the measured option comparison (in-house signals ~0.45KB vs Preact ~7.3KB vs Solid/React), the two-slice spike evidence, and the per-slice + accessor-helper migration rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQ6VFCwwUWnU3YgHQK2K5k
Drop-in swap on top of the two converted slices: @preact/signals-core has the same .value / signal / effect / batch API, so the only changes are import lines + deleting src/core/signal.js and its test. Suite green (1022 tests; -11 from removing the primitive's own tests). Adds @preact/signals-core ^1.14.3. Trade vs hand-rolled: +1.4 KB gzip artifact (~1%) and one deliberate dependency, in exchange for a battle-tested, glitch-free core with computed()/untracked() and no ~70 lines + 178 test lines of reactive code to own. NOTE if adopted: add @preact/signals-core to THIRD-PARTY-NOTICES.md and update ADR-0001 to record signals-core as the chosen primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQ6VFCwwUWnU3YgHQK2K5k
Update ADR-0001 (decision + measured 3-way comparison: signals-core +1.4KB vs
hand-rolled +0.45KB vs Preact +7.3KB), add the @preact/signals-core MIT notice
to THIRD-PARTY-NOTICES.md (two→three inlined deps), and fix the build.mjs
dependency comments. CLAUDE.md rule 4 ('two deps') to be updated on adoption.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQ6VFCwwUWnU3YgHQK2K5k
resultView/running become signals; the results pane and Run button now repaint via effects in createApp instead of manual setRunBtn/renderResults calls in the run flow. The tab effect drops renderResults — a new results effect reads activeTabId + resultView + running (and, via activeTab(), tabs). Run-start writes are batched, and the run bookkeeping (runT0, elapsed_ns) is set *before* the run signals flip, since the effects fire synchronously and read it. The format-error path keeps its explicit renderResults (an in-place tab.result write with no signal change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…o signals The header title (name + unsaved-changes dot) repaints via a libraryName/ libraryDirty effect in createApp instead of manual updateLibraryTitle/ renderLibraryTitle calls scattered across saved-history.js, file-menu.js, and the save popover. Removed the now-obsolete app.updateLibraryTitle seam. The editingLibrary-driven renders stay explicit (editingLibrary is not a signal); finish() now leaves edit mode before renameLibrary so the title effect repaints the button view rather than a transient input. The data-driven calls (saveJsonAction, afterLibraryChange, favorite/delete/rename) are dropped — equality-gated signals only skip a repaint when the title is already correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…ADR-0001 Accepted Bump CLAUDE.md rule 4 from two to three bundled runtime deps (adds @preact/signals-core, pointing at ADR-0001). Move ADR-0001 to Accepted and reconcile its "behind accessor helpers" wording with the validated helper-free `.value` reality: the accessor helper is now a guideline (for slices with many scattered readers), not a rule — the sidePanel, resultView/running, and libraryName/libraryDirty slices all converted fine without one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…dapters Record the architecture decision from #88 + the Preact spike (ADR-0001): state reactivity via @preact/signals-core, no React/Preact/Solid, and the hard third-party/high-frequency-pointer surfaces (editor, graphs, Chart, result grid) stay imperative behind injected seams. CodeMirror 6 pre-approved as the next dep behind an EditorPort seam (#21, enabling #84). Shared UI primitives are extracted on a second consumer, not built speculatively. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
Record the spike outcome in ADR-0001: the component model removes the schema panel's in-place-mutation anti-pattern and hits the 100% coverage gate, but at +6.8 KB gzip (measured) and a second render paradigm + icon/h/columns integration seams paid app-wide for one panel. Recommendation: do not adopt Preact now — stay signals-core; keep the schema panel a documented imperative exception (or convert it with replaced Set/Map signals). Revisit only when several complex rich-local-state panels are actually on the roadmap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…ep count Add a Working-discipline section — surface out-of-scope findings as `inbox` issues; reconcile forward work (roadmap #68, the issue's Goal/Acceptance, the ADR addendum, CHANGELOG [Unreleased], close obsoleted via Closes #N) in the same commit; convert friction into memory. Also reconcile the intro line with rule 4 (two → three bundled runtime deps, now that @preact/signals-core is in). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…eleased] Per the new working-discipline rule: record the @preact/signals-core adoption (ADR-0001 / #88) and the rejected Preact spike as an [Unreleased] entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
Implement the forward-work-tracking discipline: a PR checklist item to reconcile affected tracked work (roadmap #68 / issue body / ADR / CHANGELOG), and a "Roadmap item" issue template (Goal / scope / key implementation / acceptance / re-evaluation trigger / tracking) for structured roadmap issues. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
…ditor harness
tests/e2e/editor.html loads /src as raw ESM (no bundler), but signals adoption
added a bare `import { signal } from '@preact/signals-core'` to src/state.js
that the browser can't resolve → state.js fails to load → the harness never sets
window.__ready → every editor-insert / editor-alignment spec times out on
page.waitForFunction. Add an import map pointing the bare specifier at the local
ESM build (the e2e server serves the repo root, so /node_modules is live),
mirroring how pipeline.html imports dagre. Unit tests + the bundle were
unaffected (node resolves bare specifiers; esbuild inlines them).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLVYgQpkbEbHKwEAA4aawZ
# Conflicts: # CHANGELOG.md
This was referenced Jun 30, 2026
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 19, 2026
…idelity + admonitions Owner decision at the PR gate, amending #315's original no-Markdown-dependency non-goal: the hand-written block/inline parser is replaced by marked@18.0.6 used strictly as a PURE LEXER (string -> tokens; marked.parse/innerHTML are never called — the sixth bundled runtime dependency, measured +44,130 bytes raw / ~3.0% on dist/sql.html). parseDocMarkdown's public contract, all five limits, the https-only link policy (reference links now resolve through it), the never-throws fallback, and the fixed-seed fuzz pass all survive; the mapper stays fail-closed (images/raw HTML/rejected links -> visible literal text). DocInline is now a RECURSIVE tree mirroring marked's inline tokens 1:1 — **see the [guide](/docs/x)** keeps its link inside the bold, link text can carry code, and del renders — with the nesting budget guarding inline recursion too. New 'admonition' DocBlock: Docusaurus :::tip/note/info/ warning/danger/important containers (fence-aware pre-scan, unterminated openers stay literal) render as styled asides — the visible wart on real ClickHouse doc bodies. CommonMark upgrades: setext headings, loose lists, reference links. markdown-lite reverts to its simpler pre-lift shape (panels profile unchanged, tests unmodified); the e2e editor.html import map gains marked (the #89 unbundled-harness rule). Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 20, 2026
…n-modal pane, structured sources, system.documentation Markdown (#320) * feat(#313): pure doc contracts + system.functions capability/query/normalize core src/core/doc-types.ts: shared DocKind/DocTarget/DocLookup/DocSummary/DocEntry contracts (types-only). src/core/doc-capability.ts: capability decision from system.columns (name required, every rich column optional and independent — no serverVersion gate), dynamic SELECT construction over confirmed columns with case-insensitive exact/lower/upper name matching, and raw-row normalization (leading-blank-line summaries, String categories -> string[], Nullable(UInt8) tri-state). 100% covered. Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#313): target-aware docSummary/docEntry with connection-generation safety src/net/ch-client.ts: silent system.functions capability probe (loadFunctionsDocColumns via system.columns) + prebuilt-SELECT row fetch (loadFunctionDocRow), both on the tryQueryData seam. src/application/schema-catalog-service.ts: lazy once-per-connection deduped capability probe (null = transient, retried next batch, never a storm; [] = durably unavailable), kind:name entry cache with concurrent-lookup dedup, kind-mismatch dual-key caching (fetched row is the truth), and a docGeneration counter bumped by both invalidate() and loadReferenceImpl so in-flight lookups across a reconnect resolve 'unavailable' without cache writes. docSummary projects docEntry's cache — one fetch serves both. entityDoc() unchanged (deleted when CM6 migrates in a later commit). Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * refactor(#313): extract non-modal drawer chrome primitive from results.ts src/ui/drawer.ts: buildDrawerChrome (panel/head/title/close, class-prefixed, no backdrop/focus-trap baked in) + attachDrawerResize moved verbatim behind a narrow DrawerResizeApp seam. openCellDetail/openRowsViewer compose it with their local backdrop/stacking unchanged — results tests pass unmodified (117/117). The 'shared Drawer primitive deferred to #60' from the results.ts comment; third consumer (docs pane) lands next. Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * refactor(#313): extract chLanguageExtension into src/editor/ch-lang.ts CM6 ClickHouse dialect construction now takes AssembledReference | null directly instead of the app controller, so the docs pane (SQL example highlighting, phase-3 Markdown code blocks) can reuse it without importing the editor adapter. langExtensionFor stays as a thin delegate; lazy first- mount resolve and rebuild-on-refData-arrival unchanged. Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#313): non-modal documentation pane + openDocEntry action src/ui/doc-pane.ts: persistent right-side pane on the drawer chrome (own .docs-* classes — invisible to the modal .cd-* stacking), single instance with content replacement, loading/found/missing/unavailable+Retry states, structured entry rendering (kind/since badges, cycle-guarded alias navigation, categories, deterministic/higher-order, examples via the injected CodeViewer seam with ClickHouse highlighting + exact-text Copy), capture-phase Escape with preventDefault so the global cancel-query shortcut never double-fires, focus restore, stale-response discard. docPanePx resize state end-to-end (state/prefs/splitters 'docPane' axis; attachDrawerResize gains an optional stateKey/axis param, existing callers unchanged). CodeViewerOptions gains optional languageExtension override. Pane closes on sign-out alongside catalog.invalidate(). Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#313): CM6 hover/completion docs on docSummary, F1 command, pure classifier; drop entityDoc src/core/doc-context.ts: pure phase-1 target resolver (word detection + exact/lower/upper resolution over refData functions; literal suppression is the caller's contract). codemirror-adapter.ts: one shared renderFunctionSummary powers hover and completion info (refData lookup stays the existence gate — no SQL for unknown identifiers), compact summary with since/alias badges and an accessible Open reference button into the docs pane, isConnected liveness guards after every await, and an editor-local F1 keymap command (handled -> true, no target -> false so the browser default survives). shortcuts dialog documents F1. Dead seam deleted end-to-end: entityDoc/docCache (service), loadEntityDoc (ch-client), wiring + fake-app stubs. Adds an explicit no-SQL-on-keystroke enforcement test. Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * refactor(#313): editor stays a leaf — inject openDocEntry, enforce editor↛ui boundary Review finding: codemirror-adapter.ts imported ui/doc-pane.js, reversing the editor-as-leaf layering the rest of the codebase relies on. The adapter now declares openDocEntry?: (target: DocTarget) => void on its injected app surface; app.ts binds it to ui/doc-pane's openDocEntry(app, target) at wiring time (App type gains the member). toDocPaneApp and the adapter's borrowed document/prefs/CodeViewer fields are gone. build/check-boundaries.mjs gains the src/editor -> src/ui rule with an "except" mechanism for the two deliberate carve-outs predating it (dnd-mime constants, the dom hyperscript helper) so the direction is enforced, and the e2e editor.html harness mirrors the app.ts wiring. Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * test(#313): real-browser e2e spec for F1, Open reference, and the non-modal docs pane tests/e2e/editor-docs.spec.js: F1 opens the labelled complementary pane with the version-exact entry; unhandled F1 is left to the browser default; the pane is non-modal (no backdrop, editor keeps accepting input); Escape inside the pane closes it without leaking to bubble-phase document listeners and restores editor focus; the completion-info Open reference button is a real keyboard-activatable element opening the same pane; unknown entries render the missing state. editor.html harness serves one canned rich entry (sum) via window.__docFixtures, missing otherwise. Runs in CI (Playwright is CI-only in this environment). Part of #60 (Phase 1, #313). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#314): structured doc sources — formats, table/database engines, data types src/core: DocKind gains format/table-engine/database-engine/data-type; DocEntry gains optional syntaxFull/related/facts. Per-source capability from system.columns (name required, everything else independent — no version gate), SELECT builders over confirmed columns only (system.formats never probes/selects its nonexistent syntax column — asserted in tests), and per-source normalizers (format boolean columns -> human-readable facts, related as same-kind targets or label chips from String or Array shapes, data-type alias_to, canonical casing preserved). src/net/ch-client.ts: generalized loadDocTableColumns (fixed internal table allowlist, never caller-interpolated) + loadDocRow; Phase 1 loaders stay as thin wrappers. schema-catalog-service routes docEntry by kind with fully independent per-source capability probe/cache/retry state, reset together on invalidate/reconnect; function-kind behavior untouched. Part of #60 (Phase 2, #314). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#314): strong-context SQL classifier + CM6 F1/completion routing src/core/doc-context.ts: resolveDocTarget gains ranked strong contexts — top-level FORMAT clause in either FORMAT/SETTINGS order (never confusing format()/formatDateTime(), adjacency-based), ENGINE = for table vs database DDL, and strong data-type positions (CREATE column defs, CAST(x AS T), x::T, {p:T} via the existing param-scan) with innermost nested-type resolution; statement-scoped via sql-lex tokens, pure, no SQL. Optional DocContextOptions.formats validates FORMAT names against refData. codemirror-adapter.ts: F1 uses the extended resolver; format completion info renders the shared summary card with Open reference; function hover unchanged; no metadata query on ordinary typing (enforcement test extended). Part of #60 (Phase 2, #314). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#314): schema-surface doc actions + pane rendering for the new kinds src/ui/doc-pane.ts: kind labels for format/table-engine/database-engine/ data-type, syntaxFull via the injected CodeViewer, facts chips, related items as in-place navigation buttons (label-only stay inert chips), and a bounded (20) session-local back stack unified with alias navigation — rendered in every state, cleared on fresh open/close/connection change. schema-catalog-service: sync docKindAvailable(kind) hint (reads capability state, never probes). schema-detail gains an engine row (below the head — the no-button-in-head contract holds) with Open engine reference; column type cells in the tree (schema.ts) and detail table gain Open type reference targeting the OUTERMOST type family (pure outerTypeName in core/type-display — no caret to resolve innermost from); actions are real labelled buttons, hidden when the source is durably unavailable, existing click/dblclick/Shift-click/drag gestures untouched. loadTableDetail now returns system.tables.engine. Part of #60 (Phase 2, #314). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#314): phase-2 review fixes — comment-headed DDL classification, styling, e2e Review finding (confirmed): a leading -- or /* */ comment before CREATE/ ATTACH DDL broke classification — isDatabaseDDL misread the statement head (database engines resolved as table engines) and inColumnListTypeRegion missed column types entirely. Statement head now skips comment tokens (stmtHeadIdx, mirroring from-scope's convention) with regression tests. Adds the missing CSS for every phase-2 interactive element (.docs-back, .docs-related-link, .docs-facts-list, .docs-syntax-code mirroring .docs-example-code, .schema-type-doc/.schema-engine-doc, engine row) incl. the :focus-visible outline convention across all docs-pane buttons. Extends tests/e2e/editor-docs.spec.js: F1 on FORMAT/ENGINE/column-type contexts, related navigation with Back (kind-keyed harness fixtures). Corrects the dual-key comment in schema-catalog-service (case-mismatched structured lookups DO dual-cache — now tested) and the stale doc-context module contract paragraph. Part of #60 (Phase 2, #314). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#315): version-gated system.documentation capability, loader, kind mapping src/core/format.ts: parseServerVersion/versionAtLeast generalize the supportsExplainPretty one-off (behavior unchanged, tests unmodified). src/core/doc-documentation.ts (pure): documentationProbePolicy — parsed < 26.6 skips (durably unavailable, ZERO queries or probes), >= 26.6 or unparsable version probes once; capability requires name/type/description with source OPTIONAL (agreed amendment — live 26.6.1 servers expose only three columns); 15 known Enum8 labels map to stable DocKinds plus forward-compatible Codec/Metric/System Table, unknown labels stay readable as kind 'unknown' preserving the server label; by-(type,name) and name-only disambiguation SELECTs (LIMIT 20); normalizer emits MarkdownDocEntry (raw markdown body, 1MB byte-bound truncation + oversized flag). schema-catalog-service: version-policy-gated documentation capability on the shared generation machinery; source preference — structured found/ missing wins, durable structured unavailable falls through, no-loader kinds (settings etc.) go straight to documentation; new docMarkdown(target) and docDisambiguate(name) APIs; sourceTable records which source supplied an entry. DocKind widens with 12 broad kinds + 'unknown'. Part of #60 (Phase 3, #315). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#315): bounded pure Markdown-subset parser reusing the markdown-lite inline layer src/core/doc-markdown.ts: block-level parser (ATX headings, paragraphs, bounded nested lists with ordered start numbers, fenced code preserved exactly, single/bounded block quotes, thematic breaks, simple tables with malformed-line paragraph fallback) over markdown-lite's lifted parseInline (now exported with an injected linkPolicy — no second inline parser; parseMarkdown/panels.ts behavior unchanged, tests unmodified). Raw HTML, images, setext headings, and reference links stay literal visible text. https-only default link policy + pure allowlisted clickhouseDocUrl relative mapper (scheme/host/.. rejected -> plain text). Exported limits from #315 (1MB input, 20k nodes, depth 16, 250KB code blocks, 1k links) each truncate/flatten with flags; parseDocMarkdown never throws — total fallback to one literal text block, hostile linkPolicy/toString covered. Fixed-seed fuzz pass (300 iters + pathological inputs). Part of #60 (Phase 3, #315). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#315): safe AST-to-DOM markdown view + pane markdown-subset rendering src/ui/doc-markdown-view.ts: strict DOM-construction renderer (no innerHTML) over the doc-markdown AST — headings offset below the pane title, lists/quotes/tables/hr, sql-tagged fences through the injected CodeViewer with ClickHouse highlighting (other fences plain pre/code), per-block exact-text Copy buttons, parser-approved links only rendered as target=_blank rel='noopener noreferrer' anchors, quiet truncation notes. doc-pane renders markdown-subset entries: kind badges for every #315 kind (unknown shows the preserved serverTypeLabel), muted source path, oversized note, and a visually-secondary 'View latest on clickhouse.com' link ONLY when latestDocUrlFromSource can safely derive it from a docs/**.md source path via the existing traversal/scheme-rejecting mapper — otherwise omitted. Markdown code viewers register into the pane's existing viewer teardown; structured rendering path unchanged. Part of #60 (Phase 3, #315). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#315): broad classifier contexts + accessible disambiguation; F1 name-only fallback src/core/doc-context.ts: six new strong positional contexts ranked with the existing ones — query-level SETTINGS names ('setting'; 'mergetree-setting' when CREATE/ATTACH TABLE DDL carries a preceding ENGINE = *MergeTree* clause), FROM/INSERT INTO FUNCTION table-function calls, CODEC(...) list elements, INDEX ... TYPE skipping-index types, and system.<name> after FROM/JOIN. Aggregate combinators deliberately stay unclassified (name-shape guess, not a caret position — reachable via disambiguation instead). doc-pane: openDocDisambiguation — 0 matches -> missing, 1 -> straight to the entry, 2+ -> an accessible labelled list of kind/name/summary buttons navigating in place, integrated with the back stack (Back returns to the list). F1 contract extended per the spec: an unresolved bare word now opens disambiguation through the new injected app.openDocDisambiguation seam (editor still never imports UI); no-word/literal positions still return false. Harness + e2e spec extended. Part of #60 (Phase 3, #315). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#315): version-gate 'skip' verdict is per-call, not durable (reconnect race) Review finding (confirmed): ensureDocumentationCapability read the current state.serverVersion and durably cached a pre-26.6 'skip' as an unavailable capability — but loadVersion()'s round-trip is not sequenced with resetDocsState(), so a doc lookup racing a reconnect could read the OLD connection's version and lock system.documentation off for the entire new session. The skip verdict now returns per-call without touching the cached capability: still zero network for pre-26.6 servers, self-heals as soon as the version catches up; probe RESULTS stay durable. Regression test simulates the stale-version window then the version update. Part of #60 (Phase 3, #315). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#60): live-verification polish — prose summaries and markdown-rendered structured fields Verified against a real ClickHouse 26.6.1 server: doc text in system.functions/data_type_families/documentation descriptions carries Markdown (Docusaurus :::tip admonitions, links, tables). Summaries (hover cards, disambiguation rows) now derive from the first PROSE line — shared firstProseLine skips blank/admonition/table-row/fence/thematic-break lines and strips ATX heading markers (doc-documentation's local copy replaced). The pane's structured long-text fields (description, arguments, parameters, returned value) render through the existing safe markdown-subset renderer with the same link policy, SQL-fence highlighting, Copy, and viewer teardown as documentation entries; summary/signature/examples unchanged. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * feat(#60): adopt marked@18 as the Markdown lexer — recursive inline fidelity + admonitions Owner decision at the PR gate, amending #315's original no-Markdown-dependency non-goal: the hand-written block/inline parser is replaced by marked@18.0.6 used strictly as a PURE LEXER (string -> tokens; marked.parse/innerHTML are never called — the sixth bundled runtime dependency, measured +44,130 bytes raw / ~3.0% on dist/sql.html). parseDocMarkdown's public contract, all five limits, the https-only link policy (reference links now resolve through it), the never-throws fallback, and the fixed-seed fuzz pass all survive; the mapper stays fail-closed (images/raw HTML/rejected links -> visible literal text). DocInline is now a RECURSIVE tree mirroring marked's inline tokens 1:1 — **see the [guide](/docs/x)** keeps its link inside the bold, link text can carry code, and del renders — with the nesting budget guarding inline recursion too. New 'admonition' DocBlock: Docusaurus :::tip/note/info/ warning/danger/important containers (fence-aware pre-scan, unterminated openers stay literal) render as styled asides — the visible wart on real ClickHouse doc bodies. CommonMark upgrades: setext headings, loose lists, reference links. markdown-lite reverts to its simpler pre-lift shape (panels profile unchanged, tests unmodified); the e2e editor.html import map gains marked (the #89 unbundled-harness rule). Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * docs(#60): reconcile marked adoption — CLAUDE.md rule 4 (six deps), CHANGELOG Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#60): marked-swap review fixes — CommonMark fence-length tracking, markdown-lite image guard Review findings (both confirmed live against marked's real lexer): (1) the admonition pre-scan tracked fences as a boolean toggle, so a shorter inner run desynced it from marked's CommonMark rule (closing fence = same char, run length >= opener, bare) and tore content across admonition boundaries — fence state now records char+length via a shared nextFenceState helper, with mismatched-length and suffixed-run regression tests; (2) the markdown-lite revert dropped the image lookbehind, turning  into a real link in dashboard Text panels — restored with an inert-literal regression test. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#60): review feedback — reference link, global Escape, markdown examples Three live-usage findings from the owner's review of the deployed build: 1. The hover/completion card's "Open reference" button becomes a "(reference — F1)" affordance on the badges row (after the since badge): the single word "reference" is a standard blue link (--accent), still keyboard-activatable; shared openReferenceLink helper serves both the function and structured summary cards, with the previously-missing hover-meta/badges/link CSS added. 2. Escape now closes the reference pane from ANYWHERE — not only with focus inside it. shortcuts.ts's handleKeydown gains a layered Escape branch (close-doc-pane BEFORE cancel-running-query; CM6-consumed Escapes still win via the defaultPrevented guard), driven by an injected app.closeDocPane hook (new isDocPaneOpen export; editor stays a leaf). The pane's own focus-inside capture handler is unchanged. e2e harness mirrors the wiring; new from-anywhere e2e case. 3. The examples field is a MARKDOWN document on real servers (**bold** section titles between ```sql/```response fences), not one SQL snippet — it now renders through the same markdown path as the other long-text fields: each fence its own block with an exact-text Copy button, sql fences highlighted via the injected viewer, response fences as plain preformatted text. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#60): remove per-block Copy buttons from markdown code blocks (owner decision) The Copy button under every rendered fence read as noise; code text selects/copies normally. Dropped from doc-markdown-view (onCopy option gone), the now-dead copyExample/clipboard seam and toast wiring removed from doc-pane, CSS + focus-visible entries cleaned, tests updated. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * test(#60): fix e2e failures — realistic fenced examples fixture, completion-first Escape layering Two deterministic CI failures in editor-docs.spec.js (all three browsers), both harness/spec bugs, production code unchanged: (1) the sum fixture's examples was a bare SQL line — under the markdown examples rendering that is a paragraph, so no viewer/pre existed; the fixture now mirrors real system.functions shape (bold title + fenced sql). (2) the Escape-from- anywhere test pressed one Escape while typing had left the completion popup open — CM6's own Escape correctly closes the popup FIRST (the designed completion -> pane -> query layering, same as observed live); the test now dismisses the popup before asserting the pane close. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * test(#60): deterministic Escape-from-anywhere e2e — programmatic doc set, no completion race The isVisible() completion guard raced CM6's ~100ms popup materialization (Playwright reaches the check before the popup exists; the popup then swallows the Escape — the designed completion-first layering). The test now sets the document programmatically (CM6 only auto-opens completion on user-typed input), so no popup ever exists and the single Escape is deterministically the pane's. Sequence verified against the real harness via build/e2e-serve.mjs in Chromium. Part of #60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ytb297gnTS4tQKAiFZArj * fix(#60): close MAX_DOC_AST_NODES bypass through list items and table rows/cells mapList/mapTable mapped every item/row/cell unconditionally, so once the shared node budget was exhausted a bounded 1 MB body of ~250k short list items or table rows still produced that many empty AST entries — and doc-markdown-view.ts renders one element per entry, freezing the Workbench. Each list item, header cell, row, and cell now counts against the budget and enumeration stops (truncated already set by countNode) once it is spent. Regression tests: oversized list, oversized table, and a small-list/table intactness check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019MVwBAjfFthAyPd1eQczhR --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Incremental adoption of
@preact/signals-corefor state reactivity (ADR-0001 / #88), plus the architecture decisions and contributor discipline that came out of the spike.Code — Part A signal slices (full suite + per-file coverage gate green)
resultView+running→ signals; the results pane and Run button repaint viaeffect()s (manualsetRunBtn/renderResultsremoved; run-start writesbatch()ed, bookkeeping set before the flip so the synchronous effects read current values).libraryName+libraryDirty→ signals; the header title repaints via an effect; the now-unusedupdateLibraryTitleseam removed.tabs/activeTabId/sidePanelwere already converted.)Docs / decisions
spike/preact-schema(not merged).EditorPortseam — Adopt CodeMirror 6 behind the EditorPort seam (replace the hand-rolled textarea editor) #21) and a Working-discipline section (inboxfindings; reconcile forward work; friction → memory).CHANGELOG.md [Unreleased], a PR reconcile checkbox, and a "Roadmap item" issue template.Not here (follow-ups — see the #68 build order)
The schema slice (Phase 1 remainder), CM6 /
EditorPort(#21), and the editor-intelligence + graph work.Part of #88 — the migration continues; this lands the scalar slices + the settled architecture.
🤖 Generated with Claude Code