Repository navigation
Description
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 isvalidate: "number"(editor.ts:115), and ProseMirror's string validator checks onlytypeof value(prosemirror-modelvalidateType:typeof NaN === "number"), so NaN passes.editor.ts:105toDOMthen renders anyorder !== 1asstart: NaN— an invalid HTML attribute.- On save, the node JSON carries
order: null(JSON.stringify(NaN)), and the Rust markdown codec reads it asjson["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_u64on a fractional JSON number is alsoNone→ silently renumbered from 1 on save.start="0x10"→ reinterpreted as 16,start="1e2"as 100 — forms an HTMLstartattribute never means (the sibling task-list parser ateditor.ts:196correctly uses theel.startIDL 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_listparseDOMgetAttrs 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. Usingel.start(as the task-list parser ateditor.ts:196does) 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 thestart="5"control. - If triage includes them, the image
width/heightattrs ateditor.ts:145-146get 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
developing-today commentedon Oct 28, 2026
CODE-926
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
22 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · parked
parseOrderedListStart— only a plain non-negative safe-integer start is preserved (0 included); malformed/fractional/negative/hex/scientific/empty/unsafe values fall back to 1. Laptop verification: vitestsrc/editor.test.ts85/85 pass; the singletsc --noEmiterror is pre-existing on clean main insrc/image-upload.test.ts. Parked task 5069 for 20 minutes because PR fix(id-web): validate ordered_list start in parseDOM (#2005) #4078 CI (basic guard PR check list, native arm64 world snapshot determinism) was still pending after the 5-minute poll window and no Codex review had posted yet.Merge pull request #4078 from developing-today/queue/5069-2005-ordere…
dezren39 commentedon Nov 2, 2026
Agent note — done (task 5069, lane E resume)
editor.tsparseDOM +editor.test.ts);parseOrderedListStartpreserves plain non-negative safe integers (0 included), falls back to 1 for malformed/fractional/negative/hex/scientific/empty/unsafe. Lane B laptop run: vitestsrc/editor.test.ts85/85 pass; the onetsc --noEmiterror is pre-existing on clean main (src/image-upload.test.ts). Codex review of head cbb749b completed with zero findings. PR CI still pending in the Actions backlog at merge (standing merge authority: CI runs on main; targeted tests verified).