[1/3] refactor(painter): consume only ResolvedLayout (SD-2836)#3116
[1/3] refactor(painter): consume only ResolvedLayout (SD-2836)#3116luccas-harbour merged 13 commits intomainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33e0471db3
ℹ️ 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".
33e0471 to
c5d0b09
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
luccas-harbour
left a comment
There was a problem hiding this comment.
hey @tupizz! I left a few inline comments
also here are some test coverage gaps found by AI:
packages/super-editor/src/editors/v1/core/presentation-editor/tests/HeaderFooterSessionManager.test.ts** (file-level — coverage gap)
rebuildRegionsis now the load-bearing entry fromResolvedLayout, but the existing tests in this file setheaderRegionsmanually and bypass it. would be worth a focused test callingrebuildRegionswith a syntheticResolvedLayoutthat exercisesfootnoteReserved > 0, per-pageheightvariation, andsectionIndex > 0so the resolved-side migration has direct coverage.
packages/layout-engine/layout-resolved/src/resolveLayout.test.ts** (file-level — coverage gap)
one gap: the page-level
columns/columnRegionsfields forwarded atresolveLayout.ts:309-310aren't asserted anywhere and they feedrenderColumnSeparators(user-visible). a one-liner asserting they make it ontoResolvedPagewould close that.
tupizz
left a comment
There was a problem hiding this comment.
Both coverage gaps addressed in commit bc048e9:
1. rebuildRegions direct tests — added a new describe('rebuildRegions — ResolvedLayout entry') block in HeaderFooterSessionManager.test.ts with three focused tests:
- shrinks footer height by
footnoteReservedand shifts its offset upward (page 1: untouched 36/720; page 2 withfootnoteReserved=24: 12/744) - honors per-page height variation (3 pages of 792 / 1000 / 1400 → footer offsets 720 / 928 / 1328)
- propagates
sectionIndexfromResolvedPageonto built header/footer regions
2. columns / columnRegions forwarding — added a one-shot test in resolveLayout.test.ts asserting both fields make it onto ResolvedPage from a multi-region page input.
All 22 tests in HeaderFooterSessionManager.test.ts and 113 in resolveLayout.test.ts pass.
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.
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.
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.
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.
…ms (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.
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.
…836) 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.
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.
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.
…ock (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.
- 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.
* 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.
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.
b22c3a1 to
7ee93e9
Compare
|
🎉 This PR is included in @superdoc-dev/react v1.2.0-next.99 The release is available on GitHub release |
|
🎉 This PR is included in vscode-ext v2.3.0-next.101 |
|
🎉 This PR is included in @superdoc-dev/mcp v0.3.0-next.57 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.30.0-next.57 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-cli v0.8.0-next.74 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.8.0-next.57 |
Stack
What changed
First step of the SD-2836 "dumb painter" refactor. Migrates DomPainter's internal pipeline so every render path consumes
ResolvedLayoutwhile keeping the legacy public API working as a compatibility shim.Painter (
packages/layout-engine/painters/dom)paint()body and the six top-level rendering methods now operate onResolvedLayout/ResolvedPage/ResolvedPaintItem.ResolvedPage.currentLayoutis typedResolvedLayout | null.Page->ResolvedPage(coupled migration; pulled forward into PR1 because painter internals call the provider).Contracts (
packages/layout-engine/contracts)run-helpers.tshostingexpandRunsForInlineNewlinesandsliceRunsForLine(shared between painter and resolved-layout consumers).resolved-layout.ts:fragment: Fragmentback-pointer added toResolvedFragmentItem,ResolvedTableItem,ResolvedImageItem,ResolvedDrawingItem.ResolvedPagegains optionalcolumnsandcolumnRegions.Super-editor consumer
HeaderFooterSessionManager.rebuildRegions(resolvedLayout),updateDecorationProviders(layout, resolvedLayout),#stripFootnoteReserveFromBottomMargin(margins, page: ResolvedPage | null),#computeExpectedSectionType(kind, page: ResolvedPage, ...)migrated toResolvedPage.PresentationEditor.#updateDecorationProviders(layout, resolvedLayout)plumbed through.Docs
packages/layout-engine/AGENTS.md"DomPainter is Dumb" section updated.Why this split
This PR is large but focused: every change here is "consume ResolvedLayout instead of Layout/Fragment/PaintInput" inside the painter and its direct callers. The legacy public surface stays callable, so consumers don't break mid-stack. PR2 then collapses that surface; PR3 locks the boundary.
Verification
painter-dom,pm-adapter,layout-bridge,layout-engine,super-editor.Acceptance criteria
ResolvedLayoutinternallyFragmentback-pointer available on resolved items for legacy pathsResolvedPageTest plan
Layoutdirectly