CLS verified zero (0.00000) on /blueprints/1 and /overlays/1 via
PerformanceObserver({type: 'layout-shift', buffered: true}) on a
real browser session — previously CLS=0.00859 from a 253 px shift
when cm6 mounted into a display:none slot.
Mechanism:
- editor-entry.js: mount() accepts `rows`. When provided, prepends
an EditorView.theme that pins
.cm-editor { height: calc(rows * 1.84rem + 1.125rem) }
and sets .cm-scroller overflow:auto. cm6 renders at a fixed,
predictable height; long content scrolls internally (same UX the
raw <textarea rows="N"> used to give).
- editor.js: reads textarea.rows attribute and passes it to mount().
- editor.css: new .editor-mount wrapper uses the same calc on
min-height keyed off an inline --editor-rows CSS custom property,
so the slot is pre-reserved BEFORE cm6 mounts. Wrapper and cm6
match exactly (browser-measured 254 / 254 px for rows=8 and
607 / 607 px for rows=20).
- Templates: each editor textarea wrapped in
<div class="editor-mount" style="--editor-rows: N">. Single source
of truth on N (only the rows attribute + the inline custom prop
vary per call site).
Per-row metric 1.84 rem derived empirically: 253 px for rows=8 minus
1.125 rem chrome = 235 px content, ÷ 8 ≈ 29.4 px = 1.84 rem.
Fast suite + e2e suite still green (3 + 2 pass, 0 fail).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two e2e tests:
- test_blueprint_autocomplete_accept_writes_into_hidden_textarea:
loads /blueprints/1, types 'sv_che', asserts the cm6 autocomplete
popup shows 'sv_cheats', presses Tab to accept, fires a synthetic
submit on the form, and reads the hidden textarea value back.
Exercises both the autocomplete extension and the submit-time copy
bridge in editor.js end-to-end.
- test_copy_preserves_newlines_across_lines: regression gate for
bug class 1 from v1 (Prism+contenteditable collapsed multi-line
selections). cm6 preserves linebreaks in its doc by construction;
we verify via the per-textarea controller's getValue().
editor-entry.js: discovered during the e2e debug that cm6's default
completionKeymap does NOT bind Tab. Added an explicit
`{ key: "Tab", run: acceptCompletion }` ahead of the rest of the
keymap stack so Tab accepts when the popup is open and falls through
to indentWithTab otherwise. Bundle rebuilt + SHA refreshed.
Tests also surfaced a 200ms popup-settle timing race: the popup is
*visible* on the same tick acceptCompletion runs against null
selectedCompletion. A page.wait_for_timeout(200) before pressing
the accept key bridges the gap reliably in CI.
Chromium runs fine in Claude Code's default sandbox — the stale note
in the handoff doc about Mach-port IPC sandbox-blocking is no longer
accurate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the Task 1 stub. Builds an EditorView with:
- history, line numbers, active-line highlight, bracket matching,
close brackets, indent-on-input
- default + custom HighlightStyle
- light/dark theme via matchMedia-driven Compartment with a
prefers-color-scheme change listener
- language via Compartment (swappable for the files-modal dropdown)
- autocomplete via Compartment (only if vocab is provided)
- keymap stack: closeBrackets, default, history, completion, indentWithTab
Mounts the EditorView immediately before the textarea, hides the
textarea. Exposes window.__editor.mount(textarea, opts) returning a
controller with getValue / setContent / setLanguage / destroy.
bash language comes via @codemirror/legacy-modes/mode/shell wrapped
in StreamLanguage.define — same mechanism as srccfg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CompletionSource over the srccfg-vocab.json shape. Word fragment
matched via /[A-Za-z0-9_]{2,}/ at the caret; ranking is
prefix-match-first (shorter prefixes preferred) then substring;
cap 50 candidates, top 8 rendered. Each option carries the kind
('cvar'/'command') as cm6's autocomplete `type` so the popup
shows the appropriate icon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
themes.js exports four extensions:
- editorLightTheme / editorDarkTheme: EditorView.theme() variants
keyed to the --cm-* CSS variables defined in tokens.css (light) and
its prefers-color-scheme: dark block.
- editorHighlightStyle: HighlightStyle bound to Lezer tags
(comment, string, number, keyword, variableName).
- editorHighlighting: syntaxHighlighting(editorHighlightStyle) ready
to drop into the EditorState extensions array.
@lezer/highlight comes in transitively via @codemirror/language;
no new package.json dependency needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
~30 LOC StreamLanguage definition for Source-engine .cfg syntax.
Tokens: line comment (//…), string, number, keyword (exec/alias/bind/
unbindall/wait), identifier. Linewise, no nesting — matches the
shape we authored as a Prism regex grammar in the v1 attempt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>