Skip to content

Map resolved CSS to canonical block style attributes (#261, #259)#268

Merged
chubes4 merged 1 commit into
trunkfrom
fix/canonical-block-style-attributes-261
Jun 28, 2026
Merged

Map resolved CSS to canonical block style attributes (#261, #259)#268
chubes4 merged 1 commit into
trunkfrom
fix/canonical-block-style-attributes-261

Conversation

@chubes4

@chubes4 chubes4 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Closes #261
Closes #259

Problem

Core blocks were emitted with a raw inline style STRING attribute (e.g. wp:paragraph {"style":"color:var(--x)"}, wp:heading {"style":"font-size:clamp(...)"}, wp:group {"style":"padding:...;background:..."}). Core blocks expect a structured style OBJECT (style.typography/color/spacing/border) + the layout attribute, so the stored HTML diverged from each block's save() and the editor threw "This block contains unexpected or invalid content" for nearly every styled element. Only buttons were fixed previously (#241).

Fix — generalize #241 to ALL blocks

New Style/StyleAttributeMapper translates an element's resolved CSS declarations (inline + matched <style>/linked CSS) into canonical block attributes and serializes them back to the inline CSS + has-* support classes WordPress writes in save(), keeping stored markup in sync with the block.

Mapping implemented:

  • typography → style.typography.{fontSize,fontWeight,lineHeight,letterSpacing,textTransform,textDecoration,fontStyle}
  • color → style.color.{text,background,gradient} (+ has-text-color/has-background classes)
  • spacing → style.spacing.{padding,margin} (shorthand expanded to side objects)
  • border → style.border.{width,style,color,radius} (+ has-border-color)
  • display:flex|grid (+ flex/grid/gap props) → the block's layout attribute

Wiring:

  • presentationAttributes() now returns a canonical style object (never a string) + layout, used by every block path (group, heading, paragraph, list, quote, columns, icon, …).
  • BlockFactory serializes the object for all blocks via the shared mapper.
  • Buttons keep resolving from the raw merged CSS via a threaded resolvedStyle callback (ButtonStyleResolver unchanged).

Hard rule: never a raw style string on a core block

Declarations that don't map to a block support (position, inset, overflow, transform, box-shadow, background images, aspect-ratio, nav anchor/submenu CSS, …) are dropped and ride on the already-preserved className (+ carried theme/companion CSS) — they are never emitted as a style string.

Hidden-state safety (#259)

display:none / visibility:hidden / opacity:0 base states (responsive/JS-revealed) are no longer frozen onto content-bearing/interactive elements (nav, CTA) — they are normalized away and surfaced as a frozen_hidden_state diagnostic finding. Genuinely decorative / aria-hidden nodes may stay hidden.

Tests

  • New "canonical block style attributes" contract (tests/contract/run.php):
    • Guard: walks every emitted block and asserts the style attr is an object or absent — never a raw string (locks in the fix / prevents regression); also asserts serialized markup carries no raw display: style.
    • Positive: styled <h2>/<p> → canonical style.typography/style.color; styled <div style="display:flex;...">layout.type=flex + canonical color/padding.
    • Negative: unmappable props (position/inset/overflow) → className, not a raw style string.
    • Hidden-state: <nav style="display:none"> is not frozen; frozen_hidden_state finding surfaced.
  • Fixture churn (behavior fix, justified): 15 parity fixtures updated. Only the style-attribute shape changed — raw style string → canonical object / layout / dropped-to-className. No content or structure changes.
  • composer test fully green (contracts, unit, 128 parity fixtures, packaging); php -l clean.

Deferred long-tail (documented)

text-align (maps to a per-block align/textAlign attribute) and blockGap/gap are dropped for now rather than emitted as raw styles — the no-raw-string guard holds. These can ride in a follow-up.

🤖 Generated with Claude Code

Core blocks were emitted with a raw inline `style` STRING attribute
(e.g. wp:paragraph {"style":"color:..."}, wp:group {"style":"padding:..."}).
Core blocks expect a structured `style` OBJECT + `layout`, so the stored HTML
diverged from each block's save() and the editor flagged "unexpected or invalid
content" for nearly every styled element.

Generalize the ButtonStyleResolver approach (#241) to ALL blocks:

- New Style/StyleAttributeMapper translates resolved CSS declarations into the
  canonical block style OBJECT (style.typography/color/spacing/border) and
  serializes it back to the inline CSS + has-* support classes WordPress emits
  in save(), keeping stored markup in sync with the block.
- presentationAttributes() now returns a canonical `style` object (never a
  string) plus the `layout` attribute; display:flex|grid folds into `layout`.
- BlockFactory serializes the style object for every block (group, heading,
  paragraph, list, quote, columns, icon, ...) via the shared mapper.
- HARD RULE: a raw `style` string is never emitted on a core block. Unmappable
  declarations (position, overflow, transform, background images, aspect-ratio,
  nav anchor/submenu CSS, ...) are dropped and ride on the preserved className.
- Buttons keep resolving from the raw merged CSS via a threaded resolvedStyle
  callback (ButtonStyleResolver unchanged).
- Hidden-state safety (#259): display:none / visibility:hidden / opacity:0 base
  states are not frozen onto content-bearing/interactive elements; they are
  normalized away and surfaced as a frozen_hidden_state diagnostic finding.
  Genuinely decorative / aria-hidden nodes may stay hidden.

Tests:
- New "canonical block style attributes" contract: guard asserting no emitted
  core block carries a raw string `style`; positive cases (styled h2/p/div ->
  canonical typography/color + layout flex); negative case (unmappable props ->
  className, not a raw style string); hidden-nav normalization.
- 15 parity fixtures updated: only the style-attribute SHAPE changed
  (raw string -> canonical object / layout / dropped-to-className); content and
  structure are unchanged. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chubes4 chubes4 merged commit 93f6a33 into trunk Jun 28, 2026
1 check passed
@chubes4 chubes4 deleted the fix/canonical-block-style-attributes-261 branch June 28, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant