"Live Preview is the decoration engine's first job: render the look over the source, and reveal the raw markup where the cursor is."
Job One — Render the Look Over the Source
Live Preview is the most visible of the three jobs and, mechanically, the simplest: walk the Markdown syntax, and for each construct attach mark decorations that render its look. A **fire** gets a mark that renders bold; a # heading gets marks that enlarge the text and dim the hash. Nothing is inserted or deleted — the source is exactly the Markdown you typed, wearing a rendered coat.
The Active-Line Reveal
The behavior that makes it feel alive is the active-line reveal. On lines your cursor isn't on, the markup is rendered (bold looks bold, the # is dimmed). On the line your cursor is on, the raw markup snaps back so you can edit it precisely. The decoration set is simply recomputed with the cursor's line excluded from the "render" treatment. Read a clean document; edit exact source; the boundary follows your attention.
function buildLivePreview(state) {
const cursorLine = state.doc.lineAt(state.selection.main.head).number;
const marks = [];
for (const node of parseMarkdown(state.doc)) {
if (node.line === cursorLine) continue; // reveal raw markup on the active line
marks.push(renderMark(node)); // render the look everywhere else
}
return Decoration.set(marks, true);
}
All Marks, No Mutation
Every bit of Live Preview is mark decorations over unchanged source. That's why it can coexist with everything else: turn it off and the document is byte-identical Markdown; turn it on and you've added a layer, not a model. It's also why this is the same engine as the next two jobs — voice underline and CMD+K are the same 'compute ranges, attach decorations' shape, just with different ranges and, for CMD+K, a widget instead of a mark. Job one is the gentle introduction to a mechanism the whole soul layer rides.