Skip to content

id(web/editor): ordered_list parseDOM bare Number() lets NaN/fractional start through — pasted lists silently renumber from 1 on save #2005

Closed

Description

@dezren39

Summary

The web editor's ordered_list DOM parser (pkgs/id/web/src/editor.ts:100) reads the list start with a bare Number(el.getAttribute("start")). Non-numeric and fractional start values from pasted HTML therefore enter the ProseMirror node unchecked:

  • start="abc" → order = NaN. The schema's only guard is validate: "number" (editor.ts:115), and ProseMirror's string validator checks only typeof value (prosemirror-model validateType: typeof NaN === "number"), so NaN passes.
  • editor.ts:105 toDOM then renders any order !== 1 as start: NaN — an invalid HTML attribute.
  • On save, the node JSON carries order: null (JSON.stringify(NaN)), and the Rust markdown codec reads it as json["attrs"]["order"].as_u64().unwrap_or(1) (pkgs/id/src/web/markdown.rs:1844-1845) — the list is silently renumbered from 1.
  • start="2.5" → order = 2.5 (same validate pass, start="2.5" rendered) → as_u64 on a fractional JSON number is also None → silently renumbered from 1 on save.
  • start="0x10" → reinterpreted as 16, start="1e2" as 100 — forms an HTML start attribute never means (the sibling task-list parser at editor.ts:196 correctly uses the el.start IDL property, which follows HTML's integer parsing rules).

So a pasted ordered list whose start is not a plain integer either displays with a bogus start attribute or loses its numbering entirely on the next save, with no signal at any step. The control case works end to end: start="5" → order 5 → rendered 5 → saved 5.

Evidence (verified this run)

Laptop probe (~/git/fleet/probe-2026-10-28-laneB-olstart/, node, main 6a284efd) evaluating the verbatim editor.ts:100 expression, the vendored prosemirror-model validateType semantics, the editor.ts:105 toDOM branch, JSON round-trip, and the markdown.rs:1845 as_u64().unwrap_or(1) read:

start="5"    -> order=5   validate=ok toDOM start=5     json=5    rust_save_order=5
start="abc"  -> order=NaN validate=ok toDOM start=NaN   json=null rust_save_order=1
start="2.5"  -> order=2.5 validate=ok toDOM start=2.5   json=2.5  rust_save_order=1
start="-3"   -> order=-3  validate=ok toDOM start=-3    json=-3   rust_save_order=1
start="0x10" -> order=16  validate=ok toDOM start=16    json=16   rust_save_order=16
start="1e2"  -> order=100 validate=ok toDOM start=100   json=100  rust_save_order=100
(no attr)    -> order=1   validate=ok toDOM (no attr)   json=1    rust_save_order=1

Source lines: editor.ts:100 (bare Number()), editor.ts:111-116 (order: { default: 1, validate: "number" }), editor.ts:105 (toDOM), markdown.rs:1844-1845 (as_u64().unwrap_or(1)). The image attrs at editor.ts:145-146 use the same bare Number() for width/height ("100px" → NaN, silently dropped by the falsy check in toDOM) — same fix shape, worth covering in the same change if triage agrees.

This is the parser-strictness class the web code has been fixing one site at a time: go-to-line strict validation (#712), world seed digits-only (#832), identity refresh finite check (#847). This parseDOM site is unguarded.

Why it matters

Paste is how existing documents arrive in the editor. A list pasted from a web page or another editor with an unusual start keeps its numbering on screen (or shows NaN) and then silently renumbers from 1 when saved — the saved markdown no longer says what the user saw and confirmed. Silent reinterpretation (0x10 → 16) is the same class of harm in the other direction.

Acceptance criteria

  • The ordered_list parseDOM getAttrs produces only a valid list start: a finite integer the save codec can represent (non-negative safe integer), or falls back to 1 — never NaN, a fraction, or a hex/scientific reinterpretation. Using el.start (as the task-list parser at editor.ts:196 does) plus a range/integer check is an acceptable shape; triage pins the exact fallback for negative starts, which markdown cannot represent.
  • A pasted <ol start="5"> still round-trips: order 5 in the node, start="5" rendered, 5 in the saved markdown.
  • Tests in editor.test.ts (or the schema's test home) cover at least: start="abc", start="2.5", start="0x10", and the start="5" control.
  • If triage includes them, the image width/height attrs at editor.ts:145-146 get the same treatment ("100px"/non-numeric → null, plain integer preserved).
  • Existing web unit tests pass.

Found by new-issue-creator lane B, 2026-10-28 run. Dedup: gh search issues for "ordered list start", "parseDOM start", "ol start NaN editor" (open + closed) returned no matching issue. Related but distinct closed issues: #1469 (lists lose their tight flag — a different attr) and #997 (ordered task lists rewritten as bullet lists on save — the contains-task-list branch at editor.ts:196, not the plain ordered_list getAttrs at editor.ts:100 covered here).

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator-code-b
    • Session / ID: new-issue-creator-code-b · 2026-10-28 18:50 CDT run
    • Found via: filed this run
    • Task: Filed verified bug: ordered_list parseDOM bare Number() lets NaN/fractional start through, pasted lists silently renumber on save.
    • Expectation: effort/xs — triage pins fallback for negative starts, then fix + editor tests.
    • Links: body hidden_files/issue-code-editor-ol-start-body.md; probe ~/git/fleet/probe-2026-10-28-laneB-olstart/ (laptop)
  4. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  5. added
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  6. 22 remaining items

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 1, 2026
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  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 · parked

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

    @dezren39
    MemberAuthor

    Agent note — done (task 5069, lane E resume)

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/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobtask/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