fix(docx): continue counters across numIds that share an abstract (#96)

MR
2mo ago
+151-13

Fixes #96

Summary

Heading and list numbering now continues across w:num instances that share one w:abstractNum, instead of restarting at every instance switch.

Root cause

Counters were keyed per numId. Word documents (and converter output) routinely split one logical outline across several numIds of the same abstract, so every direct-numPr vs style-inherited switch reset the sequence: the reported file renders "Section 4 → 4.1 → 1.1 → 4.2" with multiple "Section 1"s. Reapplying the style "fixes" it only because it removes the direct numPr, collapsing everything to a single numId.

Reproduced with a minimal OOXML fixture: two numIds sharing abstract 0 produce Section 1, Section 2, Section 1, 1.1 on this branch's base commit.

What changed

Counters keys state by the resolved abstract definition (after numStyleLink indirection). Entering through an instance carrying lvlOverride restarts exactly its overridden levels; plain instances continue the shared sequence. startOverride starts are honored as before.

Evidence this matches Word: the real-world text.docx fixture has consecutive items on numId 5 then numId 4 whose source text labels them "Roman starting at four", "Roman five" - previously rendered IV then I; now IV then V.

Snapshot updates (intentional, flagged)

  • handmade-numbering: "Two-one independent counter" now continues (5.) rather than restarting (1.), and the style-referenced paragraph follows the merged count (8.). Override cases (10., 7)) unchanged. If bare shared instances were meant to stay independent despite Word behavior, that contract can be restored behind an option -- but parity with Word is what #96 reports as missing.
  • text.docx / malformed variants: "I." becomes "V.", matching the document's own labels.

Verification

  • New regression test instances_sharing_an_abstract_continue_one_sequence (continues across n1→n2, restarts at startOverride=10).
  • Full suite: 287 passed incl. snapshots; clippy clean; fmt clean.

This change was prepared with AI assistance under human direction and review.


Summary by cubic

Continue DOCX list and heading counters across w:num instances that resolve to the same w:abstractNum, matching Word. Previously counters keyed by numId restarted on each instance switch; now they key by the resolved abstract and restart only levels overridden by w:lvlOverride/w:startOverride.

  • Key counters by abstract identity (after w:numStyleLink resolution). Track per-instance overridden levels and restart them on first use under that instance; plain instances continue the shared sequence.
  • Store the last active instance as Option<u64> to avoid sentinel collisions with legal max numId values.
  • Add regression tests instances_sharing_an_abstract_continue_one_sequence and override_restart_does_not_leak_into_the_next_instance. Update snapshots where shared instances now continue (e.g., "Two-one independent counter" -> 5., "I." -> "V."); override-driven restarts are unchanged.