You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The editor pane is currently a plain <textarea id="editor">. The recently bundled marked@11 gave us a strong preview pane, but the editing experience is still browser-default — no syntax tinting while typing, no find/replace, no smart Enter inside lists, no multi-cursor, no proper undo. Clarion developers reading and editing markdown READMEs deserve a tool that feels like a tool.
Switching to CodeMirror 6 (@codemirror/*, modular bundle) lifts the editor side to the same standard as the preview without dragging in something as heavy as Monaco.
Current state — the textarea touch surface
markdown-editor.js references the textarea in 32 places via these properties / methods:
All 14 toolbar buttons via wrapSelection, insertText, insertAtLineStart
editor.setSelectionRange(s, e)
Toolbar cursor restoration
editor.focus()
Toolbar focus return
editor.scrollTop
Bidirectional scroll sync with preview
editor.readOnly
URL tab lock-down
editor.addEventListener('keydown', ...)
Ctrl+S / Ctrl+B / Ctrl+I shortcuts
C# side touches no editor DOM directly — all C#→JS calls go through named functions (getEditorContent, getTabContent, setDarkMode, etc.). So as long as the JS keeps those function signatures, the C# layer is untouched.
In scope
Bundle CodeMirror 6 locally under Resources/codemirror.bundle.min.js. CM6 ships ESM-only, so a one-time bundling step is needed — recommended: tiny scripts/build-editor-bundle.mjs (or .cmd) invoking esbuild. Output committed to the repo like marked.min.js. Not part of dotnet build — rebuilt only when CM versions bump.
Adapter shim. Wrap the EditorView in a cmEditor object exposing .value (get/set), .selectionStart, .selectionEnd, .setSelectionRange(), .focus(), .scrollTop, .readOnly, plus an addEventListener('keydown', ...) route. Keeps the existing 30+ call sites essentially unchanged.
Per-tab state. Each tab gets its own EditorState. On tab switch, view.setState(tab.state); on switch-away, capture latest state back to the tab. Replaces the current "stuff content into textarea.value" approach. Must add a flushActiveTab() call before any read from C# to prevent state-drift.
Markdown language mode (@codemirror/lang-markdown) — bold / italic / headers / code visually distinct in the editor pane.
Find / replace (@codemirror/search) — Ctrl+F and Ctrl+H open the CodeMirror panel (replaces the WebView2-default Ctrl+F).
Smart list continuation — pressing Enter inside a - or 1. list inserts the next bullet automatically.
Dark mode theming wired to setDarkMode(). Use the built-in oneDark theme to stay visually consistent with the Atom One Dark hljs theme already in the preview. Default light theme for light mode.
History extension replaces browser-native undo with CodeMirror's. Ctrl+Z must continue to clear the dirty marker correctly when undoing to the last-saved state.
Read-only mode for URL tabs via a Compartment holding EditorState.readOnly.of(true) + EditorView.editable.of(false). Parallel to the existing format-toolbar isReadOnly plumbing.
Out of scope (deliberate)
Vim / Emacs keybindings (@codemirror/vim is +50 KB and a separate decision).
Spell check. CodeMirror replaces the textarea with a contenteditable div that browser-native spellcheck doesn't squiggle the same way. Document the regression; a spellcheck extension can be a follow-up.
Linting (markdown linting via remark or similar).
Bundling Mermaid locally (still on CDN — separate offline-support issue).
CSS redesign beyond what's needed to make CodeMirror visually consistent.
Scroll sync. Re-routed through view.scrollDOM.scrollTop. Verify line-mapping still aligns when CM word-wraps a long line.
Tab state churn. Editor-visible content can briefly drift from getTabContent() if the state isn't pushed back to the tab object. Mitigation: flushActiveTab() before any C#-initiated read.
Save / Save As / Insert-to-IDE. Pull from view.state.doc.toString(). JSON-escape decode in DecodeJsonString should be unaffected (still just a string).
Dirty tracking. Replace oninput with EditorView.updateListener.of(u => if (u.docChanged) markDirty()).
Read-only URL tabs. The format toolbar's applyFormatToolbarVisibility already handles isReadOnly; the CM read-only Compartment switch is parallel and additional.
Bundle size. Expect ~250-300 KB minified added to the zip.
Spellcheck regression. Will be a user-visible behaviour change. Document and possibly add an extension later.
Per-tab undo history. Each tab's EditorState carries its own history. Verify Ctrl+Z stays scoped to the active tab.
Acceptance criteria
Markdown source highlighted in the editor pane (headers, bold, italic, code, lists)
All 14 toolbar buttons still work
Save / Save As / Insert-to-IDE still work
Tab switching, dirty tracking, read-only URL tabs all still work
Motivation
The editor pane is currently a plain
<textarea id="editor">. The recently bundledmarked@11gave us a strong preview pane, but the editing experience is still browser-default — no syntax tinting while typing, no find/replace, no smart Enter inside lists, no multi-cursor, no proper undo. Clarion developers reading and editing markdown READMEs deserve a tool that feels like a tool.Switching to CodeMirror 6 (
@codemirror/*, modular bundle) lifts the editor side to the same standard as the preview without dragging in something as heavy as Monaco.Current state — the textarea touch surface
markdown-editor.jsreferences the textarea in 32 places via these properties / methods:editor.value(get/set)editor.selectionStart/selectionEndwrapSelection,insertText,insertAtLineStarteditor.setSelectionRange(s, e)editor.focus()editor.scrollTopeditor.readOnlyeditor.addEventListener('keydown', ...)C# side touches no editor DOM directly — all C#→JS calls go through named functions (
getEditorContent,getTabContent,setDarkMode, etc.). So as long as the JS keeps those function signatures, the C# layer is untouched.In scope
Resources/codemirror.bundle.min.js. CM6 ships ESM-only, so a one-time bundling step is needed — recommended: tinyscripts/build-editor-bundle.mjs(or.cmd) invokingesbuild. Output committed to the repo likemarked.min.js. Not part ofdotnet build— rebuilt only when CM versions bump.EditorViewin acmEditorobject exposing.value(get/set),.selectionStart,.selectionEnd,.setSelectionRange(),.focus(),.scrollTop,.readOnly, plus anaddEventListener('keydown', ...)route. Keeps the existing 30+ call sites essentially unchanged.EditorState. On tab switch,view.setState(tab.state); on switch-away, capture latest state back to the tab. Replaces the current "stuff content into textarea.value" approach. Must add aflushActiveTab()call before any read from C# to prevent state-drift.@codemirror/lang-markdown) — bold / italic / headers / code visually distinct in the editor pane.@codemirror/search) — Ctrl+F and Ctrl+H open the CodeMirror panel (replaces the WebView2-default Ctrl+F).-or1.list inserts the next bullet automatically.setDarkMode(). Use the built-inoneDarktheme to stay visually consistent with the Atom One Dark hljs theme already in the preview. Default light theme for light mode.CompartmentholdingEditorState.readOnly.of(true)+EditorView.editable.of(false). Parallel to the existing format-toolbarisReadOnlyplumbing.Out of scope (deliberate)
@codemirror/vimis +50 KB and a separate decision).contenteditablediv that browser-native spellcheck doesn't squiggle the same way. Document the regression; a spellcheck extension can be a follow-up.Decisions
esbuildbundling step. Committed artifact pattern (likemarked.min.js). README documents the rebuild command. Bundle target:iifeglobal, minified.v1.3.0— significant user-visible behaviour change (Ctrl+F UI, history behaviour, spellcheck regression). Minor bump.oneDarkfor dark mode, default theme for light mode.@codemirror/state,@codemirror/view,@codemirror/commands,@codemirror/language,@codemirror/lang-markdown,@codemirror/search,@codemirror/theme-one-dark. ~250-300 KB minified.Risks / test surface
view.scrollDOM.scrollTop. Verify line-mapping still aligns when CM word-wraps a long line.getTabContent()if the state isn't pushed back to the tab object. Mitigation:flushActiveTab()before any C#-initiated read.view.state.doc.toString(). JSON-escape decode inDecodeJsonStringshould be unaffected (still just a string).oninputwithEditorView.updateListener.of(u => if (u.docChanged) markDirty()).applyFormatToolbarVisibilityalready handlesisReadOnly; the CM read-onlyCompartmentswitch is parallel and additional.EditorStatecarries its own history. Verify Ctrl+Z stays scoped to the active tab.Acceptance criteria
cme-roundtrip-test.mdstill render correctlyEffort estimate
~5 days focused work. Staged as two PRs reduces the per-PR review burden.