feat: adopt CodeMirror 6 behind the EditorPort seam (#21) - #145
Merged
Conversation
Step-0 delta on dist/sql.html (esbuild, minified, real build): raw 484,674 -> 896,794 B (+412,120, +85%); gzip 155,810 -> 291,723 B (+135,913, +87%). Over the issue's ~100-150 KB raw estimate; approved to proceed at the plan gate (gzip transfer cost is in range, artifact stays single-file/zero-request). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
src/editor/codemirror-adapter.js implements the #143 port against real CM6: per-tab EditorState (per-tab undo), annotation-guarded state sync (tab switches never emit doc changes), ClickHouse dialect from app.refData with Compartment reconfigure (parked tabs re-applied on restore), completion parity v0 over core/completions.js ranking (filter:false, per-option apply for the () caret), hover docs, IDENT/SUBQUERY drop handling, and defaultKeymap with Mod-Enter stripped so global chords bubble to the document handler. Token colors map onto the existing .sql-* classes via HighlightStyle; editor chrome (gutter/selection/search panel/tooltips) styled in styles.css. Unit-tested against real CM6 under happy-dom at the per-file gate (35 specs, incl. the ported #143 port behavior suite + re-mount reparenting). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
Deletes the superseded modules the CM6 adapter replaces wholesale:
src/editor/{textarea-adapter,editor-complete,editor-intel,editor-search}.js,
src/core/editor-{marks,geometry,brackets,search}.js, their unit suites, the
textarea-specific styles (.sql-textarea/.sql-pre/.sql-gutter/mark overlay/
.ac-*/.srch-*/.sig-*), and the now-consumerless maskFromTokens/maskLiterals
(sql-highlight) + signatureContext (completions) — the #141 mis-masking
consumers the issue slated to die in this cutover. tokenize and the .sql-*
token color classes stay (completions.js + the adapter's HighlightStyle).
app.test.js editor-backed flows now inject the CM6 adapter and drive the
port/view. e2e: editor.html mounts the CM6 adapter with the full 13-entry
import map (incl. @marijn/find-cluster-break); editor-insert.spec.js ported;
editor-alignment.spec.js deleted (textarea/pre scroll-sync bug class is
structurally gone); new editor-cm6.spec.js covers highlight-on-type, the
never-swallowed run chord, Enter-accept, per-tab undo, and the search panel
— 30/30 green on chromium+firefox+webkit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
Caught in the live pass: CM6's addInfoPane appendChild()s an info FUNCTION's result, so returning the doc text raw threw "TypeError: parameter 1 is not of type 'Node'" the first time a row with a description was dwelled on. Wrap keyword/function docs in a div; unit specs now assert nodeType, not just text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
CHANGELOG [Unreleased] records the swap + measured bundle delta (+402,911 B raw / +132,903 B gzip). README: dep list is four, SQL-editor section rewritten for CM6 (per-tab undo, CM6 search panel, info tooltip; signature help deferred to #60), layout map gains src/editor/. CLAUDE.md rules 4/5: CM6 is the landed 4th dep (port-shaped injected seam), no longer "pre-approved pending #84". THIRD-PARTY-NOTICES: 14 MIT packages (7 @codemirror, 3 @lezer, style-mod, w3c-keyname, crelt, @marijn/find-cluster-break). ADR-0001 addendum: the editor island has its intended imperative-adapter implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
Correctness (all confirmed by the 8-angle review pass):
- lowercase dialect `builtin` words: lang-sql looks words up lowercased, so
camelCase function names (toDateTime, arrayJoin, …) never highlighted
- resolve langExt lazily at first mount: the factory runs before createApp
assembles the fallback refData, so the editor mounted with an EMPTY
dialect (and stayed monochrome all session if the reference load failed)
- re-register app.dom.editorView on every mount (renderApp resets app.dom;
the ref was lost after sign-out → sign-in)
- restore quote auto-pairing (' " `) — the deleted editor-brackets.js paired
quotes (wrap/type-over/auto-close); only {} is deliberately unpaired
- literal-aware brackets: a Prec.high inputHandler types a bare ( / [ inside
strings/comments/quoted idents (CM6's closeBrackets only tree-checks
quotes) — the issue's acceptance criterion the first cut missed
- hover: case-insensitive function lookup (SUM → sum) and no phantom doc
cards inside strings/comments; scan the line, not the whole doc
- IDENT drops land at the pointer like SUBQUERY drops — dropCursor already
promises exactly that target (the caret-insert was textarea-era semantics)
- collapse a restored tab's parked selection to its head: an invisible
selection silently retargeted ⌘↵/Export
- emit unless EVERY transaction in an update is sync-annotated (a coalesced
user edit + reconcile update must still reach tab.sql); sync reconciles
are also addToHistory:false now (⌘Z can't resurrect a replaced doc)
- shortcuts.js: ignore defaultPrevented keys — Esc that closed the CM6
completion/search no longer also cancels the running query
Cleanup from the same pass: clamp() reused from core/format.js, identity
CM_TYPE map dropped (kind passes through), dead .hover-card overlay block
collapsed into one .cm-tooltip rule, shared fullReplace spec for the three
full-doc replaces, completion source slices to the caret instead of
serializing the rope, same-tab sync short-circuits on length, adapter tests
ride tests/helpers/fake-app.js. README bullets updated (quotes pair; drops
land at the pointer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
Product fixes (all adversarially confirmed by the multi-agent pass): - inputGuards (né bracketInLiteral) now mirrors closeBrackets' bail-outs: no dispatch mid-IME-composition, no action when the reported range isn't the selection (browser-generated corrections must not re-anchor), and it additionally owns: type-over of PRE-EXISTING closers/quotes (CM6's closedBracketAt only tracks pairs it inserted this session — a loaded/ formatted doc's quotes never stepped over), quote quiet-zones in comments and backtick identifiers (CM6 is only tree-aware for same-char quotes in Strings), and quote-over-selection inside a string replacing instead of wrapping (editor-brackets.js parity) - Escape filtered out of defaultKeymap alongside Mod-Enter: CM6's simplifySelection consumed Esc whenever a selection existed, and with the defaultPrevented guard the first Esc silently collapsed the selection instead of cancelling the running query - replaceDocument refocuses the editor (microtask-deferred) — a toolbar Format left focus on the button and the advertised ⌘Z dead until a click - hover lookupFn guards own properties: a column named `constructor` hovered a phantom card off Object.prototype (hole inherited from editor-intel) - completion source slices to the caret's line END, not the caret: cutting at the caret misread an open backtick-identifier with an escaped backtick as closed and popped a spurious completion on Ctrl+Space Test pins the review demanded (each proven load-bearing by mutation): undo-after-reconcile (addToHistory:false), multi-line hover offsets, the behavioral auto-pairing e2e (pairs in code / steps over / quiet in strings — pins the inputHandler wiring + Prec.high), and the per-tab-undo e2e now undoes AFTER the tab round-trip so a fresh-state impostor fails it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
The app's styled ::-webkit-scrollbar paints a classic always-visible bar whenever an element is scrollable, and under real-Safari zoom rounding the editor scroller can read as scrollable by a few phantom px — painting both bars over a six-line query that visibly fits (reported on the PR). The old textarea editor always hid its native bars (scrollbar-width: none) for exactly this class of noise; restore that for .cm-scroller. Wheel/trackpad/ keyboard scrolling is unaffected (verified live: scrollTop/scrollLeft take on a 40-line doc with no bars painted). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 4, 2026
… bars (#147) The console rendered at 1.2x via `html{zoom}` since the first commit. zoom:1.2 (=6/5) lands element box sizes on fractional device pixels, and the leftover sub-pixel made scroll containers read as scrollable-by-~1px over content that visibly fit — a phantom scrollbar. #145 dodged it on the editor by hiding its bars entirely (both axes); the rest of the UI still showed spurious bars at some window sizes. (The same rounding drove the Safari viewport divergence #70.) Set --zoom:1 (native size; users enlarge with browser zoom). This puts every engine into the state the `@supports not (zoom:1)` fallback already defined and tested, so the zoom-bridging machinery (--vp-zoom measurement, Chart/menu/ splitter correction) is now dormant at divisor 1 rather than removed — full teardown tracked in roadmap #68. With no rounding the editor uses the app's standard themed scrollbars like every pane: a vertical bar for a long query, a horizontal bar for a long line, and nothing when it fits. Verified live on otel through the bundled artifact: short query → no bars, long line → horizontal bar, many lines → vertical bar, back-to-short clears; fullscreen graph panel measures --vp-zoom=1 and fits one screen. 1337 tests pass. Claude-Session: https://claude.ai/code/session_01FXHG1CxKa5vm4SzNUQ8iWW Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 4, 2026
Versioned screenshot capture of the shipped app (antalya, v0.2.0 + CM6 #145/#84, ClickHouse 26.3.10) for design review: 22 desktop + 5 mobile shots, dark canonical with two light-theme parity shots, plus meta.json and notes.md. Also adds the reusable capture spec + MCP runbook (CAPTURE-SPEC.md) and the version index (README.md). 21-header-longversion skipped (antalya header doesn't crowd). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyDQ7yG4gaxWiGqB3Bbtgu
BorisTyshkevich
added a commit
that referenced
this pull request
Jul 4, 2026
Cuts the accumulated Unreleased work since 0.2.0: dashboard phases 1-3 (#149 D1-D3 — favorited-query dashboard, Arrange/Report layout, global filter bar), FROM-aware autocompletion (#84), the CodeMirror 6 editor cutover (#21/#145) and EditorPort seam (#143), query variables (#134), best-effort mobile mode (#126), schema-graph click-to-draw (#124), CSS minification, and assorted fixes.
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 & why
Closes #21 — the SQL editor is now CodeMirror 6, behind the #143
EditorPortseam (a one-factory swap inmain.js). This is the deliberate 4th bundled runtime dependency and the foundation Phase 4 builds on (#84 schema-aware autocomplete becomes a completion-source data swap, #60 a hover/docs extension).What users get: per-tab undo history, real IME/touch editing, CM6's find/replace panel (⌘F), measured-text rendering. What gets deleted: the whole hand-rolled stack — execCommand undo (with its Firefox workaround), fixed-glyph-width geometry, four-way scroll sync, the editor's
html{zoom}popover bridging —src/editor/{textarea-adapter,editor-complete,editor-intel,editor-search}.js,src/core/editor-{marks,geometry,brackets,search}.js,maskLiterals/signatureContext, ~2,600 LOC incl. tests. Net diff: −1,100 lines.Parity notes
system.keywords/functions— they feed a ClickHouseSQLDialectswapped via a Compartment on connect (parked tab states re-applied on restore).core/completions.jscandidates + ranking (filter: false; per-optionapplyfor the()caret). Function docs show as CM6's info tooltip (side panel, not the old footer) and on hover.Mod-EnterandEscapedefaultKeymap bindings are stripped so ⌘↵ is never swallowed by an open completion and Esc still cancels a running query;shortcuts.jsnow ignoresdefaultPreventedkeys.Prec.highinput guard restores the old literal semantics CM6 lacks: brackets/quotes never pair inside strings/comments/backtick idents, type-over works on pre-existing closers/quotes, quote-over-selection inside a string replaces instead of wrapping.{}still deliberately unpaired (Support variables in SELECT queries #134 variables).Step-0 bundle gate (measured, approved at the plan gate)
Over the issue's ~100–150 KB raw estimate (gzip in range); accepted as the price of the Phase-4 editor foundation. THIRD-PARTY-NOTICES covers all 14 bundled packages.
Review & verification
@marijn/find-cluster-breakimport-map entry,filter:falseall caught pre-code)./code-review+ a 21-agent adversarial ultrareview pass; 20 confirmed findings fixed (empty-dialect-at-mount, non-lowercasedbuiltinwords, quote pairing in comments, Esc swallowed bysimplifySelection, IME-composition guard, prototype-pollution in hover lookup, ⌘Z dead after Format, …) with mutation-proven test pins.Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)src/core/, network insrc/net/(injected fetch), DOM insrc/ui/CHANGELOG.md([Unreleased]) updated if behavior or the deployed surface changed🤖 Generated with Claude Code
https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa