Skip to content

refactor(painters/dom): unify SDT container rendering (SD-2838)#3322

Open
luccas-harbour wants to merge 21 commits into
luccas/sd-2838-unify-paragraph-renderingfrom
luccas/sd-2838-unify-sdt-rendering
Open

refactor(painters/dom): unify SDT container rendering (SD-2838)#3322
luccas-harbour wants to merge 21 commits into
luccas/sd-2838-unify-paragraph-renderingfrom
luccas/sd-2838-unify-sdt-rendering

Conversation

@luccas-harbour
Copy link
Copy Markdown
Contributor

Summary

Consolidates SDT (Structured Document Tag) container rendering across paragraphs, tables, and nested tables into a single shared module, then fixes a long tail of nested/split-fragment chrome bugs that the unified pipeline made tractable.

Previously, the duplicate-suppression contract was implicit: cells were handed a raw tableSdt reference and each renderer reimplemented its own metadata comparisons. With nested tables, idless SDT controls, and split fragments, this produced missing borders, duplicated chrome, clipped labels, and chrome that disappeared across page splits. The new pipeline uses a key-based ancestor chain and an upward chrome-rendered callback, so every renderer (paragraph, table fragment, table cell, embedded table) makes the same decisions from the same data.

Changes

New shared module — painters/dom/src/sdt/container.ts (replaces utils/sdt-helpers.ts)

  • Renames applySdtContainerStylingapplySdtContainerChrome
  • Adds shouldRenderSdtContainerChrome, getSdtContainerKeyForBlock, getSdtSiblingBoundaries
  • Adds SdtAncestorOptions type — ancestorContainerKey(s) + ancestorContainerSdt(s) chains for explicit suppression

Contracts (contracts/src/sdt-container.ts) — new file

  • getSdtContainerKey, getSdtContainerMetadata, hasExplicitSdtContainerKey, isSdtContainerMetadata
  • layout-resolved drops the local sdtContainerKey.ts and imports from contracts (single source of truth)
  • idlessSdtContainerKeys WeakMap groups id-less sibling controls by identity, with an AIDEV-NOTE explaining why alias/title matching is unsafe

Renderer threading

  • renderTableFragment, renderTableRow, renderTableCell, renderEmbeddedTable, and renderParagraphContent all accept the ancestorContainerKey(s)/ancestorContainerSdt(s) chain and an onSdtContainerChrome callback
  • Cells/tables flip overflow: visible only when a descendant actually renders chrome, not preemptively based on metadata
  • sdtBoundary start/end flags are now correctly narrowed at split points (paragraph mid-line splits, embedded table mid-row splits)

Nested + split-fragment fixes (each is its own commit)

  • Continue SDT chrome across paragraph splits, partial embedded tables, and nested-table continuations
  • Suppress duplicate chrome when a nested table or cell already inherits the same container
  • Preserve the SDT ancestor chain through nested tables so id-less controls don't re-render
  • Skip media (drawings) at SDT boundaries
  • Use the rendered lock mode (not the resolved one) so the dataset matches what's visually shown
  • Allow nested SDT labels to overflow rather than being clipped
  • Merge idless SDT siblings into a single container instead of fragmenting

Tests

  • New contracts/src/sdt-container.test.ts (42 lines) and painters/dom/src/sdt/container.test.ts (200 lines)
  • renderTableCell.test.ts expanded with ~1.2k lines covering nested SDT cases
  • renderTableFragment.test.ts adds ~195 lines for ancestor/chrome-callback wiring

@luccas-harbour luccas-harbour requested a review from a team as a code owner May 15, 2026 17:37
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 15, 2026

SD-2838

…odule

Move sdt-helpers from utils/ into a dedicated sdt/container.ts module
and rename applySdtContainerStyling → applySdtContainerChrome. Introduce
shouldRenderSdtContainerChrome, getSdtContainerKeyForBlock, and
getSdtSiblingBoundaries so renderer.ts and renderTableCell.ts can share
the same boundary/suppression logic instead of inlining metadata
comparisons. Table cells now receive an ancestorTableSdtKey rather than
the raw tableSdt object, making the duplicate-suppression contract
explicit and key-based.
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.
@luccas-harbour luccas-harbour force-pushed the luccas/sd-2838-unify-sdt-rendering branch from 8f91a95 to 1ce01ee Compare May 15, 2026 19:52
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.

1 participant