Skip to content

fix(theme): keep source block margins on paragraphs lowered from non-paragraph text - #2157

Merged
chubes4 merged 2 commits into
trunkfrom
fix/2156-paragraph-margin
Sep 23, 2026
Merged

chubes4 merged 2 commits into
trunkfrom
fix/2156-paragraph-margin

Conversation

@chubes4

@chubes4 chubes4 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #2156

Root cause

preserveDomStrategy.emitChild (packages/blocks-engine/src/theme/preserve-dom/strategy.ts) lowers a text-only <div>/<span> to core/paragraph, carrying the source classes onto the emitted <p>. A div has no block margins of its own, but the rendered <p> inherits the user-agent margin-block: 1em wherever the author CSS is silent on that side. The carried source CSS (e.g. Tailwind utilities) sets only what the source authored — Tailwind's preflight reset is not part of the projected styles — so the lowered box rendered taller than the source (footer copyright: margin-bottom: 14px from UA, footer 451px vs 437px).

Fix

  • Paragraphs synthesized from non-paragraph source text (text-only div/span, stray inline text) are marker-classed with an engine-owned is-lowered-text class.

  • The strategy drains a zero-specificity support rule through the existing dedup CSS channel (only when lowered paragraphs exist):

    :where(p.is-lowered-text){margin-block-start:0;margin-block-end:0}

    Zero specificity is the load-bearing property: as author-origin CSS it beats UA defaults, while losing to every authored rule (class, tag, or inline) by specificity — so authored margins like mt-12/mb-4 still win the cascade, and the source box's margins (0 unless authored) are preserved.

  • Real <p> sources are untouched — their UA margins are part of the source.

Render-level evidence

Same section, same minimal block theme (carried utilities + engine support rule only, blockGap: null so no theme styles mask the UA defaults), measured computed values in Chromium via Playwright against a disposable WordPress:

element before (trunk markup) after (fixed markup)
lowered <p class="mt-12 pt-6 border-t text-center text-sm"> margin-bottom 14px (UA 1em) 0px
same, margin-top (authored .mt-12 preserved) 48px 48px
real <p class="real-copy text-sm"> margin-bottom (UA kept) 14px 14px (unchanged)

Live-import reference (unfixed import) showed the same symptom: lowered copyright <p> computed margin-bottom: 14px, margin-top: 48px, footer height 451px.

Tests

  • New regression tests in src/theme/preserve-dom/__tests__/strategy.test.ts: a text-only div lowers with source classes + marker and the support rule drains; a real <p> in the same section is untouched; stray inline text is marker-classed too.
  • Updated two existing expectations whose span-derived paragraphs now carry the marker.
  • pnpm typecheck passes. Package suite: 497 passed; the only failures are pre-existing on unchanged origin/trunk (verified in a detached scratch worktree): wp-runtime-bundle.test.ts requires a prior pnpm build, and file-save-validity.test.ts fails a role="none" RichText assertion unrelated to this change.

AI assistance: GLM-5.3 Flash via OpenCode implemented the fix and tests; Claude (Anthropic) via OpenCode localized the issue and orchestrated. Directed by @chubes4.

…paragraph text

A text-only div/span (or stray inline text) lowered to core/paragraph by
the preserve-dom strategy carried its source classes onto the <p> but
inherited the user-agent paragraph margin-block (1em), making lowered
boxes taller than the source element whenever the author CSS is silent
on that side (e.g. a footer copyright div with margin-top utilities
rendered 14px taller).

Mark synthesized paragraphs with an engine-owned is-lowered-text class
and ship a zero-specificity (:where) support rule that pins their block
margins to the source box's (0 unless authored): the rule beats UA
defaults by origin while losing to every authored rule by specificity,
so authored margin utilities still win. Real <p> sources are untouched —
their UA margins are part of the source. The rule drains through the
existing dedup CSS channel only when lowered paragraphs exist.

Fixes #2156
@chubes4

chubes4 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

The original commit here fixed the TypeScript preserve-dom strategy. The real import runs through the PHP transformer, where the footer copyright still rendered margin-bottom:14px (verified in a full-site import built with this branch). Commit 0431921 adds the PHP fix:

  • HtmlCompilation::paragraphBlockFromInlineContentWrapper() is the path that lowers a margin-styled text-only div to core/paragraph. It now marks that paragraph blocks-engine-lowered-paragraph.
  • EngineSupportCss emits :root :where(p.blocks-engine-lowered-paragraph){margin-block-start:0;margin-block-end:0} before author CSS, only when a marked paragraph exists, so authored margins still win.
  • Real <p>, blockquote, and synthetic inline wrappers (which already carry their own reset) are not marked.
  • New tests/unit/lowered-paragraph-margin.php (5 assertions) fails without the fix. One existing expectation in list-item-lowering.php was updated for the new class.
  • composer test:unit and composer test both pass.

AI assistance: Claude (Anthropic) via OpenCode found the gap in the live import and wrote the PHP fix and test. Directed by @chubes4.

@chubes4
chubes4 marked this pull request as ready for review September 23, 2026 22:59
@chubes4
chubes4 merged commit 7c37489 into trunk Sep 23, 2026
10 of 12 checks passed
@chubes4
chubes4 deleted the fix/2156-paragraph-margin branch September 23, 2026 22:59
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.

Text div lowered to core/paragraph picks up the UA paragraph bottom margin

1 participant