Skip to content

id(web): nested marks corrupt on save — em/strike/code inside strong/em gain   entities and multiplied delimiters, several non-fixpoint #968

Closed

Description

@dezren39

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

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 26, 2026
  2. developing-today commented on Oct 26, 2026

    @developing-today
    Contributor
  3. added
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    on Oct 26, 2026
  4. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job
    • Session / ID: new-issue-creator run 2026-10-26 08:15 CDT
    • Found via: filed this run
    • Task: Filed nested-mark save corruption in the id web markdown codec (verified probe on main aa0d6d1, verbatim codec in a standalone crate: **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.
    • Expectation: effort/m — done when nested-mark round-trips are byte-stable fixpoints with no   entities, with double round-trip unit tests.
    • Links: packet enqueued to repo-health-code-pilot this run
  5. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · update

    • Agent: new-issue-creator job
    • Session / ID: new-issue-creator run 2026-10-26 08:15 CDT
    • Found via: same probe batch, after filing
    • Task: Two more reproduced cases in the same nested-mark family, recorded here instead of as separate issues: (1) 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.
  6. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. added
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 26, 2026
  8. 29 remaining items

  9. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Oct 26, 2026
  10. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  11. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    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.rs conflicted. 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-arg append_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.

    {"v":1,"kind":"note","state":"parked","task_id":1173,"summary":"PR 1026 was green but conflicted with main in markdown.rs. Merged main 5709db9f, resolved 2 hunks keeping main #1031/#1032 logic on the branch 3-arg append_inline_child; probe suite 75/75 pass, rustfmt clean. Merge commit cdf990f1 pushed; parked for CI rerun.","artifacts":["https://github.com/developing-today/code/pull/1026"],"next":"merge PR 1026 when CI green on cdf990f1, verify #968 auto-close, complete task 1173"}
    
  12. added a commit that references this issue on Oct 27, 2026
  13. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    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.

    {"v":1,"kind":"note","state":"done","task_id":1173,"summary":"PR 1026 merged 20e1a23c; #968 auto-closed. CI fully green on cdf990f1 (27/27); probe 75/75 at merge-resolution time.","artifacts":["https://github.com/developing-today/code/pull/1026"],"next":"none — task 1173 complete"}
    
  14. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:donetask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions