Reconciled 2026-07-13: implemented in PR #215; cell-detail consumption remains tracked separately in #214.
Problem
The application already bundles CodeMirror 6 for the editable SQL editor, but read-only text surfaces still use plain <pre> elements.
The cell-detail drawer is the immediate consumer. Large JSON, SQL, HTML/XML source, Markdown source, logs, and stack traces need:
- line numbers;
- search;
- syntax highlighting where a language is known;
- optional line wrapping;
- efficient scrolling for large values;
- reliable teardown when the drawer closes.
Reusing the editable SQL EditorPort is not appropriate. That adapter also owns:
- per-tab undo history;
- SQL completions;
- hover documentation;
- schema-aware column loading;
- drag-and-drop insertion;
- app-state synchronization;
- editable keyboard behavior.
A read-only viewer needs a smaller, independent seam while sharing the common CodeMirror styling and base extensions.
Goal
Add a reusable, injected read-only CodeMirror viewer that can display source text in any document realm, including detached Data views.
Initial language modes:
- plain text;
- JSON;
- SQL;
- HTML/XML source;
- Markdown source as plain text.
YAML is explicitly out of scope.
Architecture
New viewer seam
Add:
src/editor/code-viewer.js
Export a factory such as:
createCodeViewer({
parent,
document,
text,
language,
wrap,
})
Return:
{
setText(text),
setLanguage(language),
setWrap(enabled),
focus(),
destroy(),
}
Equivalent naming is acceptable.
The viewer is read-only:
EditorState.readOnly.of(true)
EditorView.editable.of(false)
It must not expose editing commands, completion, history, schema loading, drag/drop insertion, or app-state subscriptions.
Injection
Follow the existing third-party seam rule.
Expose the real factory through application construction:
const app = createApp({
Editor,
CodeViewer,
// ...
});
Store it as:
Tests for consumers may inject a lightweight stub. The real adapter receives direct unit coverage.
Do not import the CodeMirror viewer directly from results.js.
Shared CodeMirror base
Extract the genuinely common CodeMirror presentation pieces from the SQL adapter into a small module, for example:
src/editor/codemirror-base.js
Candidates:
- common
HighlightStyle mapping to existing .sql-* token classes;
- read-only-safe search keymap;
- base visual extensions;
- line-number and selection styling;
- line-wrapping compartment helper.
Do not move SQL-specific behavior into the common module:
- SQL dialect construction;
- completion source;
- hover source;
- input guards;
- tab-state parking;
- schema prefetch;
- editor write-back.
The editable SQL editor must continue using the same visual token classes after the extraction.
Language registry
Use a small explicit registry:
languageExtension('text') // []
languageExtension('json') // json()
languageExtension('sql') // existing SQL language support
languageExtension('xml') // xml()
languageExtension('html') // XML-style source highlighting in v1
languageExtension('markdown') // plain text in v1
Add only:
@codemirror/lang-json
@codemirror/lang-xml
as new runtime packages.
Do not add a second Markdown parser or Markdown language package in this issue. Rendered Markdown is handled by the existing safe Markdown parser/renderer; source mode can remain plain text.
Record the bundle-size change in the PR.
Viewer behavior
Read-only
The document cannot be changed through:
- typing;
- paste;
- cut;
- drag/drop;
- input methods;
- editor commands.
Selection and copying remain available.
Search
Use the existing CodeMirror search extension and keymap.
Mod-f opens local viewer search. Search UI and matches stay inside the viewer.
Line wrapping
The consumer controls wrapping:
viewer.setWrap(true | false)
Changing wrapping must not reconstruct the viewer or lose search state unnecessarily.
Large values
The viewer must receive the complete cell text.
Do not pre-truncate source content inside the viewer. Existing drawer sizing and browser memory limits remain the governing constraints.
Document realm
The cell-detail drawer can be opened:
- in the main application document;
- inside a detached browser tab/document.
Construct the EditorView with the correct parent/root/document so all DOM nodes belong to the target document.
Do not read the ambient global document when a target document was supplied.
Lifecycle
destroy() must:
- destroy the
EditorView;
- release observers and event listeners;
- be idempotent.
A drawer mode switch must destroy the outgoing viewer before replacing its DOM.
Closing the drawer must destroy the active viewer before removing the backdrop.
Tests
Real adapter
Add:
tests/unit/code-viewer.test.js
Verify:
- mounts with the supplied text;
- is read-only and non-editable;
- selection/copy remains possible;
- JSON mode loads without throwing;
- SQL mode loads without throwing;
- XML and HTML modes load without throwing;
- text and Markdown modes use plain text;
- wrapping can be toggled;
- text can be replaced programmatically;
- language can be reconfigured;
- search extension is installed;
destroy() is idempotent;
- a custom detached document owns the viewer DOM.
Shared-base regression
Update the SQL adapter tests to verify:
- editable SQL behavior is unchanged;
- completions, hover, drag/drop, undo, and tab-state parking remain intact;
- token classes remain the same;
- no viewer-only read-only setting leaks into the editor.
Consumer seam
Add a stub-friendly contract test showing that a consumer can:
- create a viewer;
- switch wrapping;
- destroy it during mode changes;
- destroy it again during parent teardown safely.
Files
Expected changes:
src/editor/codemirror-base.js
src/editor/code-viewer.js
src/editor/codemirror-adapter.js
src/ui/app.js or the application bootstrap injection point
src/main.js
tests/unit/code-viewer.test.js
tests/unit/codemirror-adapter.test.js
tests/unit/app.test.js
package.json
- lockfile
- E2E import maps whose raw ESM graphs reach the new packages
Acceptance criteria
Non-goals
- Replacing the editable SQL
EditorPort.
- Editable JSON/XML/Markdown cells.
- JSON parsing or formatting policy.
- Rendered Markdown or HTML.
- YAML.
- CSV/TSV table parsing.
- Automatic content-type detection.
Reconciled 2026-07-13: implemented in PR #215; cell-detail consumption remains tracked separately in #214.
Problem
The application already bundles CodeMirror 6 for the editable SQL editor, but read-only text surfaces still use plain
<pre>elements.The cell-detail drawer is the immediate consumer. Large JSON, SQL, HTML/XML source, Markdown source, logs, and stack traces need:
Reusing the editable SQL
EditorPortis not appropriate. That adapter also owns:A read-only viewer needs a smaller, independent seam while sharing the common CodeMirror styling and base extensions.
Goal
Add a reusable, injected read-only CodeMirror viewer that can display source text in any document realm, including detached Data views.
Initial language modes:
YAML is explicitly out of scope.
Architecture
New viewer seam
Add:
Export a factory such as:
Return:
Equivalent naming is acceptable.
The viewer is read-only:
It must not expose editing commands, completion, history, schema loading, drag/drop insertion, or app-state subscriptions.
Injection
Follow the existing third-party seam rule.
Expose the real factory through application construction:
Store it as:
Tests for consumers may inject a lightweight stub. The real adapter receives direct unit coverage.
Do not import the CodeMirror viewer directly from
results.js.Shared CodeMirror base
Extract the genuinely common CodeMirror presentation pieces from the SQL adapter into a small module, for example:
Candidates:
HighlightStylemapping to existing.sql-*token classes;Do not move SQL-specific behavior into the common module:
The editable SQL editor must continue using the same visual token classes after the extraction.
Language registry
Use a small explicit registry:
Add only:
as new runtime packages.
Do not add a second Markdown parser or Markdown language package in this issue. Rendered Markdown is handled by the existing safe Markdown parser/renderer; source mode can remain plain text.
Record the bundle-size change in the PR.
Viewer behavior
Read-only
The document cannot be changed through:
Selection and copying remain available.
Search
Use the existing CodeMirror search extension and keymap.
Mod-fopens local viewer search. Search UI and matches stay inside the viewer.Line wrapping
The consumer controls wrapping:
Changing wrapping must not reconstruct the viewer or lose search state unnecessarily.
Large values
The viewer must receive the complete cell text.
Do not pre-truncate source content inside the viewer. Existing drawer sizing and browser memory limits remain the governing constraints.
Document realm
The cell-detail drawer can be opened:
Construct the
EditorViewwith the correct parent/root/document so all DOM nodes belong to the target document.Do not read the ambient global
documentwhen a target document was supplied.Lifecycle
destroy()must:EditorView;A drawer mode switch must destroy the outgoing viewer before replacing its DOM.
Closing the drawer must destroy the active viewer before removing the backdrop.
Tests
Real adapter
Add:
Verify:
destroy()is idempotent;Shared-base regression
Update the SQL adapter tests to verify:
Consumer seam
Add a stub-friendly contract test showing that a consumer can:
Files
Expected changes:
src/editor/codemirror-base.jssrc/editor/code-viewer.jssrc/editor/codemirror-adapter.jssrc/ui/app.jsor the application bootstrap injection pointsrc/main.jstests/unit/code-viewer.test.jstests/unit/codemirror-adapter.test.jstests/unit/app.test.jspackage.jsonAcceptance criteria
Non-goals
EditorPort.