Skip to content

id(web): unreferenced footnote definitions are silently deleted on save — a definitions-only document saves as an empty file #1565

Closed

Description

@dezren39

Bug

In the id web editor's markdown ↔ ProseMirror codec (pkgs/id/src/web/markdown.rs), a footnote definition that no [^label] reference in the document points at is silently deleted on save:

Hello world.

[^a]: orphan definition

loads and saves as just:

Hello world.

The extreme case is total: a document consisting only of footnote definitions (a shared notes page collecting definitions, or a draft whose references are added later)

[^a]: just a definition

loads as the empty-document placeholder paragraph and saves as an empty file ("").

Definitions that are referenced survive — the codec has dedicated footnote_reference / footnote_definition nodes for them (#945, #1031) and they round-trip. The loss is specifically: any definition whose label no reference in the current document uses. With two definitions where only one is referenced, the referenced one round-trips and the unreferenced one vanishes in the same save.

Verified repro (this run, main db6f7d0)

Standalone probe crate compiling the verbatim pkgs/id/src/web/markdown.rs (md5 8478c0d4fdcb251801a8efc83379f213, identical on sandbox main and the laptop copy) against comrak 0.56 + serde_json, built and run on the laptop (~/git/fleet/probe-2026-10-27-creator-a2 there; logs in the goal's hidden_files/probe-2026-10-27-creator-a2/probe_out3.log):

  • orphan-footnote-def-with-text: "Hello world.\n\n[^a]: orphan definition\n" → PM is a single paragraph Hello world. (the definition appears nowhere) → save returns "Hello world.\n".
  • only-footnote-def: "[^a]: just a definition\n" → PM is {"content":[{"type":"paragraph"}]} (the empty-doc placeholder) → save returns "".
  • two-defs-one-referenced: "text[^a]\n\n[^a]: used\n\n[^b]: unused\n" → PM carries only definition a → save drops [^b]: unused.
  • footnote-def-only-multiline: a two-paragraph unreferenced definition likewise saves as "".
  • Layer isolation: a raw comrak parse_document of "[^a]: just a definition\n" (footnotes extension on, exactly as commonmark_options(), markdown.rs:81-91) yields zero AST children, and "Hello.\n\n[^a]: orphan\n" yields only the Paragraph — comrak never emits a FootnoteDefinition node for an unreferenced definition, so the codec's parse direction (the NodeValue::FootnoteDefinition arm, markdown.rs:559-575) never sees it. The codec inherits the loss from its parser, exactly as id(web): unused link reference definitions are silently deleted on save — a definitions-only document saves as an empty file #1004 does for link reference definitions.

Relation to #1004 (not a duplicate)

#1004 tracks the same phenomenon for link reference definitions ([label]: url), which have no PM representation at all. Footnote definitions are a different construct with a different fix surface: the PM schema and editor already represent them (#945/#1031, and #1347 tracks their editor-schema wiring), referenced definitions already round-trip, and a source pre-scan fix for #1004 would not automatically know about [^label]: definition syntax. This issue tracks the footnote half so neither fix silently leaves the other construct losing data.

Why it matters

Keeping footnote definitions at the foot of a note before (or without) referencing them is a normal authoring pattern, and footnote-heavy documents are exactly the ones this codec added dedicated support for. Opening such a file in the id web editor and saving — even with no edit — destroys every unreferenced definition with no warning. Same silent-loss-on-save family as #996 (empty links) and #1004 (unused reference definitions), in a distinct mechanism.

Suggested direction (triage to decide)

Mirroring #1004's options: (a) preserve unreferenced definitions verbatim — the parse direction would need a source pre-scan for [^label]: definitions, diff them against the labels comrak actually emitted, and carry the leftovers (e.g. as a trailing raw block the editor displays but does not eat); (b) if neither preservation nor a PM representation is acceptable for the unreferenced case, document the limitation in the codec's module docs (top of markdown.rs) so the loss is a known, stated behaviour instead of a silent one. Doing nothing silently is the only unacceptable outcome. If a shared pre-scan lands for #1004, extending it to footnote definitions in the same change would close this issue by reference.

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
    on Oct 28, 2026
  2. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  3. added
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/mMedium: expected under 1 day of work
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  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 28, 2026
  6. 345 remaining items

  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — task 1829 picked up (lane A shift, 2026-11-01). Fresh-verified: PR #2935 OPEN at d5db5ec, issue #1565 OPEN, no hard-stop labels, 8 unresolved Codex P2 threads (five 14:35Z + three 15:08Z). Starting fix round 29 in worktree ~/git/fleet/1829-1565 on branch queue/1829-1565-footnote-prescan: disposition all 8 fail-first, then web::markdown suite, thread replies/resolution, @codex re-review.

  8. chatgpt-codex-connector commented on Nov 1, 2026

    @chatgpt-codex-connector
  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 Nov 1, 2026
  10. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — task 1829 parked (lane A shift, 2026-11-01) for Codex re-review of PR #2935 head 1c762d6. Fix round 29 dispositioned all 8 unresolved P2 threads (replied + resolved): container-relative indentation gates for footnote HTML openings and fence closers, reference-collector footnote state derived from the opening body, multiline labels stop at interrupting blocks, unescaped ( rejected in parenthesized titles, whitespace required after angle destinations, sibling list items end list-scoped fences, space/tab-only blank-line predicate. 8 new regression tests; suite 202 passed / 0 failed on the laptop. Resume: if the re-review is clean, merge PR #2935 and close #1565; if new findings land, fix round 30.

  11. added a commit that references this issue on Nov 1, 2026
  12. 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 Nov 1, 2026
  13. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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)effort/mMedium: expected under 1 day of workkind/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