Repository navigation
Description
Summary
richSchema in the id web editor declares no tight attr for bullet_list and no tight attr for ordered_list, even though the Rust markdown codec carries tight on both. Node.fromJSON silently drops undeclared attrs on editor load, and the codec's save side defaults a missing tight to false (loose). Net effect: every tight bullet or ordered list opened in the web editor is rewritten as a loose list on the next save — blank lines are inserted between every item. This is the same schema-mirror defect class as #1036 (task_list, fixed) and #1436 (code_block, fixed today in PR #1447); those two fixes mirrored their node types and left the plain list types behind.
Evidence (verified this run, main 1ff34134)
TS side — pkgs/id/web/src/editor.ts:46:
- The schema is built as
addListNodes(basicSchema.spec.nodes, "paragraph block*", "block")and then.update(...)is applied only tocode_block(:47) andimage(:73); the appended nodes (task_list,table,html_inline, …) include thetask_listmirror from id(web): loose task list items fuse on save — task_list hardcodes tight, second paragraph/code block destroyed #1036.bullet_list/ordered_list/list_itemare never updated. - In
prosemirror-schema-list@1.5.1(the version inpkgs/id/web/package.json),bulletListdeclares no attrs at all andorderedListdeclares only{order: {default: 1}}.
Probe — verbatim richSchema definition extracted from editor.ts (sha256 81a60bfd…), run under Bun on the laptop (~/git/fleet/probe-2026-10-27-laneA-lists, log hidden_files/probe-2026-10-27-laneA-lists/out.log in the goal workspace):
bullet tight=true IN : {"tight":true} OUT: null SPEC attrs: []
bullet tight=false IN : {"tight":false} OUT: null SPEC attrs: []
ordered order=3 tight=true IN : {"order":3,"tight":true} OUT: {"order":3} SPEC attrs: ["order"]
ordered order=3 tight=false IN : {"order":3,"tight":false} OUT: {"order":3} SPEC attrs: ["order"]
task_list tight=false (control, #1036) IN : {"tight":false,"order":null} OUT: {"tight":false,"order":null} SPEC attrs: ["tight","order"]
code_block params/htmlBlock (control, #1436) IN : {"params":"html","htmlBlock":true} OUT: {"params":"html","htmlBlock":true} SPEC attrs: ["params","htmlBlock"]
tight is dropped in both directions for plain lists; order survives; the two previously fixed node types survive as controls.
Rust side — pkgs/id/src/web/markdown.rs:
- Parse emits it: bullet
"attrs": {"tight": tight}(:267-268), ordered"attrs": {"order": list.start, "tight": tight}(:257-261). - Save requires it:
bullet_listarmlet tight = json["attrs"]["tight"].as_bool().unwrap_or(false);(:1193) andordered_listarm the same (:1205) — a missing attr becomes a looseNodeList, which comrak renders with blank lines between items.
So - a\n- b (tight) → editor load (attr dropped) → save → - a\n\n- b (loose). Loose lists survive only by accident, because the save-side default happens to be false.
Why it matters
Tight lists are the overwhelmingly common case. Any document containing one is silently reformatted — every item separated by a blank line — by a single open + save in the web editor, producing noisy whole-document diffs and changing list rendering semantics. It is silent data corruption of the same class the fleet has been fixing node type by node type (#1036, #1436); this is the remaining unmirrored node pair.
Fix direction
Mirror the codec attrs into richSchema following the #1036/#1436 pattern: .update("bullet_list", { attrs: { tight: { default: <see below> } }, … }) and .update("ordered_list", { attrs: { order: { default: 1 }, tight: { default: <see below> } }, … }), preserving the inherited content/group/parseDOM/toDOM fields exactly as the code_block update does. Choose the tight default deliberately (the codec's legacy-doc inference in the task_list arm at :1225 is the house precedent for absent-attr docs); a false default matches the codec's current save-side behaviour for editor-created lists.
Acceptance criteria
-
richSchema.nodeFromJSON→toJSONround-tripsbullet_listattrs.tight(trueandfalse) andordered_listattrs {order, tight}unchanged;orderbehaviour is not regressed. - A tight bullet list and a tight ordered list survive a full open → editor load → save cycle byte-identically (codec round-trip test or editor + codec integration evidence).
- Regression tests in
pkgs/id/web/src/editor.test.tsin the style of the id(web): PR #1024's htmlBlock provenance attr is dropped by the editor schema — HTML blocks still re-fence on editor save (#982 regression path) #1436code_blockblock (spec declares the attrs;{params…}-style fromJSON→toJSON cases for both list types). - Existing
editor.test.tsandweb::markdownsuites pass in CI; lists created fresh in the editor still save correctly (tight by default for single-paragraph items).
Dedup
Checked open + closed issues for tight / bullet_list / ordered_list schema mirroring this run: #1036 (closed) covered task_list only; #1436 (closed, PR #1447) covered code_block only. #1380 (ordered-list ) delimiter), #1397 (adjacent-list end comments), and #1370 (mixed task/plain lists) are different mechanisms in the Rust codec, not the editor schema.
Activity
developing-today commentedon Oct 27, 2026
CODE-545
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
tighton bullet_list andtight(+ existingorder) on ordered_list in pkgs/id/web/src/editor.ts following the id(web): loose task list items fuse on save — task_list hardcodes tight, second paragraph/code block destroyed #1036/id(web): PR #1024's htmlBlock provenance attr is dropped by the editor schema — HTML blocks still re-fence on editor save (#982 regression path) #1436 pattern, with editor.test.ts regression tests in the id(web): PR #1024's htmlBlock provenance attr is dropped by the editor schema — HTML blocks still re-fence on editor save (#982 regression path) #1436 style.~/git/fleet/probe-2026-10-27-laneA-lists, loghidden_files/probe-2026-10-27-laneA-lists/out.login the goal workspace115 remaining items
dezren39 commentedon Oct 28, 2026
🤖 Agent note · parked
dezren39 commentedon Oct 28, 2026
Resumed by Lane B worker (queue task 1504): PR #1559 CI re-checked fresh at 2026-10-28T07:52Z. All 30 constituent checks pass, including test-web-unit, test-web-typecheck, web-lint, test-unit and all nix flake sub-checks; the only outstanding check is the aggregate
nix flake checkjob (run 40634718086, job 122689852467), still queued after >5 minutes of polling. Parking again until that aggregate completes; merge immediately on resume when it is green.dezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up
Merge pull request #1559 from developing-today/queue/1504-1469-list-t…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done
nix flake checksummary job 122689852467 sat QUEUED with no runner assigned for 45+ min (infra stall, not a test result) — merged under the standing directive after verifying every constituent check green and the diff matches intent.