Repository navigation
Description
id(web/markdown): HTML <img> width/height that aren't bare integers are silently dropped on markdown import — 200px, 50%, 200.0 sizing vanishes with no trace
Evidence (verified this run, main c58507a3)
pkgs/id/src/web/markdown.rs:1208-1217 (parse_img_tag, the #893 HTML-image import path):
if let Some(w) = attr("width")
&& let Ok(n) = w.parse::<u64>()
{
attrs["width"] = json!(n);
}
if let Some(h) = attr("height")
&& let Ok(n) = h.parse::<u64>()
{
attrs["height"] = json!(n);
}A dimension that fails the bare-u64 parse is not rejected, not preserved, and not reported — the attribute simply never enters the ProseMirror node. On the next save, prosemirror_to_markdown (markdown.rs:1953-1968) emits dimensions only from attrs.width/attrs.height, so the sizing is gone from the document permanently.
Verbatim-probe (laptop, ~/git/fleet/probe-2026-10-29-laneA-imgwidth/, parse_tag_attrs + parse_img_tag copied from main c58507a3):
| input | resulting attrs |
|---|---|
width="200" height="100" (control) |
width:200, height:100 — kept |
width="200px" height="100px" |
both dropped |
width="50%" |
dropped |
width="200.0" |
dropped |
width=" 200" (leading space) |
dropped |
width="0" |
kept as 0 |
width="+200" |
kept as 200 (Rust u64 parse accepts a leading +) |
200px is the form most of the world's HTML and Markdown-with-inline-HTML actually uses (CSS-style dimensions in exported notes, README badges, pasted web content); percentages are the standard responsive form. Both reach this parser through the raw-HTML image path (#893) and lose their sizing silently on the first load/save round-trip.
Why it matters
- Silent data loss on import: the document the user saved is not the document they wrote, and nothing anywhere says a dimension was discarded.
- The accept set is accidental, not designed:
+200and0pass while200px,200.0and" 200"fail — that isu64::from_str's grammar, not an image-sizing policy. - Sibling asymmetry: the editor's own parseDOM side of this same problem is id(web/editor): image parseDOM bare Number() lets NaN/fraction/hex dimensions through — pasted image sizing silently lost or reinterpreted on save #2010 (TypeScript, bare
Number()); this is the Rust markdown-import side, a separate code path with the opposite failure shape (drop instead of reinterpret).
Acceptance criteria
- Triage pins the dimension grammar for the markdown import path: which of
200px(strip apxsuffix),50%(percent — represent or refuse),200.0, and whitespace-padded values are accepted, and what happens to the rest. - A dimension the pinned grammar refuses is not silently dropped: it is either preserved in the node or the import surfaces it (the pinned behavior is recorded on this issue).
- Regression tests in
web/markdown.rscover the pinned grammar end-to-end (import → node attrs → markdown emission) for at least: bare integer (control),px-suffixed, fractional, and padded values. - No behavior change for images whose dimensions already parse as bare integers.
Dedup: #2010 is the TypeScript editor parseDOM path (bare Number() — NaN/fraction/hex pass through); #872 (closed) was substring attribute shadowing in this same function; #893 (closed) added the sized-image import form itself. None covers the Rust import-side dimension grammar.
Activity
developing-today commentedon Oct 29, 2026
CODE-1073
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
31 remaining items
fix(id/web): parse img dimensions as exact decimals, not via f64 (#2180)
Merge pull request #3408 from developing-today/queue/5201-2180-img-di…
dezren39 commentedon Nov 1, 2026
agent-note:v1
state: done
task: 5201 (repo-health-code-pilot, lane D)
stage: fixed_merged (resumed)
did: PR #3408 merged (merge commit 199d9f2); this issue auto-closed. This shift committed and pushed the exact-decimal dimension parser (f68c96e) left uncommitted by the prior lane, verified web::markdown 119/119 on the laptop, replied to and resolved both Codex P2 threads, and confirmed a fresh Codex round on the final head with no findings before merging.
resume: none — terminal.
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
awaiting_cicheckpoint.