Skip to content

id(web/editor): image parseDOM bare Number() lets NaN/fraction/hex dimensions through — pasted image sizing silently lost or reinterpreted on save #2010

Closed

Description

@dezren39

Problem

In the id web editor, the image node's parseDOM converts the width/height attributes with a bare Number() and the attrs declare no validation, so non-integer dimensions are stored verbatim and then silently dropped (or reinterpreted) downstream:

  • pkgs/id/web/src/editor.ts:145-146 — width: el.getAttribute("width") ? Number(el.getAttribute("width")) : null (same for height). The image attrs at editor.ts:125-126 are { default: null } with no validate, unlike ordered_list's order which at least declares validate: "number" (and still lets NaN through — id(web/editor): ordered_list parseDOM bare Number() lets NaN/fractional start through — pasted lists silently renumber from 1 on save #2005).
  • pkgs/id/web/src/editor.ts:157-158 — toDOM emits the attr only if (node.attrs.width): NaN and 0 are falsy, so they vanish; a fraction (2.5) or negative (-5) is emitted as the invalid attribute width="2.5" / width="-5".
  • pkgs/id/src/web/markdown.rs:1953-1954 — on save, json["attrs"]["width"].as_u64() is None for a fraction, a negative, and for NaN (which serialises to JSON null), so the dimension is dropped from the saved markdown with no error. The Rust-side HTML parse (markdown.rs:1208-1215) accepts only a pure u64, so the editor is the permissive side of an asymmetric pair.
  • pkgs/id/web/src/image-node-view.ts:58-62 — the node view sets img.style.width = ${width}px``; NaNpx / `-5px` are invalid and silently ignored by the browser.

Verified this run (2026-10-28, main 38f1f00f)

Probe on the laptop (~/git/fleet/probe-2026-10-28-laneB-imgdim/, the shipped getAttrs conversion copied verbatim; editor.ts sha256 4340aef4… identical on laptop and sandbox main), with the toDOM truthiness check and the save-side as_u64() semantics modelled from the shipped code:

  • Control width="200" → parseDOM 200, toDOM "200", saved 200 ✓
  • width="200px" → NaN → toDOM omits the attribute, saved null — dimension silently lost
  • width="50%" → NaN → same silent loss
  • width="2.5" → 2.5 → toDOM emits width="2.5", saved null — lost on save
  • width="-5" → -5 → toDOM emits width="-5", saved null — lost on save
  • width="abc" → NaN → silently lost
  • width="0x10" → 16 and width="1e3" → 1000 — silently reinterpreted as values the author never wrote (an HTML dimension attribute is a leading-digits value, not a JS numeric literal)

Pasted web content is the realistic source: images in copied HTML routinely carry width="200px", percentage widths, or fractional/negative junk from CMS markup, and every one of those pastes loses its sizing on the next save without any signal.

Why it matters

This is the same silent-coercion class as #2005 (ordered-list start) in the adjacent node of the same file, but with a different failure shape: the list renumbers, the image loses its dimensions (or gains ones nobody typed, 0x10 → 16). The repo's own strict parsers show the intended pattern — parseWorldSeed (web/src/world-seed.ts, #707/#832) accepts only decimal digits, and parseProxyPortValue does the same for the bin proxies (#483). The image attrs are an unhardened spot the earlier passes missed.

Acceptance criteria

  • Image width/height from parseDOM are either a non-negative integer or null: a value that is not a plain run of decimal digits (e.g. 200px, 50%, 2.5, -5, abc, 0x10, 1e3) never reaches the node attrs as NaN, a fraction, a negative, or a reinterpreted number — it is dropped to null at parse time (the triage-pinned contract may instead take the HTML leading-digits value, but it must be one documented behaviour, applied identically to width and height).
  • The editor-side result agrees with the save side: any dimension the editor keeps survives markdown.rs's as_u64() round-trip, and any it drops is dropped at parse time, not discovered missing after save.
  • Regression tests (web/src/editor.test.ts or a sibling) cover at least: "200" (kept), "200px", "50%", "2.5", "-5", "0x10" — asserting the resulting attrs; existing editor tests still pass.
  • The fix is scoped to the image node (the ordered-list site is id(web/editor): ordered_list parseDOM bare Number() lets NaN/fractional start through — pasted lists silently renumber from 1 on save #2005's, already filed).

Filed by new-issue-creator-code-b. Related: #2005 (same file, same bare-Number() class, ordered_list node), #893 (image parseDOM link handling).

Activity

  1. developing-today commented on Oct 29, 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 29, 2026
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b)
    • Session / ID: new-issue-creator-code-b run 2026-10-28 19:20 CDT
    • Found via: filed this run
    • Task: Filed this issue — image parseDOM bare Number() lets NaN/fraction/hex dimensions through; verified with a laptop probe on main 38f1f00, enqueued for triage.
    • Expectation: effort/xs; done = triage pins the dimension contract (drop-to-null vs leading-digits) and a fix with editor tests lands.
    • Links: probe ~/git/fleet/probe-2026-10-28-laneB-imgdim/ (laptop)
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 125 remaining items

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

    @dezren39
    MemberAuthor

    agent-note:v1
    state: parked
    task: 5066
    lane: a (repo-queue-worker-shift)
    note: PR #4201 verified this shift: targeted editor tests 117/117 pass on the laptop @ 66aea20; full pkgs/id/web suite 589 passed with only the 4 pre-existing main failures (proven identical on clean base 77970eb; tracked by #4103 and #3243). Codex review on 66aea20: no findings. Parked 20m because basic+native CI checks remain QUEUED (runner delay, run 41394912384) after a 5-minute poll. Resume: merge PR #4201 when checks green, close #2010, complete task 5066 as fixed_merged.

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  9. added
    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 Nov 2, 2026
  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Nov 2, 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/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/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