Live Preview: rendered code blocks shorter than 20 characters lose line styling when the cursor enters them

Steps to reproduce

  1. Open the Sandbox vault (Help → Sandbox vault). No community plugins, themes, or snippets needed.

  2. Create a note containing this query block (16 characters total, counting from the first backtick of the opening fence to the last backtick of the closing fence):

    Some text.
    
    ```query
    foo
    ```
    
  3. In Live Preview, click on “Some text.”, then press the Down arrow until the cursor moves into the code block.

Did you follow the troubleshooting guide? [Y]

Expected result

The block expands to source view with normal code-block styling on all three lines (HyperMD-codeblock line classes, code background), as happens for longer blocks.

Actual result

Only the opening fence line gets its code-block line classes. The content line (foo) and the closing fence render as bare paragraph lines — no HyperMD-codeblock class, no background. Typing any character anywhere restores correct rendering. Moving the cursor out and back in reproduces it again.

The same happens for any rendered block (query, mermaid, and every registerMarkdownCodeBlockProcessor language, e.g. Dataview) whose total source length is 19 characters or less. At 20 characters and above (e.g. ```query + foobarbaz), rendering is correct. Plain (non-rendered) languages such as js are unaffected at any length.

Environment

SYSTEM INFO:
    Obsidian version: 1.13.7
    Installer version: 1.12.7
    Operating system: Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:56 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6041 25.2.0
    Login status: logged in
    Language: zh
    Catalyst license: insider
    Insider build toggle: on
    Live preview: on
    Base theme: adapt to system
    Community theme: none
    Snippets enabled: 0
    Restricted mode: on

RECOMMENDATIONS:
    none

Additional information

Screenshots below (taken in the Sandbox vault, default theme): block A (16 chars) broken after cursor entry vs block B (22 chars) rendering correctly — same block type, same note, the only variable is source length.

Analysis of app.js (1.13.7) suggests where the 20 comes from:

  1. Visible-range computation calls RangeSet.spans(decorations, viewport.from, viewport.to, iterator, 20) with minPointSize = 20, so a block-replace decoration (the rendered code block widget) shorter than 20 characters is not treated as a point — the text it covers stays inside visibleRanges even while the widget is displayed.

  2. The line-class decorator plugin (the one that turns HyperMD-* line-class node props into Decoration.line) iterates the syntax tree over visibleRanges with a monotonically advancing position guard (if (c > lineBlockAt(from).from) return). While the widget is displayed, all covered source lines belong to one line block, so after the first line’s tokens are emitted, the guard drops the line-class decorations for every following line of the block.

  3. When the cursor enters the block, the widget decoration is removed — but for a sub-20-character block visibleRanges do not change (the text was never excluded) and the syntax tree has not changed either, so the plugin’s update (which rebuilds only on tree change or viewportChanged) keeps the stale, incomplete decorations. Any edit changes the tree and repairs the display; for blocks ≥ 20 characters the widget’s removal changes visibleRanges, so they render correctly on the first try.

The syntax tree itself is correct in the broken state — all three lines carry their full HyperMD-codeblock(-begin/-end) line-class props. Only the decoration set is stale.

A · 16 chars · broken after cursor entry (Sandbox vault, default theme):

B · 22 chars · renders correctly:

thanks