Repository navigation
Description
id(web): marks spanning inline HTML are broken on save — links fragment around the tags, emphasis leaks entities
Summary
In the pkgs/id web markdown codec (pkgs/id/src/web/markdown.rs), a mark (link, emphasis, …)
that spans an html_inline atom does not survive a save. The parse direction is correct —
the marked text runs carry the mark and the html_inline nodes sit between them — but the
save direction reassembles the span wrongly: the HTML tags migrate outside the mark, a link
is fragmented into one link per text run, and emphasis forms leak literal   entities
into the saved file. The corrupted output is a stable fixpoint, so nothing downstream ever
notices or repairs it.
This is the html_inline counterpart of #1407 (marks wrapping an image). The
html_inline node type was introduced by the #943 fix (PR #1009); unlike the image arm,
its save arm has no mark handling at all, so any fix scoped to images will not touch this.
Evidence (verified this run on main 37418e56)
Verbatim-codec probe crate (markdown.rs copied byte-identical from main, comrak 0.56.0;
log: goal hidden_files/probe-2026-10-27-creator-a6/out2.log + out3.log):
| Input | Saved (first save) |
|---|---|
[a <b>x</b>](/y) |
[a ](/y)<b>[x](/y)</b> |
[a <b>x</b> c](/y) |
[a ](/y)<b>[x](/y)</b>[ c](/y) |
[a <sup>2</sup> c](/y) |
[a ](/y)<sup>[2](/y)</sup>[ c](/y) |
*a <b>x</b> c* |
*a *<b>*x*</b>* c* |
Parse shape for [a <b>x</b> c](/y) is correct (probe out3.log): PM paragraph =
text "a " [link], html_inline "<b>", text "x" [link], html_inline "</b>",
text " c" [link]. The information loss/fragmentation happens entirely on save.
Controls that round-trip cleanly in the same probe:
a <b>x</b> c(inline HTML, no marks) — clean.# a <sub>x</sub>,a <!-- c --> b— clean.[<b>x</b>](/y)and**<b>x</b>**(mark around a single atom run) — survive, because
comrak re-nests the single marked text run equivalently.
Root cause (from the code)
- Parse:
NodeValue::HtmlInlinearm (markdown.rs:421-433) emits anhtml_inlineatom
with only anhtmlattr; atoms carry no marks, so the surrounding mark context exists
only as marks on the neighbouring text runs. - Save: the
"html_inline"arm ofjson_to_ast(markdown.rs:1360-1361) emits a bare
NodeValue::HtmlInlinenode. Mark wrapping is done bycreate_marked_text
(markdown.rs:1737) per text run, and neither it norappend_inline_child
(markdown.rs:1712) can carry a mark across the atom — contrast the image arm
(markdown.rs:1308+), which at least special-cases the link mark (id(web): linked images [](href) lose their link in the markdown ↔ ProseMirror codec — save strips the href #880). The result:
each text run gets its own mark wrapper, the tags land outside, boundary spaces inside
emphasis wrappers are escaped by comrak as .
Why it matters
Inline HTML inside links/emphasis is ordinary imported-markdown content (linked terms
with <sup>/<sub> notation, <b>/<kbd> inside link text, formatted references).
The first open→save silently rewrites it into fragmented links and entity-polluted
emphasis; the rendered meaning changes (link scope and tag nesting invert) and the
  form is a stable fixpoint, so the corruption is permanent and invisible to
later round-trip checks.
Acceptance criteria
[a <b>x</b> c](/y)and[a <sup>2</sup> c](/y)round-trip with the link intact as a
single link span containing the inline HTML (exact markdown or a form re-parsing to
the same PM structure).*a <b>x</b> c*(and strong/strikethrough equivalents) save as a single mark span
containing the tags, with no or other HTML entities introduced.- Cases that already work stay working: unmarked inline HTML (
a <b>x</b> c),
single-run forms ([<b>x</b>](/y)), and the id(web): inline HTML (<sub>/<sup>/<u>/…) is backslash-escaped on save — tags become literal text #943 sub/sup/u cases. - Regression tests in
pkgs/id/src/web/markdown.rscover mark-spanning-html_inline in
both directions; existingweb::markdowntests pass.
Related
- id(web): emphasis/strong/strikethrough wrapping an image are silently dropped on save — mixed text+image spans split and leak   entities #1407 — same failure class for image atoms (different arm, different fix surface).
- id(web): inline HTML (<sub>/<sup>/<u>/…) is backslash-escaped on save — tags become literal text #943 (closed) — introduced the
html_inlinenode; its cases carry no marks. - id(web): nested marks corrupt on save — em/strike/code inside strong/em gain   entities and multiplied delimiters, several non-fixpoint #968 (closed) — text-run mark re-nesting; this issue is the atom-boundary remainder.
Activity
developing-today commentedon Oct 27, 2026
CODE-502
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
16 remaining items
dezren39 commentedon Oct 27, 2026
🤖 Agent note · state: parked
[a <b>x</b> c](/y)->[a ](/y)<b>[x](/y)</b>[ c](/y)), then fixed the save side in pkgs/id/src/web/markdown.rs:append_inline_childnow callsadopt_trailing_html_atomsbefore inserting a mark chain — trailing html_inline atoms move into the tail mark wrapper (recursing along shared marks, mirroring insert_marked_chain) when the incoming chain shares that wrapper's mark, so the span emits whole. Atoms not between same-mark runs keep legacy placement. Sibling id(web): emphasis/strong/strikethrough wrapping an image are silently dropped on save — mixed text+image spans split and leak   entities #1407 (image atoms) has no mechanism yet; this helper's atom predicate is the generalization point for it.dezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up
queue/1481-1412-html-inline-marksMerge pull request #1505 from developing-today/queue/1481-1412-html-i…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done