[2/3] refactor(painter): collapse legacy API surface (SD-2836)#3117
Conversation
33e0471 to
c5d0b09
Compare
8f18041 to
1c869c1
Compare
luccas-harbour
left a comment
There was a problem hiding this comment.
hey @tupizz!
I started reviewing this one and ran into an issue running tests, both packages/layout-engine/layout-bridge/test/performance.test.ts and packages/layout-engine/pm-adapter/src/integration.test.ts failed for me. It looks like you need to add @superdoc/layout-resolved as a test dependency to both @superdoc/pm-adapter and @superdoc/layout-bridge
Make ResolvedLayout the only painter input contract:
* DomPainterInput collapses to `{ resolvedLayout: ResolvedLayout }`.
sourceLayout, blocks/measures, headerBlocks/Measures,
footerBlocks/Measures all removed.
* DomPainterOptions: drop blocks/measures.
* DomPainterHandle: drop setData, setResolvedLayout. paint takes only
DomPainterInput. Drops the `paint(Layout)` overload across painter,
PresentationPainterAdapter, and (transitively) PresentationEditor's
paintInput.
* createDomPainter shrinks to a thin pass-through. Removes
buildLegacyPaintInput, normalizeDomPainterInput, isDomPainterInput,
wrapProvider, resolveDecorationItems, normalizeOptionalBlockMeasurePair,
assertRequiredBlockMeasurePair, createEmptyResolvedLayout,
LegacyDomPainterState, OptionalBlockMeasurePair.
* PageDecorationPayload.items becomes required (the synthesis path
is gone).
Tests:
* New `_test-utils.ts` exposes a legacy-shaped `createTestPainter`
that test files import in place of `createDomPainter`. The helper
builds a real DomPainterInput internally and synthesizes decoration
items so existing test bodies don't have to be rewritten.
* All 17 painter test files migrated to the helper.
* Two source-anchor tests still failing under investigation;
remaining work is bounded and tracked.
createTestPainter was overwriting the user-supplied onPaintSnapshot callback with its own, so tests that relied on the callback (source-anchor tests) saw an undefined snapshot. Chain the user callback after the internal one.
The three integration tests in pm-adapter were calling painter.paint(layout, mount) with raw Layout. They now resolveLayout() first and call painter.paint({ resolvedLayout }, mount). All 1794 pm-adapter tests pass.
The layout-bridge incremental-pipeline performance benchmark called painter.paint(layout, mount) and painter.setData(...). Both are removed by the API collapse. Migrate to resolveLayout() + DomPainterInput so the benchmark continues to exercise the painter under the new contract.
1c869c1 to
87ddc92
Compare
…use it (SD-2836) PR1 added test imports of @superdoc/layout-resolved in pm-adapter/src/integration.test.ts and layout-bridge/test/benchmarks/index.ts without declaring it as a devDependency. Both packages now resolve the import locally via pnpm.
tupizz
left a comment
There was a problem hiding this comment.
Confirmed and fixed in 528efe4. The two test files (pm-adapter/src/integration.test.ts:14 and layout-bridge/test/benchmarks/index.ts:6) imported @superdoc/layout-resolved without their parent packages declaring it as a devDependency.
Added @superdoc/layout-resolved: workspace:* to devDependencies of both pm-adapter/package.json and layout-bridge/package.json, regenerated the lockfile.
Verified locally:
pnpm --filter @superdoc/pm-adapter test→ 1794/1794 passpnpm --filter @superdoc/layout-bridge test→ 1192/1192 pass (incl.performance.test.ts)
PR3 rebased on top.
When resolveAlignedDecorationItems cannot align items 1:1 with fragments (returns undefined), HeaderFooterSessionManager now bails out with null instead of emitting a payload with items: undefined, which would violate the now-required PageDecorationPayload.items contract from PR2. normalizeDecorationItems narrowed to ResolvedPaintItem[] -> ResolvedPaintItem[]. Also refresh painter-dom README: drop blocks/measures/setData/paint(layout) examples; document the ResolvedLayout-only paint() and the items-aligned- with-fragments invariant on PageDecorationPayload.
tupizz
left a comment
There was a problem hiding this comment.
Both items addressed in commit 198adac:
P2 — PageDecorationPayload.items contract honored
The upstream issue: resolveAlignedDecorationItems() returns undefined when items cannot be aligned 1:1 with fragments (it logs a warning and gives up). The provider was still constructing a payload with items: normalizedItems where normalizedItems could be undefined, violating the contract this PR is meant to enforce.
Fix: when alignedItems / alignedVariantItems is undefined, the provider now returns null (decoration skipped for that page) instead of emitting a payload with items: undefined. Also tightened normalizeDecorationItems(items: ResolvedPaintItem[]) -> ResolvedPaintItem[] so the type system prevents a regression.
Verified: super-editor tsc --noEmit no longer reports the PageDecorationPayload.items mismatch (older unrelated super-editor type errors remain). 12251/12251 super-editor tests still pass; 22/22 in HeaderFooterSessionManager.test.ts.
P3 — README updated
packages/layout-engine/painters/dom/README.md now documents the ResolvedLayout-only API path (no blocks/measures props, no paint(layout), no setData), with a brief note about the items 1:1-with-fragments invariant on PageDecorationPayload.
PR3 rebased on top.
* test(painter): lock ResolvedLayout-only boundary (SD-2836) * chore(painter): drop unused imports and skipped legacy tests (SD-2836) renderer.ts: drop unused Layout, Page, Measure, FlowBlock, ParagraphBorder type imports left over from the migration. index.test.ts: delete the skipped 'decoration item synthesis' describe block (it was protecting the synthesis path that has been removed). * chore(deps): regenerate lockfile after dropping layout-resolved runtime dep (SD-2836) Moves @superdoc/layout-resolved to devDependencies in the lockfile to match package.json, so CI's --frozen-lockfile install matches. Boundary tests still need it under devDependencies.
b22c3a1
into
tadeu/sd-2836-1-consume-resolved-layout
* refactor(painter): collapse legacy API surface (SD-2836)
Make ResolvedLayout the only painter input contract:
* DomPainterInput collapses to `{ resolvedLayout: ResolvedLayout }`.
sourceLayout, blocks/measures, headerBlocks/Measures,
footerBlocks/Measures all removed.
* DomPainterOptions: drop blocks/measures.
* DomPainterHandle: drop setData, setResolvedLayout. paint takes only
DomPainterInput. Drops the `paint(Layout)` overload across painter,
PresentationPainterAdapter, and (transitively) PresentationEditor's
paintInput.
* createDomPainter shrinks to a thin pass-through. Removes
buildLegacyPaintInput, normalizeDomPainterInput, isDomPainterInput,
wrapProvider, resolveDecorationItems, normalizeOptionalBlockMeasurePair,
assertRequiredBlockMeasurePair, createEmptyResolvedLayout,
LegacyDomPainterState, OptionalBlockMeasurePair.
* PageDecorationPayload.items becomes required (the synthesis path
is gone).
Tests:
* New `_test-utils.ts` exposes a legacy-shaped `createTestPainter`
that test files import in place of `createDomPainter`. The helper
builds a real DomPainterInput internally and synthesizes decoration
items so existing test bodies don't have to be rewritten.
* All 17 painter test files migrated to the helper.
* Two source-anchor tests still failing under investigation;
remaining work is bounded and tracked.
* fix(painter): chain user onPaintSnapshot in test utility (SD-2836)
createTestPainter was overwriting the user-supplied onPaintSnapshot callback with its own, so tests that relied on the callback (source-anchor tests) saw an undefined snapshot. Chain the user callback after the internal one.
* test(pm-adapter): migrate integration tests to ResolvedLayout (SD-2836)
The three integration tests in pm-adapter were calling painter.paint(layout, mount) with raw Layout. They now resolveLayout() first and call painter.paint({ resolvedLayout }, mount). All 1794 pm-adapter tests pass.
* test(layout-bridge): migrate perf benchmark to ResolvedLayout (SD-2836)
The layout-bridge incremental-pipeline performance benchmark called painter.paint(layout, mount) and painter.setData(...). Both are removed by the API collapse. Migrate to resolveLayout() + DomPainterInput so the benchmark continues to exercise the painter under the new contract.
* chore(deps): declare @superdoc/layout-resolved as devDep where tests use it (SD-2836)
PR1 added test imports of @superdoc/layout-resolved in
pm-adapter/src/integration.test.ts and layout-bridge/test/benchmarks/index.ts
without declaring it as a devDependency. Both packages now resolve the
import locally via pnpm.
* fix(super-editor): honor PageDecorationPayload.items contract (SD-2836)
When resolveAlignedDecorationItems cannot align items 1:1 with fragments
(returns undefined), HeaderFooterSessionManager now bails out with null
instead of emitting a payload with items: undefined, which would violate
the now-required PageDecorationPayload.items contract from PR2.
normalizeDecorationItems narrowed to ResolvedPaintItem[] -> ResolvedPaintItem[].
Also refresh painter-dom README: drop blocks/measures/setData/paint(layout)
examples; document the ResolvedLayout-only paint() and the items-aligned-
with-fragments invariant on PageDecorationPayload.
* [3/3] test(painter): lock ResolvedLayout-only boundary (SD-2836) (#3118)
* test(painter): lock ResolvedLayout-only boundary (SD-2836)
* chore(painter): drop unused imports and skipped legacy tests (SD-2836)
renderer.ts: drop unused Layout, Page, Measure, FlowBlock, ParagraphBorder type imports left over from the migration. index.test.ts: delete the skipped 'decoration item synthesis' describe block (it was protecting the synthesis path that has been removed).
* chore(deps): regenerate lockfile after dropping layout-resolved runtime dep (SD-2836)
Moves @superdoc/layout-resolved to devDependencies in the lockfile to
match package.json, so CI's --frozen-lockfile install matches. Boundary
tests still need it under devDependencies.
* refactor(contracts): host expandRunsForInlineNewlines (SD-2836) Move the inline-newline run expander from @superdoc/pm-adapter into @superdoc/contracts. The function is a pure transformation on Run[] shapes already defined here; relocating it lets painter-dom consume the helper without depending on pm-adapter (per the SD-2836 acceptance criterion: no painter-dom imports from pm-adapter or layout-bridge). pm-adapter chains its public re-export through contracts, keeping the import path stable until painter-dom is migrated to consume directly. * refactor(contracts): host sliceRunsForLine (SD-2836) Move the line-aware run slicer from @superdoc/layout-bridge into @superdoc/contracts, alongside expandRunsForInlineNewlines. The function is a pure transformation on Run/Line shapes already defined here; relocating it lets painter-dom consume the helper without depending on layout-bridge (per the SD-2836 acceptance criterion). layout-bridge chains its public re-export through contracts, keeping the import path stable until painter-dom is migrated to consume directly. Also restore a TrackedChangeMeta import in pm-adapter's paragraph.test.ts that the prior commit dropped; the type is still referenced outside the migrated describe block. * refactor(painter): consume run helpers from contracts (SD-2836) Switch the painter's two cross-package run-helper imports (expandRunsForInlineNewlines, sliceRunsForLine) to source from @superdoc/contracts directly, then drop @superdoc/pm-adapter and @superdoc/layout-bridge from painter-dom's runtime dependencies. This closes the painter-dom -> pm-adapter / layout-bridge import edge called out in the SD-2836 acceptance criteria. Architecture-boundary guards added in [3/3] of this stack will prevent reintroduction. * refactor(layout): add fragment back-pointer to resolved items (SD-2836) Add a `fragment: Fragment` field to ResolvedFragmentItem, ResolvedTableItem, ResolvedImageItem, and ResolvedDrawingItem, and populate it from the corresponding resolve* helper. The reference is shared, not copied: items now carry the same Fragment object that lives on the source page. This is the precondition for stopping painter iteration over `page.fragments`. The next commit drops getResolvedFragmentItem and switches the painter to iterate ResolvedPage.items, reading the source fragment via `item.fragment` instead of indexing back into the legacy fragments array. Includes focused tests in resolveLayout.test.ts asserting back-pointer identity for each kind. * refactor(painter): drop getResolvedFragmentItem; iterate resolved items (SD-2836) Replace the three `page.fragments.forEach((fragment, index) => ...)` loops in renderPage / patchPage / initPage with iterations over `resolvedItems` (the resolved page's items), reading the source Fragment via the back-pointer added in the previous commit. Removes: - getResolvedFragmentItem (parallel-array index lookup into resolved items) - direct iteration of `page.fragments` from the painter render path Updates affected hand-rolled resolved-layout tests to populate the new required `fragment` back-pointer; the painter now treats items without a fragment as not-yet-renderable rather than indexing back into the legacy fragments array. * refactor(painter): paint() body reads ResolvedLayout (SD-2836) Switch paint()'s top-level reads from input.sourceLayout to input.resolvedLayout for: layoutEpoch, page count, and the mounted-page indices. The local `layout = input.sourceLayout` binding stays for one more commit because the four legacy-Layout helpers (beginPaintSnapshot, fullRender, patchLayout, renderHorizontal, renderBookMode, renderVirtualized) still take a Layout argument. The next commit migrates those signatures and removes the binding entirely. * refactor(painter): drop fragments param from sdt+border helpers (SD-2836) computeSdtBoundaries and computeBetweenBorderFlags previously took both the raw `fragments` array and the parallel resolvedItems array. They now take only resolvedItems and read each fragment via the back-pointer added in [PR1#4]. Updates all four call sites in renderer.ts plus the between-borders test fixture. This eliminates the painter's lookup into `page.fragments` from the helper-call layer, leaving only the deeper-method Layout dependency (beginPaintSnapshot, fullRender, patchLayout, renderHorizontal, renderBookMode, renderVirtualized) to migrate next. * refactor(painter): migrate internals to ResolvedLayout (SD-2836) Switch DomPainter's internal state and helpers from raw Layout/Page to ResolvedLayout/ResolvedPage: * The six top-level legacy-Layout methods (beginPaintSnapshot, fullRender, patchLayout, renderHorizontal, renderBookMode, renderVirtualized) take ResolvedLayout. paint() drops the `const layout = input.sourceLayout` binding and calls every method with input.resolvedLayout. * The page-level helpers (renderPage, createPageState, patchPage, renderDecorationsForPage, renderDecorationSection, getDecorationAnchorPageOriginY, renderPageRuler, renderColumnSeparators) take ResolvedPage and read width/height/ margins/numberText/etc. directly. Drops every redundant `resolvedPage?: ResolvedPage | null` parameter. * `currentLayout: Layout | null` -> `ResolvedLayout | null`. Strips the `(page.size ?? layout.pageSize)` cascades inside virtualization + page-iteration code; uses ResolvedPage.width/.height directly. * Lifts `columns` and `columnRegions` onto ResolvedPage so the column-separator renderer can read them without falling back to raw Page. Couples PageDecorationProvider (planned PR2 work) since the painter now passes ResolvedPage to the third callback argument: * `PageDecorationProvider`'s `page?` parameter is `ResolvedPage`. * `HeaderFooterSessionManager.rebuildRegions` takes ResolvedLayout. `updateDecorationProviders(layout, resolvedLayout)` plumbed through PresentationEditor. * The provider closure body and internal helpers (`#stripFootnoteReserveFromBottomMargin`, `#computeExpectedSectionType`) now operate on ResolvedPage; `page?.size?.h` -> `page?.height`. * `buildLegacyPaintInput` always calls `resolveLayout` so the legacy paint(Layout) path produces ResolvedPages even when no body blocks/measures are supplied (preserves page-level metadata). Skips a "decoration item synthesis" describe block that exercises `paint(Layout)` + `setData` + `setResolvedLayout`. Those legacy entry points get deleted in the next commit; the block is being preserved as .skip so the deletion is visible in diff. * chore(deps): regenerate lockfile after painter-dom dep cleanup (SD-2836) Drops the now-unused @superdoc/layout-bridge and @superdoc/pm-adapter entries from pnpm-lock.yaml so CI's --frozen-lockfile install matches package.json. * test(super-editor): synthesize ResolvedLayout in PresentationEditor mock (SD-2836) rebuildRegions now iterates resolvedLayout.pages (was layout.pages). The PresentationEditor test mocked resolveLayout to return empty pages, which caused the header/footer region tests to populate zero regions and bookmark navigation to fail. The mock now synthesizes a minimal ResolvedPage per source Layout page so region tests stay green. * refactor(painter,super-editor): address PR review feedback (SD-2836) - Drop redundant pageSize parameter from createPageState/patchPage; read page.width/height directly from ResolvedPage. - Migrate createDecorationProvider to ResolvedLayout; updateDecorationProviders no longer needs the legacy Layout parameter. - Add direct rebuildRegions(resolvedLayout) tests covering footnoteReserved>0, per-page height variation, and sectionIndex>0. - Assert columns and columnRegions forward through to ResolvedPage in resolveLayout tests. * [2/3] refactor(painter): collapse legacy API surface (SD-2836) (#3117) * refactor(painter): collapse legacy API surface (SD-2836) Make ResolvedLayout the only painter input contract: * DomPainterInput collapses to `{ resolvedLayout: ResolvedLayout }`. sourceLayout, blocks/measures, headerBlocks/Measures, footerBlocks/Measures all removed. * DomPainterOptions: drop blocks/measures. * DomPainterHandle: drop setData, setResolvedLayout. paint takes only DomPainterInput. Drops the `paint(Layout)` overload across painter, PresentationPainterAdapter, and (transitively) PresentationEditor's paintInput. * createDomPainter shrinks to a thin pass-through. Removes buildLegacyPaintInput, normalizeDomPainterInput, isDomPainterInput, wrapProvider, resolveDecorationItems, normalizeOptionalBlockMeasurePair, assertRequiredBlockMeasurePair, createEmptyResolvedLayout, LegacyDomPainterState, OptionalBlockMeasurePair. * PageDecorationPayload.items becomes required (the synthesis path is gone). Tests: * New `_test-utils.ts` exposes a legacy-shaped `createTestPainter` that test files import in place of `createDomPainter`. The helper builds a real DomPainterInput internally and synthesizes decoration items so existing test bodies don't have to be rewritten. * All 17 painter test files migrated to the helper. * Two source-anchor tests still failing under investigation; remaining work is bounded and tracked. * fix(painter): chain user onPaintSnapshot in test utility (SD-2836) createTestPainter was overwriting the user-supplied onPaintSnapshot callback with its own, so tests that relied on the callback (source-anchor tests) saw an undefined snapshot. Chain the user callback after the internal one. * test(pm-adapter): migrate integration tests to ResolvedLayout (SD-2836) The three integration tests in pm-adapter were calling painter.paint(layout, mount) with raw Layout. They now resolveLayout() first and call painter.paint({ resolvedLayout }, mount). All 1794 pm-adapter tests pass. * test(layout-bridge): migrate perf benchmark to ResolvedLayout (SD-2836) The layout-bridge incremental-pipeline performance benchmark called painter.paint(layout, mount) and painter.setData(...). Both are removed by the API collapse. Migrate to resolveLayout() + DomPainterInput so the benchmark continues to exercise the painter under the new contract. * chore(deps): declare @superdoc/layout-resolved as devDep where tests use it (SD-2836) PR1 added test imports of @superdoc/layout-resolved in pm-adapter/src/integration.test.ts and layout-bridge/test/benchmarks/index.ts without declaring it as a devDependency. Both packages now resolve the import locally via pnpm. * fix(super-editor): honor PageDecorationPayload.items contract (SD-2836) When resolveAlignedDecorationItems cannot align items 1:1 with fragments (returns undefined), HeaderFooterSessionManager now bails out with null instead of emitting a payload with items: undefined, which would violate the now-required PageDecorationPayload.items contract from PR2. normalizeDecorationItems narrowed to ResolvedPaintItem[] -> ResolvedPaintItem[]. Also refresh painter-dom README: drop blocks/measures/setData/paint(layout) examples; document the ResolvedLayout-only paint() and the items-aligned- with-fragments invariant on PageDecorationPayload. * [3/3] test(painter): lock ResolvedLayout-only boundary (SD-2836) (#3118) * test(painter): lock ResolvedLayout-only boundary (SD-2836) * chore(painter): drop unused imports and skipped legacy tests (SD-2836) renderer.ts: drop unused Layout, Page, Measure, FlowBlock, ParagraphBorder type imports left over from the migration. index.test.ts: delete the skipped 'decoration item synthesis' describe block (it was protecting the synthesis path that has been removed). * chore(deps): regenerate lockfile after dropping layout-resolved runtime dep (SD-2836) Moves @superdoc/layout-resolved to devDependencies in the lockfile to match package.json, so CI's --frozen-lockfile install matches. Boundary tests still need it under devDependencies. * fix(painter): adapt content-presence gate to ResolvedPage (SD-2836) After rebasing PR1's painter migration onto main, the new column-separator content-presence gate added by SD-2452 (a5ff6ed) reads page.fragments directly. On the new architecture the painter receives a ResolvedPage whose fragments live as page.items[]. Switch the in-region scan to iterate items (every ResolvedPaintItem carries x/y). Also extend the test-only createTestPainter shim to auto-synthesize minimal ParagraphBlock/ParagraphMeasure entries for any layout fragment whose blockId isn't covered by the supplied blocks. Tests that only exercise wrapper-level rendering (column separators, page chrome) can keep using fragAt(...) without boilerplate matching blocks.
Stack
What changed
Builds on PR1 (which made DomPainter consume
ResolvedLayoutinternally) by collapsing the legacy public surface so thatResolvedLayoutis the only painter input.Painter (
packages/layout-engine/painters/dom)index.tscollapsed to a thin pass-through. Deleted:buildLegacyPaintInput,normalizeDomPainterInput,isDomPainterInput,wrapProvider,resolveDecorationItems,normalizeOptionalBlockMeasurePair,assertRequiredBlockMeasurePair,createEmptyResolvedLayout.paint()now accepts{ resolvedLayout, mount, mapping? }only. The legacy(layout, mount)shape is gone._test-utils.ts(new) hosts acreateTestPainterhelper that wraps a painter for tests still expressing inputs in the legacy shape, chaining the user'sonPaintSnapshotafter the internal one.Tests
pm-adapterintegration tests migrated toResolvedLayout.layout-bridgeperf benchmark migrated toresolveLayout()+paint({ resolvedLayout }).super-editorPresentationEditor.test.tsmock forresolveLayoutsynthesizes aResolvedPageper sourceLayoutpage sorebuildRegions(resolvedLayout)populates regions correctly.Why this split
PR1 keeps the internal migration reviewable on its own. This PR removes the legacy entry points and forces every consumer onto
ResolvedLayout. PR3 then locks the boundary (test guard + drops@superdoc/layout-resolvedfrom runtime deps).Verification
painter-dom,pm-adapter,layout-bridge,layout-engine,super-editor.Test plan