Repository navigation
Description
Problem
In pkgs/id's web markdown codec, a delimiter-mark span (em, strong, strikethrough) whose text starts or ends with a space loses its mark on the next open whenever a word character touches the span on that same side. The save emits the   entity form inside the delimiters (e.g. a* b*), and that form cannot reparse as emphasis: the delimiter run is followed by & (punctuation, the entity start) and preceded by a word character, so it fails CommonMark's flanking rules for opening emphasis. On reopen the mark is gone and the delimiters remain as literal text — a* b* — and a second save escapes them (a\* b\*). The codec's own output is not readable by its own parser for these shapes.
Verified on main 37418e56 with a verbatim-codec probe crate (comrak 0.56.0, compiled and run on the laptop; hidden_files/probe-2026-10-27-0927-laneA-out4.log). PM documents below are editor shapes (a user selects a space-leading word mid-sentence and italicizes it):
| PM paragraph | Saved | After reopen |
|---|---|---|
a + em( b) |
a* b* |
plain text a* b* — mark destroyed, literal *s remain |
a + strong( b) |
a** b** |
plain text a** b** |
a + strikethrough( b) |
a~~ b~~ |
plain text (same failure) |
a + em(b ) + c |
a *b *c |
plain text a *b *c |
a + strong+em( b) |
(same   form) |
mark destroyed |
heading a + em( b) |
# a* b* |
mark destroyed (same) |
Controls in the same probe round-trip PM-identical and byte-fixpoint, which pins the trigger to the outside neighbor on the space-carrying side: span at paragraph start/end, neighbor is a space or punctuation, or the span is padded on both edges with spaced neighbours (a * b * c survives). An authored-markdown control confirms the mechanism at the parser level: the literal markdown a* b* parses to plain text, while a * b* (space before the delimiter) parses to em( b).
Root cause
Save direction, pkgs/id/src/web/markdown.rs: create_marked_text (:1737) builds the mark wrappers (em/strong/strikethrough arms :1762-1764) around the run's full text, keeping a leading/trailing space inside the wrapper; comrak's format_commonmark then renders that edge space as an   entity inside the delimiters. Nothing in the codec checks whether the emitted delimited form can reparse — with a word character immediately outside the delimiter, it cannot (flanking rules above), so the first save writes a file the editor itself will misread on the next open.
Why it matters
The shape is ordinary editor output: select b (with its leading space) inside a sentence and press Ctrl+I, or apply bold to the last word of a sentence including the space before it. The loss is silent, destroys the formatting, and injects literal */~~ characters into the visible text; because the result is not a fixpoint, later saves compound it with backslash escapes. Siblings #1407/#1412 also leak   entities but have different root causes (marks around an image node; marks spanning html_inline nodes) — this one is a single flat span with no other nodes involved.
Acceptance criteria
- The full matrix round-trips PM-identical and byte-fixpoint: em, strong, strikethrough (and em+strong combined) × leading-edge space and trailing-edge space × word-character neighbour on that side, in paragraphs and headings.
- The fix preserves the rendered document (e.g. hoisting the edge space out of the mark wrapper — the space becomes unmarked, which renders identically — or another representation that reparses to the same PM doc); it must not regress the currently-working cases (span padded on both edges, space/punctuation/boundary neighbours) or the id(web): inline code combined with another mark drops the other mark on save — code+link loses the link #953/id(web): nested marks corrupt on save — em/strike/code inside strong/em gain   entities and multiplied delimiters, several non-fixpoint #968/id(web): marked spans crossing a soft line break corrupt or fragment on save — single-mark, non-fixpoint #981 mark tests.
- Regression tests in
pkgs/id/src/web/markdown.rscover the matrix in both directions; existingweb::markdowntests pass.
Related: #1407, #1412 (different   mechanisms), #968 (nested-mark fragmentation, closed), #981 (softbreak-crossing spans, closed).
Activity
developing-today commentedon Oct 27, 2026
CODE-508
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
65 remaining items
fix(id): normalize unmarked text-run edge whitespace at save (#1420) (#…
fix(id): hoist delimiter-mark edge spaces touched by word chars on sa…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done
cargo-fmt-checkSUCCESS; the aggregatenix flake checkjob completed SUCCESS after the runner backlog cleared). Merge commit dd9eb97.