Commit 884c7fb
fix(pm-adapter): emit pending section break before TOC/docPartObj SDTs (SD-2557) (#2872)
* fix(pm-adapter): emit pending section break before TOC/docPartObj SDTs (SD-2557)
When a Word document stores a `w:sectPr` on a paragraph immediately before a
TOC (or other `docPartObj`) SDT, the section break was dropped. The TOC
ended up rendering on the same page as the prior section's content instead
of starting a new page as Word does.
Root cause in pm-adapter:
- `findParagraphsWithSectPr` (sections/analysis.ts) recurses into `index`,
`bibliography`, and `tableOfAuthorities` to count their children as
paragraphs, but NOT into `documentPartObject` / `tableOfContents`.
- As a result, section ranges treat a TOC SDT as a single opaque unit —
its children don't occupy paragraph indices.
- When processing flow, `handleParagraphNode` / `handleIndexNode` /
`handleBibliographyNode` / `handleTableOfAuthoritiesNode` each emit a
pending section break before the paragraph whose index matches
`nextSection.startParagraphIndex`.
- `handleDocumentPartObjectNode` and `handleTableOfContentsNode` did NOT
run this check, so the deferred break only fired on the next body
paragraph AFTER the SDT. The SDT's content rendered in the PREVIOUS
section, with no page break before it.
Fix:
- Add `emitPendingSectionBreakForParagraph(sectionState, nextBlockId,
blocks, recordBlockKind)` helper in sections/breaks.ts that centralizes
the "check, emit, advance" pattern.
- Call the helper at the top of `handleDocumentPartObjectNode` and
`handleTableOfContentsNode` — once per SDT. Since the SDT's children
don't affect `currentParagraphIndex` (`findParagraphsWithSectPr` skips
them), the check fires correctly at the SDT boundary: if the SDT sits
at a section boundary, the nextPage break is emitted so the SDT renders
on a new page.
- No changes to section-range computation — counting stays consistent.
Verified against both fixtures from the issue (Highstreet Proposal Sample,
Heffernan Proposal Sample): cover stays on its own page, TOC starts on a
new page, matching Word.
Tests:
- 3 new unit tests in document-part-object.test.ts covering:
- Section break emitted at SDT boundary
- No emission when SDT is not at a section boundary
- No-op when sectionState is undefined
- 1740 pm-adapter tests pass (up from 1737), 604 layout-engine tests pass.
* fix(pm-adapter): recurse into docPartObj for section ranges + per-child emission (SD-2557)
The initial fix only handled the sectPr BEFORE a TOC docPartObj (on the
empty paragraph that precedes the SDT). It missed the SECOND sectPr that
Word commonly stores on the trailing empty paragraph INSIDE the TOC SDT —
which signals "TOC section ends here, next section starts on new page".
Because `findParagraphsWithSectPr` did not recurse into `documentPartObject`
or `tableOfContents`, that inner sectPr was never discovered, so no
section-range boundary was built between the TOC and the following body
content. SuperDoc rendered the TOC and the first body section stacked on
the same page (just one page later than before the first fix).
Complete fix:
1. `findParagraphsWithSectPr` (sections/analysis.ts) now recurses into
`documentPartObject` and `tableOfContents` in addition to `index` /
`bibliography` / `tableOfAuthorities`. This lets section-range analysis
see sectPrs stored anywhere inside a TOC SDT.
2. `handleDocumentPartObjectNode` (non-TOC branch) emits the pending
section break before each child paragraph and advances
`currentParagraphIndex` — matching the pattern in `handleIndexNode`,
`handleBibliographyNode`, and `handleTableOfAuthoritiesNode`.
3. `processTocChildren` (toc.ts) accepts `sectionState` via its context
arg and performs the same per-child emit + increment dance as the
paragraph handlers. This handles the TOC branch of
`handleDocumentPartObjectNode` and the nested `tableOfContents`
recursion path.
With all three changes, the Highstreet fixture now renders exactly like
Word:
- Page 1: cover
- Page 2: TOC alone
- Page 3: ABOUT US body
- Page 4: ON BEHALF OF HIGHSTREET
- Page 5: WORKERS COMPENSATION
Tests:
- 4 new tests in document-part-object.test.ts (non-TOC emission,
non-boundary no-op, undefined state, sectionState passthrough to
processTocChildren)
- 1741/1741 pm-adapter, 604/604 layout-engine, 11377/11377 super-editor
* refactor(pm-adapter): unify TOC handling via processTocChildren (SD-2557)
Collapse handleTableOfContentsNode into a delegation to processTocChildren
so direct `tableOfContents` nodes share the same code path as the
`documentPartObject` TOC gallery. Keeps the section-range counting contract
from findParagraphsWithSectPr consistent across both paths: every TOC child
paragraph advances sectionState.currentParagraphIndex, so deferred section
breaks fire at the correct boundary.
- Gate applyTocMetadata sdt fabrication on gallery — a direct
tableOfContents PM node has no enclosing w:sdt, so we no longer invent
a docPartObject SDT metadata entry on its entries.
- Forward themeColors through processTocChildren to the paragraph
converter; also pass it in from handleDocumentPartObjectNode.
- Add regression tests for per-child index advancement, sdt gating, and
themeColors pass-through.
Addresses PR #2872 review feedback.
---------
Co-authored-by: Nick Bernal <117235294+harbournick@users.noreply.github.com>1 parent ffef210 commit 884c7fb
7 files changed
Lines changed: 453 additions & 57 deletions
File tree
- packages/layout-engine/pm-adapter/src
- sdt
- sections
Lines changed: 119 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
473 | 473 | | |
474 | 474 | | |
475 | 475 | | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
476 | 595 | | |
477 | 596 | | |
Lines changed: 19 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
17 | 26 | | |
18 | 27 | | |
19 | 28 | | |
| |||
27 | 36 | | |
28 | 37 | | |
29 | 38 | | |
| 39 | + | |
30 | 40 | | |
31 | 41 | | |
32 | 42 | | |
33 | 43 | | |
34 | 44 | | |
35 | 45 | | |
| 46 | + | |
36 | 47 | | |
37 | 48 | | |
38 | 49 | | |
| |||
50 | 61 | | |
51 | 62 | | |
52 | 63 | | |
| 64 | + | |
53 | 65 | | |
54 | 66 | | |
| 67 | + | |
55 | 68 | | |
56 | 69 | | |
57 | 70 | | |
58 | 71 | | |
59 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
60 | 76 | | |
61 | 77 | | |
| 78 | + | |
62 | 79 | | |
63 | 80 | | |
64 | 81 | | |
| |||
75 | 92 | | |
76 | 93 | | |
77 | 94 | | |
| 95 | + | |
78 | 96 | | |
79 | 97 | | |
80 | 98 | | |
| |||
0 commit comments