Skip to content

fix(layout): suppress pageBreakBefore directly after explicit break (SD-3366)#3712

Closed
tupizz wants to merge 3 commits into
mainfrom
tadeu/sd-3366-bug-pagebreakbefore-inside-style-preceded-by-pagebreak
Closed

fix(layout): suppress pageBreakBefore directly after explicit break (SD-3366)#3712
tupizz wants to merge 3 commits into
mainfrom
tadeu/sd-3366-bug-pagebreakbefore-inside-style-preceded-by-pagebreak

Conversation

@tupizz

@tupizz tupizz commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

A paragraph whose style resolves pageBreakBefore, placed directly after an explicit page break (w:br w:type="page"), rendered an extra blank page. Word collapses the redundant break.

Fixes SD-3366. Also the root cause behind IT-1152 (the SDT wrapper is irrelevant, confirmed in the ticket).

Root cause

The layout engine already had a fresh-page guard (shouldSkipRedundantPageBreakBefore, added for page-forcing section breaks), but it only fires when the fresh page has zero fragments. An explicit page break paragraph leaves its empty remnant (the paragraph mark) on the fresh page, so the guard never fired and the style break added a blank page.

Word semantics (fixture-verified)

Word's rule is structural, not geometric. Verified by rendering a 5-shape fixture matrix in Word:

Shape Structure before the pageBreakBefore paragraph Word
A paragraph ending in explicit break (empty remnant) suppressed
B plain empty paragraph, no break fires
C explicit break + text after it fires
D explicit break + one extra empty paragraph fires
E explicit break at end of a text paragraph suppressed

Only the break paragraph's own remnant is forgiven; any other content re-arms the break.

Fix

isPageBreakBeforeSatisfiedByExplicitBreak in layout-engine/src/index.ts: suppress a source=pageBreakBefore break when the preceding blocks are [explicit pageBreak][empty remnant paragraph]. The directly-adjacent case (shape E, no remnant emitted) was already covered by the existing geometric guard.

Tests

  • 6 new layout-engine tests covering shapes A–E plus the explicit+explicit double-break case (intentional blank page still honored).
  • 3 new adapter contract tests pinning the FlowBlock emission shapes the window check relies on.
  • Full suites green: layout-engine 755 tests, layout-adapter 1972 tests.

Verification

  • All 3 ticket fixtures + 5 mutation fixtures match Word page counts exactly (v05: 2 pages, was 3; IT-1152 repro: 4 pages; minimal repro: 2 pages).
  • Corpus scan (488 docs): exactly 1 doc contains the trigger adjacency (sd-1962-nested-tables-and-sections.docx, where Word also suppresses); 10 others have both features but never adjacent, provably unchanged.

…SD-3366)

A paragraph whose style resolves pageBreakBefore, placed directly after an
explicit page break, rendered an extra blank page. Word collapses the
redundant break: the break paragraph's own empty remnant (its paragraph
mark on the fresh page) does not re-arm pageBreakBefore, but any other
content does, including a single extra empty paragraph.

The existing fresh-page guard only covered pages with zero fragments, so
the remnant fragment defeated it. Add a structural check on the block
sequence: suppress a source=pageBreakBefore break when it is preceded by
an explicit page break plus that break's empty remnant paragraph.

Word-verified against a five-shape fixture matrix (break remnant, plain
empty paragraph, text after break, extra empty paragraph, break at end of
text paragraph); all five shapes plus both ticket fixtures now match
Word's pagination. Adapter emission shapes the check relies on are pinned
by new contract tests. Corpus scan: 1 of 488 docs contains the trigger
adjacency.
@tupizz tupizz requested a review from a team as a code owner June 11, 2026 13:34
@linear-code

linear-code Bot commented Jun 11, 2026

Copy link
Copy Markdown

SD-3366

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3177add56e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/layout-engine/layout-engine/src/index.ts
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@luccas-harbour

Copy link
Copy Markdown
Contributor

hey @tupizz! it looks good to me. can you address the comment by codex? then I think we're good to go

An empty-text list item still paints a visible marker from paragraph
attrs (numberingProperties / wordLayout.marker), so a break remnant that
is a list item is page content and must re-arm a following
pageBreakBefore, consistent with the Word rule that anything other than
the bare remnant fires the break. Addresses PR review feedback.
@superdoc-bot superdoc-bot Bot closed this in ce63bc6 Jun 11, 2026
@superdoc-bot

superdoc-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

This pull request landed on main as ce63bc6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants