Skip to content

refactor(painters/dom): unify paragraph rendering across body and table cells (SD-2838)#3309

Merged
harbournick merged 50 commits into
luccas/sd-2851-remove-list-item-code-from-painterfrom
luccas/sd-2838-unify-paragraph-rendering
May 26, 2026
Merged

refactor(painters/dom): unify paragraph rendering across body and table cells (SD-2838)#3309
harbournick merged 50 commits into
luccas/sd-2851-remove-list-item-code-from-painterfrom
luccas/sd-2838-unify-paragraph-rendering

Conversation

@luccas-harbour

Copy link
Copy Markdown
Contributor

Summary

Collapses two parallel paragraph-rendering implementations (body fragments in renderer.ts and table cells in renderTableCell.ts) into a single shared module, then reorganizes the painter so paragraph and run rendering live in dedicated directories instead of being interleaved with ~3k lines of renderer.ts.

What changed

1. Unified paragraph pipeline (paragraph/)

New paragraph/renderParagraphContent.ts owns the full per-paragraph pipeline — block styles, decoration layers, list markers, indentation, line walking, and drop caps — and is consumed by both the body-fragment path in renderer.ts and the table-cell path in renderTableCell.ts. The two sites had drifted into parallel ~500-line implementations with subtly different marker/indent/border behavior; one module makes them identical by construction.

Supporting modules:

  • paragraph/indentation.ts — line indent and available-width resolution
  • paragraph/list-marker.ts — list marker rendering (absorbs the deleted utils/marker-helpers.ts)
  • paragraph/styles.ts — block-level paragraph styles
  • paragraph/borders/ — moved from features/paragraph-borders/ so all paragraph pieces live together; feature registry updated
  • paragraph/renderParagraphContent.test.ts — new coverage for the resolved-paragraph path

Table-cell side effect: borders and shading now use the same .superdoc-paragraph-border / .superdoc-paragraph-shading layers as the body path instead of being stamped onto the cell-paragraph wrapper. renderTableCell.test.ts was updated to assert against those layers.

2. Run pipeline extracted into runs/

Pulled all line- and run-level rendering (~2500 lines) out of renderer.ts into runs/, split by concern:

  • Per-run-kind: text-run, image-run, math-run, tab-run, field-annotation-run
  • Cross-cutting: links, sdt, tracked-changes, formatting-marks
  • Orchestrators: render-line, render-run
  • hash — run hashing helpers moved out of paragraph-hash-utils.ts
  • types, index

Public re-exports (RenderedLineInfo, sanitizeUrl, linkMetrics, applyRunDataAttributes) now resolve through runs/index.ts. No behavior change in this step — pure colocation in preparation for the body/table unification.

3. Shared list-marker types

shared/common/list-marker-utils.ts:

  • MinimalMarkerRun gains color, letterSpacing, vanish so callers don't redeclare a parallel marker-run type
  • MinimalMarker.justification / suffix tightened from string to literal unions
  • MinimalWordLayout gains indentLeftPx

4. Bug fixes folded in along the way

  • fix(painters/dom): keep list marker right indent — right-indent was being dropped on the unified path
  • fix(painters/dom): keep split paragraph fragment height — split fragments lost their measured height
  • fix(painters/dom): mark remeasured paragraph final lines — final-line marking was missed after remeasure

@luccas-harbour
luccas-harbour requested a review from a team as a code owner May 14, 2026 18:54
@linear

linear Bot commented May 14, 2026

Copy link
Copy Markdown

SD-2838

@luccas-harbour luccas-harbour self-assigned this May 14, 2026
@caio-pizzol caio-pizzol self-assigned this May 15, 2026
@harbournick
harbournick requested a review from tupizz May 19, 2026 19:43
@luccas-harbour
luccas-harbour requested review from VladaHarbour and removed request for a team and caio-pizzol May 20, 2026 18:46
Comment thread packages/layout-engine/painters/dom/src/paragraph/renderParagraphContent.test.ts Outdated
type MinimalWordLayout,
type ResolvedListMarkerGeometry,
} from '@superdoc/common/list-marker-utils';
import { applySourceAnchorDataset } from '../renderer.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This import creates a runtime cycle through the new extraction: renderer.ts imports renderParagraphFragment, which reaches renderParagraphContent, which imports this module, which then imports back from renderer.ts. It also makes the paragraph helper depend on the renderer orchestration module for a small DOM utility. Please move applySourceAnchorDataset into a shared utility module (or pass it in) so the extracted paragraph/list-marker code remains leaf-level and avoids circular initialization risk.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. I fixed it.

@tupizz tupizz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed current head. The RenderParagraphContentParams test type issue remains, and I added an inline comment for a new renderer -> paragraph helper circular import.

@luccas-harbour
luccas-harbour requested a review from tupizz May 21, 2026 19:14

@tupizz tupizz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

…le cells

