Merge main into stable (conflicts need resolution)#2936
Merge main into stable (conflicts need resolution)#2936superdoc-bot[bot] wants to merge 40 commits intostablefrom
Conversation
* fix: avoid scrolling back when dragging selection * chore: removed references to tickets * chore: removed references to ticket
…22-013504 🔄 Sync stable → main
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…es (SD-2417) (#2705) * feat: add translator for w:pPrChange * fix(pPrChange): preserve empty w:pPr on round-trip An empty <w:pPr/> inside <w:pPrChange> means "the paragraph previously had no properties" per the OOXML spec, but the encoder was dropping it: pPrTranslator.encode() returns undefined for an empty pPr, so the paragraphProperties key never landed in the encoded change. On decode, the existing fallback couldn't fire because the key was absent, and the element vanished on export. Fall back to an empty object when the inner encode returns undefined, so the key is always present when a <w:pPr> was in the source XML. The decode path already reconstructs the empty <w:pPr/> from this shape. * chore(super-converter): drop unused emitWhenAttributesOnly option * fix(super-converter): order sectPr before pPrChange in w:pPr CT_PPr requires sectPr to precede pPrChange, but the exporter always appended sectPr as the last child. Before the w:pPrChange translator existed, no export contained both children, so the ordering bug was latent. With pPrChange now round-tripping, paragraphs that carry both a section break and a tracked property change were emitted in an order Word rejects. Insert sectPr before any existing w:pPrChange child; otherwise append as before. * test(pPrChange): cover mixed sectPr and pPr routing (SD-2417) - pPrChange-translator: add a round-trip that mixes sectPr with other paragraph properties so a regression dropping either half surfaces. - pPr-translator: add two tests that exercise the pPrChange registration end-to-end through the real pPr translator, guarding against a silent SD-2417 regression if the registration is ever removed. --------- Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com> Co-authored-by: Caio Pizzol <caio@superdoc.dev>
* ci(behavior): parallelize tests by browser x shard matrix Expands the behavior test matrix from 3 shards (each running all browsers) to 12 jobs (3 browsers x 4 shards). Each job installs only the browser it needs and uses a browser-scoped Playwright cache key. Local measurement showed a 1/9 slice at ~4 min on 2 workers; 1/12 should land ~3 min of tests + 2.5 min setup, well under the previous ~20 min wall time. * ci(behavior): self-trigger on workflow file changes --------- Co-authored-by: Caio Pizzol <caio@superdoc.dev>
* fix: improve toolbar responsiveness * test(toolbar): fill round-3 coverage gaps (SD-2328) Adds boundary and lifecycle coverage for the responsive toolbar fix: - defaultItems.test.js: XL overflow cutoff (1429 vs 1430) and LG compact-class application (1280 vs 1281). - Toolbar.test.js: positive ResizeObserver branch (constructor, observe, disconnect on unmount). Moves vi.unstubAllGlobals into afterEach so the stub doesn't leak on a thrown assertion. - super-toolbar.test.js: getAvailableWidth branches on responsiveToContainer and falls back to 0 when no container is set. - responsive-container-overflow.spec.ts: fixes a latent selector bug. The original descendant query never matched (class lives on the ButtonGroup root, not a descendant) so the minWidth assertion silently passed on null. Now uses a compound selector and asserts both side groups compact. --------- Co-authored-by: Caio Pizzol <caio@superdoc.dev>
Co-authored-by: Artem Nistuley <artem@superdoc.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* ci(release): harden stable release orchestration and recovery * fix: lint * chore: fix dry run
…2905) Co-authored-by: Artem Nistuley <artem@superdoc.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…#2899) * feat(super-editor): render imported heading/bookmark cross-references (SD-2495) Wire the sd:crossReference v3 translator into the v2 importer entity list so REF / NOTEREF / STYLEREF fields imported from DOCX actually produce PM crossReference nodes instead of being silently dropped by the dispatch loop. Walk nested run wrappers when extracting the field's cached display text, and render the cross-reference as an internal hyperlink when the instruction carries the \\h switch. Route clicks on internal #bookmark anchors through goToAnchor so rendered cross-references navigate to their target in the document. Fixes IT-949 — Word cross-references (e.g. "Section 15") now appear in the viewer and are searchable, matching Word's output. * fix(presentation-editor): anchor nav writes scrollTop to the real scroll container #scrollPageIntoView wrote scrollTop to #visibleHost, which is typically overflow: visible and therefore not the actual scroll target. Anchor navigation (TOC clicks and SD-2495 cross-reference click-to-navigate) silently did nothing whenever the bookmark target was outside the current viewport — the PM selection moved but the viewport never scrolled. Write to #scrollContainer (the resolved scrollable ancestor) as the primary target, plus #visibleHost for backward compatibility with legacy layouts and the existing test harness that mocks scrollTop on the host element. This unblocks SD-2495's cross-reference click-to-navigate on docs where cross-references and their targets live on different pages. * feat(layout): show-bookmarks bracket indicators (SD-2454) Opt-in visual indicators for bookmark positions — mirrors Word's "Show bookmarks" (File > Options > Advanced). Off by default. - Pm-adapter bookmark-start and new bookmark-end converters emit gray `[` and `]` marker TextRuns when `layoutEngineOptions.showBookmarks` is true. Markers flow through pagination and line breaking as real characters, matching Word's own visual model. - Auto-generated bookmarks (`_Toc…`, `_Ref…`, `_GoBack`) are hidden even when the feature is on — matching Word. A `renderedBookmarkIds` set on the converter context pairs suppression so closing brackets don't orphan open ones. - PresentationEditor.setShowBookmarks toggles at runtime: clears the flow-block cache and schedules a re-render. - SuperDoc.setShowBookmarks is the public API passthrough. - Dev app gets a Show/Hide bookmarks toggle button in the header. - CSS: subtle gray, non-selectable so users don't include brackets in copied text. Bookmark name surfaces via the native title tooltip on the opening bracket. * test: close regression gaps for SD-2495 / SD-2454 / anchor-nav fix Fills the test gaps surfaced by the testing-excellence review of this PR: - crossReferenceImporter.integration.test.js (new, 4 tests): exercises the full v2 body pipeline (preprocessor -> dispatcher -> entity handler -> v3 translator). Asserts crossReferenceEntity is a member of the defaultNodeListHandler entities list, so the exact root cause that produced IT-949 ("Section 15" vanishing) fails loudly if a future refactor drops the wire-up. Unit tests of the translator alone cannot catch this — they bypass the dispatcher. - EditorInputManager.anchorClick.test.ts (new, 4 tests): pins the SD-2537 click-to-navigate routing. Clicking #bookmark hrefs routes through goToAnchor (was TOC-only before). External and empty-fragment hrefs are explicitly NOT routed. - cross-reference.test.ts: added marks-propagation test (node.marks flow into the emitted TextRun so italic/textStyle on xref text survives — SD-2537 "preserve surrounding run styling" AC). - bookmark-markers.test.ts: converted the `for` loop over auto-generated bookmark names into `it.each`. Each input now reports per-case on failure, complies with testing-excellence's "no control flow inside test bodies" guideline. - PresentationEditor.test.ts: documents why the scrollContainer-vs- visibleHost branch of the SD-2495 scrollPageIntoView fix isn't unit- testable here (happy-dom doesn't propagate inline overflow through getComputedStyle, which is what findScrollableAncestor uses). * feat(pm-adapter): synthesize internal link for PAGEREF with \h switch Mirrors the pattern added for crossReference in #2882. When a PAGEREF field instruction carries the `\h` switch, attach a FlowRunLink via `buildFlowRunLink({ anchor: bookmarkId })` so clicks on the rendered page number navigate to the referenced bookmark through the existing anchor-link routing (`EditorInputManager.#handleLinkClick` → `goToAnchor`). Previously the PAGEREF inline converter emitted the `pageReference` token run with `pageRefMetadata.bookmarkId` but no `link`, so the DOM layer never produced a clickable element for PAGEREFs. TOC entries and other hyperlinked page references imported from Word therefore failed to navigate on click, even though Word honored the `\h` switch. Depends on #2882 for `buildFlowRunLink` export and the generalized anchor-click routing. * fix(pm-adapter): match PAGEREF \h switch case-insensitively Word field switches are case-insensitive per the field-code grammar, so `\H` should produce the hyperlink the same as `\h`. Reviewer (codex-connector) flagged that the original `/\\h\b/` regex skipped the link synthesis for instructions like `PAGEREF _Toc123 \H`, leaving them non-navigable even though the author had requested hyperlink behavior. Adds the `i` flag and a regression test with an uppercase switch. * test: add coverage for standalone PAGEREF \h + REF-family preprocessors Behavior test (tests/behavior/tests/navigation/pageref-standalone-click.spec.ts): Covers the PR's load-bearing case - a PAGEREF \h field NOT wrapped in a <w:hyperlink>. The existing toc-anchor-scroll.spec.ts only exercises the wrapped-in-hyperlink shape, where the outer link mark already propagates via marksAsAttrs and the PR is a no-op. Fixtures exercise both \h and \H (case-insensitivity per ECMA-376 17.16.1). Preprocessor unit tests (ref/noteref/styleref): These three importer modules were added in #2882 without tests. Each verifies the preprocessor produces a sd:crossReference node with the right fieldType and preserves the instruction text verbatim. * test(superdoc): cover setShowBookmarks propagation and no-op guard Closes the Codecov patch-coverage gap on SuperDoc.js flagged on PR #2899. The uncovered setShowBookmarks method came in via the SD-2454 merge and had no test for its config mutation, no-op short-circuit, or Boolean() coercion. Models the existing setDisableContextMenu test pattern. * refactor(pm-adapter): drop TextRun casts + case-insensitive \h in cross-reference Addresses review feedback on #2899: - page-reference.ts: remove redundant `as TextRun` casts — textNodeToRun already returns TextRun, so the casts are noise (cross-reference.ts:34 already does this cleanly). Shorten the \h comment. - cross-reference.ts: add the `i` flag to the \h switch regex to match ECMA-376 §17.16.1, same fix as 7f19358 for page-reference. Add a regression test covering `\H`. * test: add unit coverage for scroll fan-out when ancestor != host Stubs window.getComputedStyle to mark a wrapper element as scrollable so #findScrollableAncestor returns the wrapper, then asserts both the wrapper and the visibleHost receive scrollTop. This pins the SD-2495 fix: a revert to the pre-fix one-liner (writing only to the visibleHost) now fails. --------- Co-authored-by: Tadeu Tupinamba <tadeu.tupiz@gmail.com> Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com> Co-authored-by: Caio Pizzol <caio@superdoc.dev>
* fix(paragraph): guard listRendering destructure against null
ParagraphNodeView destructured `{ suffix, justification }` from
`this.node.attrs.listRendering` without a null-check in #updateListStyles,
and accessed `listRendering.markerText`/`.suffix` directly in #initList.
When a paragraph node carried `listRendering: null` — which can happen
after certain editor mutations (e.g. dispatching a transaction that
combines `setDocAttribute('bodySectPr', …)` with a paragraph delete) —
the post-transaction list-styles re-pass threw:
TypeError: Cannot destructure property 'suffix' of
'this.node.attrs.listRendering' as it is null
Use `?? {}` and optional chaining so a null value falls back through
the existing defaults (`suffix ?? 'tab'` and the `suffix == null` branch
in #createSeparator).
Adds a regression test.
* fix(paragraph): no-op list style updates when listRendering is null
Previously the null-guarded path fell back to `suffix = 'tab'` and still
invoked `#calculateMarkerStyle`/`#calculateTabSeparatorStyle`. Reviewer
(codex-connector) flagged that in mixed-suffix updates — where a queued
RAF callback runs after a node transitions from `suffix: 'space'` to
`listRendering: null` — the separator may still be a Text node. Writing
`this.separator.style.cssText` on a Text node throws.
Change #updateListStyles and #initList to return early when
`listRendering` is null, leaving the existing marker/separator untouched.
Future updates (when the node gets a real listRendering or isList()
returns false) will clean up as before.
Adds a regression test covering the space→null transition.
* test(paragraph): cover constructor mount and null-to-tab recovery
Addresses review feedback on #2896:
- Update #initList JSDoc `@param` to include `| null`, matching the
no-op-on-null behavior added in the previous commit.
- Add a test that mounts a ParagraphNodeView with `listRendering: null`
(the constructor path, not just update()), confirming the null guards
in #initList and #updateListStyles cover first-render too.
- Add a test for the space→null→tab transition that verifies the
separator swaps from a text node back to a span when listRendering
returns with a different suffix.
---------
Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…tems (#2811) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * fix(layout): use resolved continuation state for paragraph first-line width
* chore: add initial scaffold for labs release check orchestration * chore: tweak gha
…ayout (#2812) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout (#2813)
…tage (#2814) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout * refactor(layout): move change detection into resolved layout stage * fix: avoid duplicate block version hashing in DomPainter
…esolved items (#2818) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout * refactor(layout): move change detection into resolved layout stage * refactor(layout): lift paragraph and list-item block/measure into resolved items
) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout * refactor(layout): move change detection into resolved layout stage * refactor(layout): lift paragraph and list-item block/measure into resolved items * refactor(painter): extract block/measure resolution helper
…Input (#2820) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout * refactor(layout): move change detection into resolved layout stage * refactor(layout): lift paragraph and list-item block/measure into resolved items * refactor(painter): extract block/measure resolution helper * refactor(painter): remove body blocks/measures from DomPainterInput Body block and measure data now flows exclusively through the resolved layout. The painter only builds a blockLookup from header/footer data, which is the last remaining fallback surface for fragments that do not yet have a resolved path. Complex-transaction rebuild detection now walks the resolved layout items directly instead of iterating the body blockLookup. The legacy createDomPainter wrapper derives a resolved layout from its legacyState blocks/measures on the fly so the benchmark path and direct createDomPainter(options).paint(Layout) callers keep working without setResolvedLayout. * fix: dompainter body input contract on first paint
…oration layouts (#2826) * refactor(layout): lift page metadata into ResolvedPage * refactor(layout): lift fragment metadata into resolved paint items Add pmStart, pmEnd, continuesFromPrev, continuesOnNext, markerWidth, and metadata fields to resolved paint item types. Populate them in the resolvers and update the painter to prefer resolved item data over legacy Fragment reads with fallbacks. * refactor(layout): pre-compute SDT container keys in resolved layout * refactor(layout): pre-compute paragraph border data in resolved layout * refactor(layout): move change detection into resolved layout stage * refactor(layout): lift paragraph and list-item block/measure into resolved items * refactor(painter): extract block/measure resolution helper * refactor(painter): remove body blocks/measures from DomPainterInput Body block and measure data now flows exclusively through the resolved layout. The painter only builds a blockLookup from header/footer data, which is the last remaining fallback surface for fragments that do not yet have a resolved path. Complex-transaction rebuild detection now walks the resolved layout items directly instead of iterating the body blockLookup. The legacy createDomPainter wrapper derives a resolved layout from its legacyState blocks/measures on the fly so the benchmark path and direct createDomPainter(options).paint(Layout) callers keep working without setResolvedLayout. * refactor(layout): add resolveHeaderFooterLayout helper for decoration layouts * chore: fix lock file * fix: preserve per-page header/footer resolution data
* docs: enhance OpenAPI specification for sign API - Updated the `document` object description to clarify the requirement of providing either `base64` or `url`. - Expanded the `signer` object description to specify required fields and optional fields, including examples. - Improved the `auditTrail` description to emphasize compliance requirements. - Added a new `certificate` object to configure the audit trail certificate page. - Updated the API documentation in `backend.mdx` to reflect the changes in the `signer` fields and their requirements. * docs: update OpenAPI specification for event payload structure - Replaced the `field` and `value` properties with a new `data` object in the event schema. - The `data` object now supports an event-specific payload with detailed descriptions for different event types, enhancing clarity and usability. * docs: update documentation for event payload and signer fields - Enhanced the OpenAPI specification to clarify the structure of the event payload, including updates to the `field_change` type to support additional data types. - Simplified the `signer` object in the backend documentation by removing unnecessary properties and added a warning about trusting browser-submitted `ip` and `userAgent` values. --------- Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com>
* feat(layout): make tracked changes story-aware in resolved layout * feat(editor): add story editing sessions and runtime plumbing * feat(doc-api): expose story-aware tracked change operations * feat(superdoc): surface story tracked changes in comments ui * fix(notes): only strip separators after note reference runs * fix(superdoc): restore tracked-change comment rendering and CI tests * fix: sidebar activation state for tcs * fix: footnote caret selection
* fix: image rendering * chore: update test case
…der callback (#2827) * refactor(painter): deliver resolved items to decoration provider callback * fix: normalize header/footer resolved item coordinates before paint
…2925) * fix(extract): return tables as paragraph-granular blocks (SD-2672) doc.extract() was flattening tables into one joined string, which broke RAG chunking and made table citations unreachable via scrollToElement. Walk tables directly and emit one block per paragraph-like descendant of each origin cell, tagged with tableContext so consumers can group back to cell, row, or whole table. - gridBefore/gridAfter placeholder cells are skipped via the __placeholder attr; they are layout artifacts with no user content. - Block SDTs (structuredContentBlock) are transparent, so tables wrapped in content controls are not re-flattened through the wrapper's textContent. - Cell paths use physical row-and-cell child indexes so deterministic fallback nodeIds agree with buildBlockIndex, keeping the scrollToElement round-trip stable for paragraphs that lack paraId and sdBlockId inside horizontally merged tables. Tested: 13 behavior tests (7 existing SD-2525 + 6 new SD-2672), 5 new adapter unit tests, plus the full document-api-adapters suite (3105 tests) and document-api bun suite (1362 tests). * fix(extract): recurse through unrecognized block wrappers (SD-2672) The new table walker only emitted blocks for recognized types and silently dropped anything else, including their block children. That regressed coverage versus the old textContent walk for `documentSection`, `documentPartObject`, and `shapeContainer`, which all declare block-level content but aren't in EMITTABLE_BLOCK_TYPES. Treat any unrecognized block with block-level children as transparent and recurse into it, so paragraphs nested inside these wrappers still surface with their enclosing tableContext. Adds a unit test covering a `documentSection` inside a table cell. * test(extract): add DOCX-import-driven coverage for table edge cases (SD-2672) The adapter unit tests hit the algorithm via schema-constructed PM docs, which skips the importer entirely. This adds a second layer of tests that load real Word-authored .docx files, run them through the full import pipeline, and assert extract output. Closes the gap the code review flagged for a customer-facing legal RAG contract. Fixtures authored via Word COM + local OOXML patching: - sd-2672-plain-3x3.docx: baseline table, no merges or placeholders - sd-2672-merged-table.docx: colspan=2 and rowspan=2 anchors - sd-2672-rtl-table.docx: bidiVisual RTL table - sd-2672-gridbefore-vmerge.docx: w:gridBefore + w:vMerge=restart/continue - sd-2672-sdt-table.docx: table wrapped in a w:sdt block (content control) - sd-2672-nested-table.docx: 2x2 table inside cell (1,1) of outer table - sd-2672-multipara-cell.docx: cell (0,0) with two paragraphs The build-sd-2672-fixtures.mjs script regenerates the patched variants from the Word-authored base, using JSZip + regex/XmlDocument surgery. Tests assert: per-cell content lands at correct logical grid coords, merged anchors carry rowspan/colspan, RTL tables still report columns 0..N-1, gridBefore placeholders don't emit phantom blocks, SDT wrappers are transparent, nested tables get a fresh tableOrdinal with parent coordinates, multi-paragraph cells emit one block per paragraph with shared tableContext, and scrollToElement round-trips a merged-cell paragraph nodeId. * chore(tests): drop SD-2672 fixture build script The script was added alongside the fixtures to regenerate the OOXML-patched variants from a Word-authored base. It isn't carrying its weight: fixtures are committed as static binaries, the regex-based XML patching is fragile to Word COM output changes, and the commit history already documents how each fixture was constructed. If we need a new edge-case fixture later, hand-authoring it once is simpler than maintaining a generator. * chore(tests): drop stale script reference in extract-docx error
* fix: footnote editing lag * fix: flush active note sessions before DOCX export * fix: context menu accept/reject in headers/footers * fix: enable hover effect on header/footer regardless of active editing part * feat: create duplicate-comment display for header/footer tcs
…s (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>
* fix: footer tc issus, selection issues * chore: behavior tests * chore: additional fixes
* fix: header/footer undo history across story sessions
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59921c3380
ℹ️ 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".
| if (resolvedItem?.block?.kind !== 'paragraph' || resolvedItem?.measure?.kind !== 'paragraph') { | ||
| throw new Error(`DomPainter: missing resolved paragraph block/measure for fragment ${fragment.blockId}`); | ||
| } |
There was a problem hiding this comment.
Allow decoration fragments to render without resolved items
PageDecorationPayload.items is documented as optional (renderer.ts says omitted items should still render with reduced metadata), but paragraph rendering now hard-fails when resolvedItem is missing. In that case the code throws and falls back to an error placeholder, so header/footer providers that still return only { fragments, height } (a supported shape) will render broken content instead of text. This is a functional regression for legacy/third-party decoration providers and any path that drops misaligned items.
Useful? React with 👍 / 👎.
Summary
merge/main-into-stable-2026-04-24fromstablemaininto the candidate branchNext Step
Resolve the conflicts on
merge/main-into-stable-2026-04-24, push the fixes, and then merge this PR intostable.Auto-created by promote-stable workflow.