feat: write bullets and numbering through the page model

A paragraph's list references and the list definitions of the page model are
differences the page writer now lowers. A definition new to the section
becomes a native list node (jcid 0x60012) carrying the format string, list
font, restart, bullet index and the definition's text formatting; a changed
definition is rewritten in place; a definition another paragraph already owns
is copied, since native list nodes belong to one paragraph; a dropped
reference leaves the node unreferenced. Bullet definitions name their glyph
and font and carry the cleared flag every native bullet node has. List nodes
are allocated like other new objects and renamed to the model's identities in
squash: a node written directly under a model identity had the null compact
identity and failed OneNote's integrity check on open.

The three-way merge carries definitions the local side added or changed, so
a local bullet merges with a remote edit elsewhere. The model mutation driver
toggles bullets, and its round-trip projection coalesces runs that differ
only in attributes the writer leaves as stored, such as the language tag.

corpus/list-edit is the native gate: a Rust-authored page with a bullet, two
numbered paragraphs, a nested numbered child, a restart at three and plain
paragraphs, cold-read by OneNote 2010 with the expected glyph and numbering
text; tools/test_list_edit.py checks it without a VM.

Assisted-by: claude-fable-5.1
8a86e3ac0fclover caruso committed on 9/13/2026, 12:00:32 AMparentb6b6e90
24 files changedLine totals unavailable