The bug we couldn't fix in JavaScript — why SoloMD's Windows editor left contentEditable
In June a user reported that typing Chinese in SoloMD on Windows was
"sometimes fine, sometimes broken" — characters silently dropped,
punctuation needing two keypresses, worst with Sogou Pinyin
(#108).
A month later the fix shipped, and it wasn't a patch: SoloMD v4.7
replaced CodeMirror on Windows with a block editor built on native
<textarea> elements. This post is the debugging trail
that justified something that drastic.
The first suspect: our own autocomplete
IME composition is a fragile dance: between
compositionstart and compositionend, the
browser owns a small piece of the DOM, and the editor must not disturb
it. CodeMirror 6 knows this and freezes what it can. But tracing every
composition event from a real Windows WebView2 into a log receiver
showed something dispatching transactions mid-composition —
and the stack pointed at autocompletion.
With activateOnTyping: true, CodeMirror's autocomplete
plugin classifies every IME keystroke as an activation: it churns the
completion machinery on each pinyin letter, and its
compositionend handler schedules a
startCompletion transaction ~20 ms after each commit.
That post-commit dispatch aborts the next composition —
WebView2 gives up and commits raw pinyin. Guarding our completion
sources wasn't enough (the plugin activates regardless); turning
activateOnTyping off and re-triggering popups from an
explicit character whitelist was. That fix was real, and it removed the
intermittent failures.
The defect underneath
Two symptoms survived every JavaScript mitigation we tried: the
first character of a composition was dropped (吃字),
and full-width punctuation (,。?!)
required two keypresses. We could reproduce both in a
page with no editor at all — a bare contentEditable div
with event logging. Suppressing all DOM mutations on the composing node
during composition changed nothing, which told us the bug lives below
the DOM/JS layer, in WebView2's TSF (Text Services Framework)
integration.
The decisive contrast: the same keystrokes, same IME, same
session, typed into a plain <textarea> on the same
page, worked perfectly. The native text-input path is fine;
only contentEditable is broken. That means every
contentEditable-based editor — CodeMirror 6, ProseMirror, Slate —
inherits the bug inside WebView2, and none of them can fix it from
JavaScript.
We reduced it to a self-contained repro.html (a
contentEditable box and a textarea side by side, with composition /
beforeinput / input logging) and filed it
upstream:
MicrosoftEdge/WebView2Feedback#5625 — "WebView2 drops first CJK
IME character & doubles Chinese punctuation in contentEditable"
(reproduced on Evergreen runtime 149.0.4022.69, Windows 11).
The rewrite: native textareas, one block at a time
Once the defect is below JavaScript, an app has two honest options:
wait for Microsoft, or stop standing on the broken floor. Chinese input
is not an edge case for a Markdown editor with a large CJK user base,
so v4.7 moved SoloMD's Windows live editor onto the input surface we
had just proven correct: the document is split into Markdown blocks,
inactive blocks render as HTML, and the active block is a real
<textarea> — the native IME path, no
contentEditable anywhere. macOS, Linux, iOS and Android keep
CodeMirror; their webviews don't have this bug.
The cost of that decision is feature parity, re-implemented by hand: live preview with markers fading per block, autocomplete and slash commands, focus and typewriter modes, smart list continuation, AI rewrite, in-document find & replace, clipboard image paste, task-checkbox toggling, undo/redo coalescing, Tab indent, spellcheck, word wrap. A block editor also has seams a single buffer doesn't: Backspace at a block boundary used to no-op (fixed in 4.7.4 by folding boundary deletes onto the full document), and Ctrl+A could only select inside one block (fixed in 4.8.10 by collapsing the document into one merged block under a native full selection).
What we'd tell other editor authors
- Trace, don't guess. The autocomplete abort and the TSF defect produced the same user-visible symptom. Without event-level traces from the real engine we would have shipped the small fix and called the big bug fixed.
- Test against the real engine. Edge on Windows is the same WebView2 Chromium — a dev server opened in Edge inside a Windows VM reproduces what macOS never will. Our worst regression (a focus loss after select-all) was only caught by real key events, because synthetic test events kept politely re-focusing.
- The native path is a feature.
<textarea>gives you IME, selection, clipboard and undo semantics for free, maintained by the OS. Rebuilding WYSIWYG around it costs real work, but you stop owning bugs you cannot fix. - File it upstream anyway. If your users hit it, so do others — the WebView2 report exists so the next team can skip a month of tracing.
SoloMD is a free, MIT-licensed Markdown editor for macOS, Windows, Linux, iOS and Android — solomd.app. The Windows block editor ships since v4.7; the full change log is on the what's new page.
Comments & feedback
Comments live on GitHub Discussions — sign in once with your GitHub account.
Comments load from GitHub Discussions. If you see this message instead, the maintainer has not finished the one-time giscus.app setup yet — see web/COMMUNITY-SETUP.md.
Open on GitHub Discussions →