Extract the paragraph-rendering pipeline (block styles, decoration layers,
list markers, indentation, line walking, drop caps) into a shared
paragraph/renderParagraphContent module consumed by both the body-fragment
path in renderer.ts and the table-cell path in renderTableCell.ts. The two
sites had drifted into parallel ~500-line implementations; collapsing them
removes ~1k lines net and gives both paths the same marker/indent/border
behavior.

Borders and shading now live on dedicated .superdoc-paragraph-border /
.superdoc-paragraph-shading layers (already used by the body path) instead
of being stamped onto the cell-paragraph wrapper, so renderTableCell tests
were updated to assert against those layers.
Move `features/paragraph-borders/` to `paragraph/borders/` and fold
`utils/marker-helpers.ts` into `paragraph/list-marker.ts` so all
paragraph rendering pieces live under `paragraph/`. Update the
feature registry and imports to match.
Pull line- and run-level rendering out of renderer.ts (~2500 lines) into
a dedicated runs/ directory split by concern: text, image, math, tab,
field annotation, links, SDT, tracked changes, formatting marks, and the
top-level render-line/render-run orchestrators. Run hash helpers move
from paragraph-hash-utils.ts to runs/hash.ts. Public re-exports
(RenderedLineInfo, sanitizeUrl, linkMetrics, applyRunDataAttributes)
now resolve through runs/index.ts.

No behavior change — pure colocation of the run pipeline ahead of
unifying body and table-cell paragraph rendering.
…agraph/ module

Moves paragraph fragment rendering, block versioning, and PM attribute
application out of renderer.ts into dedicated paragraph/ modules:

- paragraph/renderParagraphFragment.ts — fragment rendering entry point
- paragraph/block-version.ts — block version derivation + table-version hash
- paragraph/frame.ts — PM attribute application on the paragraph element

Trims renderer.ts by ~490 lines with no behavior change.
…tests

The contextSection option no longer exists on renderParagraphContent
inputs, so the 'body' value in these test setups is dead weight.
Move SDT boundary computation, dataset application, snapshot
collection, and inline wrapper helpers out of renderer.ts into
dedicated modules under sdt/. Renames runs/sdt.ts to sdt/inline.ts
and re-exports remain stable. No behavior change.
Extract image <img> element creation, clip-path resolution, and
hyperlink-anchor wrapping into shared helpers under `painters/dom/src/images/`,
and route the renderer's block/drawing image paths plus renderTableCell's
flowing, drawing, and anchored image paths through them. Eliminates five
near-identical copies of the same img setup (objectFit, cover→left-top,
clip-path, filters, hyperlink wrap) so behavior stays consistent across all
image surfaces. Adds coverage for clip-path, filters, and hyperlinks on both
flowing and anchored images inside table cells (SD-2838).
Removes the now-dead deriveBlockVersion machinery from renderer.ts
(paragraph/image/drawing/table version hashing, SDT metadata helpers,
list-marker guards) along with renderedBlockImageVersion in
image-block.ts. No remaining call sites consume these helpers after
the image-block rendering unification.
Move image fragment and drawing image creation out of renderer.ts into
dedicated modules under images/. Renderer now delegates:

- renderImageFragment → images/image-fragment.ts (with the
  buildImageGeometryTransform / applyImageGeometryTransform helpers it
  owned)
- createDrawingImageElement, createShapeGroupImageElement, and the
  inline shape-text image element creation → images/drawing-image.ts

No behavior change — pure extraction. Trims ~157 lines from renderer.ts.
Move image-clip-path and image-selectors (plus their tests) from
utils/ into the images/ directory alongside image-block, drawing-image,
hyperlink, and image-fragment. Update import paths in callers. No
behavior change.
Updates the comment to reflect that the painter-dom copy has been removed —
this is now the single source of truth, not a duplicate to keep in sync.
@luccas-harbour
luccas-harbour force-pushed the luccas/sd-2851-remove-list-item-code-from-painter branch from a156110 to dd41af0 Compare May 22, 2026 18:49
@luccas-harbour
luccas-harbour force-pushed the luccas/sd-2838-unify-paragraph-rendering branch from 3572c77 to 5e1e81b Compare May 22, 2026 18:49
…-block-rendering

refactor(painters/dom): unify image block rendering (SD-2838)
…endering

refactor(painters/dom): unify SDT container rendering (SD-2838)
@harbournick
harbournick merged commit 2bf27fa into luccas/sd-2851-remove-list-item-code-from-painter May 26, 2026
5 checks passed
@harbournick
harbournick deleted the luccas/sd-2838-unify-paragraph-rendering branch May 26, 2026 18:14
@github-actions

Copy link
Copy Markdown
Contributor

Agent docs audit

Found deterministic findings on 1 changed agent-doc item(s).

packages/layout-engine/AGENTS.md (162 lines)

  • 7 broken path ref(s)

Broken path refs:

  • features/feature-registry.ts
  • features/paragraph-borders/
  • src/incrementalLayout.ts
  • src/index.ts
  • src/internal.ts
  • src/renderer.ts
  • table/renderTableCell.ts

Deterministic L1 only: no AI, no Bash, no secrets. Semantic L2/L3 audit runs weekly on main. Policy: agent-docs-policy.md.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants