Skip to content

id(web): bold links flip spelling on every save — strong+link marks have no round-trip fixpoint #944

Closed

Description

@dezren39

Problem

A bold link in a markdown file flips its markdown spelling on every save in the id web editor — the markdown ↔ ProseMirror codec has no fixpoint for text carrying both strong and link marks:

  • File contains **[text](https://e.com)** → open + save emits [**text**](https://e.com)
  • That output, opened + saved again, emits **[text](https://e.com)** again
  • …and so on, forever. Every save of a document containing a bold link produces a spurious diff, even if the user changed nothing near the link.

Verified probe on main a2eef6f7 (standalone crate compiling the verbatim pkgs/id/src/web/markdown.rs codec from main; logs in the goal's hidden_files probe-2026-10-25-2312/probe2.log and probe3.log):

Root cause (pointers)

The two directions disagree about mark nesting order for links:

  • Parse direction: convert_node's NodeValue::Link arm (markdown.rs:278) appends the link mark after marks already active from enclosing nodes (e.g. Strong, markdown.rs:268), so **[text](url)** yields marks [strong, link].
  • Save direction: create_marked_text / build_mark_chain (markdown.rs:1117 / 1167) treat the marks array as [innermost, …, outermost] and wrap in array order — for [strong, link] that builds Link(Strong(text)), which comrak renders as [**text**](url), whose reparse yields the opposite nesting.

One of the two directions must change so that parse(emit(doc)) is a fixpoint for link+emphasis/strong combinations (or the PM marks must be canonicalised to a single agreed order in both directions).

Why it matters

Non-idempotent save means perpetual dirty diffs and needless revision churn for any doc with bold/italic links — a common pattern — and it makes "did my edit change anything else?" unanswerable from the diff. Same codec-fidelity family as #880/#881/#882, but a stability bug rather than data loss.

Acceptance criteria

  • **[text](url)** and [**text**](url) each reach a fixpoint: open → save → open → save produces byte-identical markdown after the first save, for both spellings.
  • Same fixpoint holds for em+link (*[text](url)* / [*text*](url)).
  • Semantics unchanged: the link href/title and the strong/em emphasis survive every round-trip.
  • New unit tests in pkgs/id/src/web/markdown.rs assert two consecutive round-trips are identical for the strong+link and em+link cases.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    on Oct 26, 2026
  2. developing-today commented on Oct 26, 2026

    @developing-today
    Contributor
  3. added
    queue/queuedHas a queued task in the Supabase agent queue
    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-25 23:15 CDT (America/Chicago)
    • Found via: filed this run
    • Task: strong+link marks have no round-trip fixpoint in the markdown codec: parse (markdown.rs:278 Link arm appends link after active marks) and save (create_marked_text/build_mark_chain, markdown.rs:1117/1167, treats marks as innermost-first) disagree on nesting order. Verified this run with a standalone crate compiling the verbatim codec from main a2eef6f: **[text](url)** -> save [**text**](url) -> save **[text](url)** again, STABLE: false in both directions (logs: goal hidden_files probe-2026-10-25-2312/probe2.log, probe3.log). Queued for the code pilot queue (small_fix packet).
    • Expectation: effort/s — done looks like a canonical mark-nesting order agreed by both directions, plus two-round-trip fixpoint unit tests for strong+link and em+link.
    • Links: n/a yet (queue packet enqueued this run)
  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 26, 2026
  6. 6 remaining items

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

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. added a commit that references this issue on Oct 26, 2026
  10. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    • Agent: queue worker (repo-worker-code)
    • Session / ID: queue task 1091 (resume), cron repo-queue-worker-shift 2026-10-26 11:28 CDT
    • Found via: queue claim (resume of parked task)
    • Task: Done — PR fix(id-web): canonical mark nesting — strong/em+link round-trip fixpoint (#944) #985 merged (merge commit e1f0121) after CI went fully green; this issue auto-closed by the merge.
    • Expectation: met: strong/em/strikethrough+link spellings now reach a round-trip fixpoint; two-save fixpoint tests in PR.
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/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions