Skip to content

id(web): a ProseMirror table with no header row saves as a plain paragraph — delimiter row never emitted #1044

Closed

Description

@dezren39

Bug

Saving a ProseMirror table whose first row is made of table_cell nodes (no table_header row) produces output that is not a table at all:

{"type":"doc","content":[{"type":"table","content":[
  {"type":"table_row","content":[
    {"type":"table_cell","content":[{"type":"paragraph","content":[{"type":"text","text":"x"}]}]}
  ]}
]}]}

prosemirror_to_markdown returns Ok("| x |\n") — a single pipe row with no delimiter row (| --- |). Re-parsing that output yields a plain paragraph: the table structure is silently destroyed on save, with no error.

Root cause: in json_to_ast's table_row arm (pkgs/id/src/web/markdown.rs, ~:1087), a row is marked as the header row only when its first cell is a table_header node. A table built entirely from table_cell rows therefore gets no header row in the comrak AST, and comrak's commonmark renderer emits no delimiter row for a headerless table. Nothing in the codec promotes the first row, synthesizes a header, or rejects the shape.

Evidence (verified 2026-10-26 on main cf60812)

Standalone probe crate carrying a verbatim copy of src/web/markdown.rs (comrak 0.56.0 locked) — log in goal hidden_files probe-2026-10-26-2145/out4.log, case t_table_no_header: SAVE4 table_no_header | ok="| x |\n". Markdown-parsed tables always carry a header row, so this only bites documents produced as ProseMirror JSON (editor variants without a header row, imports, collab/API writers) — exactly the inputs the save direction exists for.

Related but distinct: #1003 (two paragraphs inside one table cell fuse on save). This issue is about the table's header row, not cell contents.

Why it matters

A document containing a headerless table saves "successfully" while its table silently becomes a paragraph — structural data loss with no signal, the same failure family as the other codec save-direction losses (#1003, #1004, #1036).

Acceptance criteria

  • Saving a PM table whose rows are all table_cell preserves the table: either the first row is promoted to a header row (delimiter row emitted, re-parse yields an equivalent table), or the conversion returns a ConversionError — silently emitting a non-table is not acceptable.
  • Regression test in web::markdown: headerless PM table round-trips as a table (or errors, per the chosen behavior); header-row tables render byte-identically to before.
  • Existing web::markdown tests still pass.

Activity

  1. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  2. 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
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    on Oct 27, 2026
  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job
    • Session / ID: new-issue-creator cron run 2026-10-26 21:45 CDT
    • Found via: filed this run
    • Task: Verified with a standalone probe crate carrying a verbatim copy of src/web/markdown.rs (comrak 0.56.0) on main cf60812 (log hidden_files/probe-2026-10-26-2145/out4.log, case t_table_no_header): a PM table whose rows are all table_cell saves as Ok("| x |\n") — no delimiter row, so a re-parse yields a plain paragraph and the table is silently destroyed. Root cause: the table_row arm in json_to_ast (~markdown.rs:1087) marks a row as header only when its first cell is a table_header node; nothing promotes the first row or rejects the shape. Enqueued as a triage packet to repo-health-code-pilot — promote-vs-error is a triage decision.
    • Expectation: triage decides promote-first-row vs ConversionError; done when a headerless PM table no longer silently degrades to a paragraph on save, with a regression test.
    • Links: probe evidence in goal hidden_files (see issue body)
  4. 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 27, 2026
  5. 24 remaining items

  6. added
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Oct 27, 2026
  7. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  8. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Check-in — fleet monitor

  9. added
    queue/queuedHas a queued task in the Supabase agent queue
    and 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
  10. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: cron repo-fleet-monitor, run 2026-10-27 05:29 CDT
    • Found via: monitor reconciliation
    • Task: Escalation requeue — this item's fix PR has been open with no activity since ~04:20Z, its parked queue task sat ~7h past its s-class deadline, its named resume condition already fired (the clippy: panic! in #1043 MisplacedNode test (markdown.rs:1999) breaks clippy-lint on main for every PR #1245 clippy fix merged as PR fix(clippy): replace panic! in MisplacedNode test so clippy-lint passes (#1245) #1299 at 08:30Z), and the check-in posted at 09:20Z drew no response. The parked task was cancelled (reason: superseded) and the work requeued as a PR-shepherd task (see the new task in repo-health-code-pilot keyed to the PR). Labels reset to queue/queued + status/needs-triage. Next holder: read the PR's checks fresh, update the branch with main if CI needs it, merge if green, verify this issue auto-closes.
    • Expectation: effort/s; done = PR merged and issue verified closed.
  11. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: repo-fleet-monitor run 2026-10-27 12:23 CDT — Duty 1 safety-net reconciliation
    • Found via: monitor reconciliation
    • Task: safety-net reconciliation — this issue carries queue/queued but has NO live task in repo-health-code-pilot, and the monitor cannot re-enqueue it with a standard packet.
    • Expectation: operator repair (see below); the work itself is confirmed and routed in triage task 1230's progress (decision: promote first row to header).
    • Links: triage task 1230 (done), fix task 1233 (cancelled 2026-10-27T10:58Z), system limitation tracked in fleet(queue): repo-queue.py — add list/inspect, surface dedupe hits, document priority direction #1289

    Why it is stuck: repo-queue.py enqueue with either standard dedupe key for this issue returns the existing terminal row instead of creating a task — triage key → task 1230 (done), fix key → task 1233 (cancelled). Verified twice this run (17:52Z and 17:56Z). The prior monitor run (16:55Z) reported this issue enqueued as task 1469; no such pilot task exists — that enqueue was a dedupe hit on task 1230. Until the queue gains a requeue/revive path for dedupe-consumed issues (#1289), this item needs operator repair to get a live task; it is flagged prominently in the monitor's run summary.

  12. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · bound

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)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first looktask/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