#viewer/common

    A small compatibility facade over the focused common-layer packages.

    The real content of the common tier lives in the sibling packages (model, view_model, view_layout, tokens, …). What remains here is a small DOM-free compatibility surface. render_line_class and escape_html live in line_html.mbt. render_line_html is intentionally retained in exports.mbt as the documented adapter from ViewLineData plus per-line decorations to one line's HTML; current repository renderers do not call this wrapper directly, so its placement records the staged external contract explicitly.

    flowchart LR VM["view_model.ViewLineData<br>text + tokens + parts"] --> R["render_line_html"] VL["view_layout.LineDecoration[]"] --> R OPT["RenderWhitespace<br>render_control_characters"] --> R R --> HTML["one line's HTML string"] HTML --> DOM["internal/viewer/browser/view<br>(the only place DOM happens)"]

    Producing markup as a string, with no DOM API in sight, is what keeps line rendering testable on the native target and reusable by the Markdown presentation.

    #Surface

    • escape_html
    • render_line_class
    • render_line_html, which adapts view_model.ViewLineData and view_layout.LineDecoration[] to the DOM-free Monaco-shaped view-line renderer

    Keeping render_line_html in exports.mbt lets embedders that already hold projected line data obtain the canonical DOM-free markup without depending on the browser view's line-node lifecycle. Its compile-checked examples below define that compatibility contract.

    escape_html covers exactly the characters that would otherwise change the parse of the emitted markup. It forwards to base/common's escaper of the same name, which owns the one implementation shared with the Markdown renderer and the agent-feedback widgets.

    ///|
    test "escaping covers the markup-significant characters" {
    debug_inspect(
    @common.escape_html("<span class=\"x\"> a & b '</span>"),
    content=(
    #|"&lt;span class=&quot;x&quot;&gt; a &amp; b '&lt;/span&gt;"
    ),
    )
    }

    render_line_class is the single source for the line element's class name, so the renderer and any stylesheet cannot drift apart.

    ///|
    test "the line class has one definition" {
    debug_inspect(
    @common.render_line_class(),
    content=(
    #|"view-line"
    ),
    )
    }

    #Rendering a line

    render_line_html takes the projected line data and the decorations that apply to it, and returns the markup for that one line.

    ///|
    test "a tokenized line renders to token spans" {
    let text_model = @model.TextModel(
    @base_common.Uri::parse("file:///line-html-doc.mbt"),
    "line-html-doc.mbt",
    "moonbit",
    1,
    "rev-1",
    "let x = 1\n",
    )
    let view_model = @view_model.ViewModel(text_model)
    let line_data = view_model.lines().get_view_line_data(1)
    debug_inspect(
    @common.render_line_html(line_data, []),
    content=(
    #|"<span class=\"view-line-content\"><span class=\"mtk1\">let x = 1</span></span>"
    ),
    )
    }

    Whitespace rendering is an option rather than a default, because the same renderer serves a code view that may want visible whitespace and a Markdown fence that never does.

    ///|
    test "whitespace rendering is opt-in per call" {
    let text_model = @model.TextModel(
    @base_common.Uri::parse("file:///line-html-ws.mbt"),
    "line-html-ws.mbt",
    "moonbit",
    1,
    "rev-1",
    " a b\n",
    )
    let view_model = @view_model.ViewModel(text_model)
    let line_data = view_model.lines().get_view_line_data(1)
    let plain = @common.render_line_html(line_data, [])
    let all_whitespace = @common.render_line_html(
    line_data,
    [],
    render_whitespace=All,
    )
    debug_inspect(
    (plain == all_whitespace, all_whitespace.contains("mtkw")),
    content=(
    #|(false, true)
    ),
    )
    }

    #Boundaries and checks

    Mouse targets and hit testing do not live here. Browser event/target values are in viewer/browser, and the hit-test implementation is in internal/viewer/browser/controller.

    This package may depend on viewer/common/editor_api, viewer/common/view_layout, and viewer/common/view_model; it must remain DOM/FFI/host independent and build for JS and native. The complete surface is pkg.generated.mbti.

    moon test --target js viewer/common

    escape_html

    fn escape_html(text : String) -> String

    HTML-escapes text for element content or attribute values. This package forwards to the shared base/common implementation.

    render_line_class

    fn render_line_class() -> String

    The class of the line node itself. The former CurrentLine-decoration merge is gone: the current-line highlight renders through its own overlay since 2026-07-02, and no producer created CurrentLine decorations anymore (decoration plan Phase 4 retirement, pulled forward with the Phase-3 seam rewire).

    render_line_html

    Pure line-HTML emission, the Monaco viewLineRenderer role: view-line data plus its per-line LineDecorations become the escaped inner HTML of one .view-line node. No DOM types; the browser view assigns the result with one innerHTML write per entering line.