Repository navigation
Description
Summary
In the id web editor's markdown ↔ ProseMirror codec (pkgs/id/src/web/markdown.rs), any nested mark combination corrupts on save: when the active mark set changes mid-run (emphasis inside strong, strong inside emphasis, strikethrough or inline code nested inside strong/em, emphasis inside a link), the emitted markdown gains   HTML entities in place of spaces and multiplied emphasis delimiters. Several cases are not a fixpoint — every subsequent save makes the output worse.
Single-mark and sibling cases are fine (plain **strong**, *em*, ~~strike~~, `code`, and ***both*** all round-trip cleanly), which is why the existing tests miss this: the corruption only appears when adjacent text nodes carry different overlapping mark sets.
Evidence (verified this run on main aa0d6d1)
Standalone probe crate compiling the verbatim codec from pkgs/id/src/web/markdown.rs (current main), markdown → ProseMirror → markdown, twice:
| Input | After 1st save | After 2nd save |
|---|---|---|
**bold _em_ end** |
**bold *****em***** end** |
**bold *******em******* end** (keeps growing) |
*em **strong** end* |
*em ****strong**** end* |
stable in corrupted form |
**bold ~~strike~~ end** |
**bold **~~**strike**~~** end** |
**bold ****~~strike~~**** end** (changes again) |
**bold \code` end**` |
**bold **\code` end` |
— |
*em \code` end*` |
*em *\code` end` |
— |
**_em_ rest** |
***em***** rest** |
— |
[link *em* text](https://e.com) |
[link ](https://e.com)*[em](https://e.com)*[ text](https://e.com) |
[*em*] migrates inside the link on the next save (not a fixpoint) |
**a *b \c` d* e**` |
**a *****b ***\c` d** e**` |
— |
The parsed ProseMirror is correct in every case (e.g. **bold _em_ end** parses to text nodes bold [strong], em [strong, em], end [strong]) — the corruption is introduced on the emit side.
Probe logs: goal workspace hidden_files/probe-2026-10-26-0815/out.log and out2.log.
Suspected area
prosemirror_to_markdown's inline emission: create_marked_text (markdown.rs:1117) and build_mark_chain (markdown.rs:1167). When consecutive text nodes have overlapping-but-different mark sets, the chain appears to close/reopen marks in an order that leaves boundary text nodes whose leading/trailing spaces comrak then renders as  , and re-parsing the result produces yet more mark boundaries — hence the per-save growth.
Related but distinct: #944 (strong+link mark-order oscillation — no nesting required), #953 (code+link drops the link via the code early-return at markdown.rs:1122). This issue reproduces with no link and no code mark at all (pure em+strong), so it is not a duplicate of either; a fix should be checked against both.
Why it matters
Nested emphasis is ordinary writing (**bold with *italic* inside**). Today, opening such a file in the web editor and saving it — even without edits — rewrites it into entity-escaped, delimiter-doubled text, and repeated saves compound the damage. This is silent data corruption of user documents.
Acceptance criteria
-
**bold _em_ end**,*em **strong** end*,**bold ~~strike~~ end**,**bold \code` end**,em rest, andlink em text` each round-trip open → save byte-identical (modulo the codec's documented normalizations), and a second round-trip is a fixpoint. - No
 (or other HTML entities for ordinary spaces) appear in emitted markdown for these inputs. - New unit tests in
pkgs/id/src/web/markdown.rscover nested-mark round-trips, including the fixpoint (double round-trip) property. - Existing codec tests still pass, and the id(web): bold links flip spelling on every save — strong+link marks have no round-trip fixpoint #944 / id(web): inline code combined with another mark drops the other mark on save — code+link loses the link #953 cases do not regress.
Activity
developing-today commentedon Oct 26, 2026
CODE-165
dezren39 commentedon Oct 26, 2026
🤖 Agent note · filed
**bold _em_ end**->**bold *****em***** end**, growing again on the second save; 8 nested combinations reproduced); queued as a triage packet for the queue worker. entities, with double round-trip unit tests.dezren39 commentedon Oct 26, 2026
🤖 Agent note · update
x *em *nested* end* y->x *em **_nested_** end* y(em inside em, same symptom); (2)[~~old~~ new](https://e.com)->~~[old](https://e.com)~~[ new](https://e.com)(strikethrough nested in a link leaks outside the link text on save). Both are mark-set-change boundaries in the same emit path; a fix for this issue should be regression-checked against them. Probe log: goal hidden_files probe-2026-10-26-0815/out3.log.dezren39 commentedon Oct 26, 2026
🤖 Agent note · parked
cargo test --features web --libfirst fails on the pre-existing RustEmbed prerequisite (pkgs/id/web/dist must exist), and after creating dist the id lib-test compile was SIGKILL'd (OOM) in this sandbox. The creator's standalone-crate probe (verbatim markdown.rs + comrak only) is the proven lightweight route.29 remaining items
dezren39 commentedon Oct 26, 2026
🤖 Agent note · parked
append_inline_childpeels pure mark chains and tail-inserts the leaf, reusing the tail wrapper for the shared mark (subsumes the id(web): inline code combined with another mark drops the other mark on save — code+link loses the link #953 merge; all three append sites). PR fix(id/web): re-nest nested-mark runs on save instead of fragmenting spans (#968) #1026 @b37a2314.web::markdowntests pass, including the 5 previously-corrupt cases byte-identical + fixpoint and the clean-set regression guards; rustfmt clean.queue/1173-968-nested-mark-emit.dezren39 commentedon Oct 27, 2026
Resumed by queue worker (Lane A), task 1173: PR #1026 CI was fully green on b37a231, but the PR had become unmergeable — main moved under it and
pkgs/id/src/web/markdown.rsconflicted. Merged current main (5709db9) into the branch and resolved both hunks: main's newer #1031 footnote-definition block handling and #1032 bullet-list coalescing were kept, adapted to this branch's 3-argappend_inline_child(the #968 mark-chain re-nesting signature). Verified the merged file in a standalone verbatim-codec probe (comrak 0.56): 75/75 unit tests pass (nested-mark matrix, footnote, coalesce, table and task-list suites), rustfmt clean. Merge pushed as cdf990f; parked for the CI rerun.Merge pull request #1026 from developing-today/queue/1173-968-nested-…
dezren39 commentedon Oct 27, 2026
Done — queue worker (Lane A), task 1173: PR #1026 merged (merge commit 20e1a23), issue auto-closed. CI on the main-merged head cdf990f was fully green — all 27 checks pass, including test-unit, all shards, clippy, fmt and the web suites — on top of the local verbatim-codec probe at 75/75 from the conflict-resolution pass.
fix(id): hoist delimiter-mark edge spaces touched by word chars on sa…