Preserve inline display overrides through the geometry carrier - #883
Merged
Conversation
Adds a deliberately RED contract for block, inline-block, and flex overrides of class-owned grid layout. The expected engine-support selector is :root .be-inline-geometry-* at specificity (0,2,0), without !important, while the existing css-owned-grid rule stays byte-frozen. RED evidence: php tests/unit/inline-display-carrier.php exits 1 with 12 failed and 14 passed assertions.
Carry display:block, display:inline-block, and display:flex only when matched static or conditional author CSS would reassert a different display mode. Flex-direction, flex-wrap, align-items, justify-content, and gap ride the same carrier for flex/inline-flex; grid-template-columns remains on the unchanged css-owned-grid path. Emit :root .be-inline-geometry-* at specificity (0,2,0) in source=engine-support before author CSS, with no !important, so a plain author class loses while authored !important retains its cascade priority. Existing sizing and forced grid declarations retain their current important rule. Gates: focused contract 34 assertions with 0 failures; composer test 275 parity fixtures with 0 failures; static-site raster parity remained 0.0000% for both solved fixtures.
This was referenced Aug 17, 2026
borkweb
added a commit
that referenced
this pull request
Aug 17, 2026
…font-family (#886) * test(php-transformer): freeze auto-fit grid and css-owned flex contracts ## Summary Adds two deliberately RED contracts, one per confirmed defect: `repeat(auto-fit, …)` track lists must not be converted to a native WordPress grid layout attribute, and a flex container demoted to a css-owned core/group must keep its authored `display:flex`. ## Why `StyleResolutionTrait::autoRepeatMinimumColumnWidth()` accepts both `auto-fit` and `auto-fill` and converts both to `layout.minimumColumnWidth`. `wp-includes/block-supports/layout.php` hardcodes `auto-fill` in every branch that renders that attribute, so an authored `auto-fit` grid renders with empty tracks retained and the content crammed into part of the measure. `cssOwnedGroupAttributes()` drops the native `layout` attribute on demotion. Grids survive because `CSS_OWNED_GRID_CARRIER_PROPERTIES` rides their inline declarations to the generated stylesheet; there is no flex equivalent, so the block is marked `blocks-engine-css-owned-flow` while the declaration that makes it a flex container is dropped and the children stack. ## How `tests/unit/auto-fit-grid-carrier.php` covers inline and class-owned `auto-fit` track lists plus `auto-fill` controls proving the native conversion is retained, not abandoned. `tests/unit/css-owned-flex-carrier.php` covers inline row and column flex containers plus controls for a container with no authored display and for a class-owned flex declaration the author stylesheet already owns. ## Testing RED evidence at this commit: `php tests/unit/auto-fit-grid-carrier.php` exits 1 with 6 failed and 5 passed assertions; `php tests/unit/css-owned-flex-carrier.php` exits 1 with 6 failed and 6 passed assertions. * fix(php-transformer): keep auto-fit grids and demoted flex containers intact Restricts the native grid-layout conversion to `auto-fill`, and carries the inline flex declarations of a container demoted to a css-owned core/group. `autoRepeatMinimumColumnWidth()` accepted `auto-fit` and `auto-fill` alike and converted both to `layout.minimumColumnWidth`. `wp-includes/block-supports/layout.php` hardcodes `auto-fill` in every branch that renders that attribute, so an authored `auto-fit` grid rendered with the empty tracks `auto-fit` collapses still retained, squeezing the content into part of the measure. Measured against a design, delivered tracks came back `267 267 267 267` where the design resolved `592 592 0 0`. `cssOwnedGroupAttributes()` drops the native `layout` attribute on demotion. Grids survive because `CSS_OWNED_GRID_CARRIER_PROPERTIES` rides their inline declarations to the generated stylesheet. There was no flex equivalent, so a container marked `blocks-engine-css-owned-flow` lost the declaration that made it a flex container and its children stacked. `auto-fit` now returns '' from the conversion and falls through to the same css-owned-grid carrier that already holds every other track list WordPress cannot express, so the author rule stays the single owner of the exact authored track list. `auto-fill` keeps converting natively. `CSS_OWNED_FLEX_CARRIER_PROPERTIES` plus `isCssOwnedFlexElement()`/`cssOwnedFlexAttributes()` mirror the grid carrier for flex containers. An inline display that overrides class-owned layout is skipped: #883 already carries those properties complete, at the non-important specificity tier, and forcing them would move them to the important tier. - [ ] `composer test:unit` — 0 failures across 37 scripts, including the two new contracts at 11 and 12 assertions. - [ ] `composer test` — 0 failures, 275 parity fixtures. Seven parity fixtures moved, each because it encoded one of the two defects: three auto-fit fixtures asserted the native conversion (one renamed, since `html-autofit-grid-maps-to-minimum-column-width` named the buggy contract), and four flex fixtures asserted output in which the authored `display:flex` had been dropped. * test(php-transformer): freeze the button font-family carry contract ## Summary Adds a deliberately RED contract proving an authored `font-family` on a button-mapped rule must reach the core/button block attributes, and that the other carried declarations keep mapping exactly as they do today. ## Why `ButtonStyleResolver::BUTTON_TYPOGRAPHY` projects `fontSize`, `fontWeight`, `letterSpacing`, `lineHeight` and `textTransform` onto buttons and drops `font-family`. core/button registers a `fontFamily` attribute, which core injects only when the typography fontFamily support is enabled, so this is a dropped declaration rather than an unsupported one. The drop is not benign. The authored `.btn` class is consumed into block attributes, so the surviving author rule is rewritten behind `:where()` at zero specificity and matches nothing that can win. theme.json's `styles.elements.button.typography.fontFamily` then substitutes the design-direction typeface, which widens the measure enough to wrap hero CTAs onto two lines. ## How `tests/unit/button-font-family-carry.php` asserts the support exists before relying on it, pins the resolver's complete attribute output so no other carried declaration can drift, covers a rule that authors no font-family, and repeats the end-to-end shape from the report where the class is consumed. ## Testing RED evidence at this commit: `php tests/unit/button-font-family-carry.php` exits 1 with 5 failed and 4 passed assertions. The passing four include the support check, which must hold before the fix is safe to write. * fix(php-transformer): carry an authored font-family onto core/button ## Summary Adds `fontFamily` to the typography subset projected onto core/button, and to the save-markup serializer that renders those attributes. ## Why `ButtonStyleResolver` carried a button rule's background, text color, border, radius, font-size, weight, letter-spacing and text-transform, but dropped `font-family`. core/button registers a `fontFamily` attribute, which core injects only when the typography fontFamily support is enabled, so the declaration was expressible all along. Because the authored class is consumed into block attributes, its surviving rule is rewritten behind `:where()` at zero specificity and cannot win. theme.json's `styles.elements.button.typography.fontFamily` therefore substituted the design-direction typeface, widening hero CTAs enough to wrap them onto two lines. ## How A raw authored family is a custom value rather than a preset slug, so it rides in `style.typography.fontFamily`. `BlockFactory` did not map that attribute either, so the block attribute alone would not have reached the rendered link; the serializer now emits `font-family` inline the way core's style engine does, ahead of the other typography declarations. Inline style outranks theme.json's element styles, so no `!important` bridge is needed and none was added. ## Testing - [ ] `composer test:unit` — 0 failures across 39 scripts. - [ ] `composer test` — 0 failures across 51 scripts, 275 parity fixtures, none edited for this defect. - [ ] `php tests/unit/button-font-family-carry.php` — 9 assertions, up from 4 passing and 5 failing at the RED commit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An inline
displaydeclaration is silently dropped, so astyle="display:block"written toneutralize a class-provided grid disappears and the grid applies unopposed.
The defect
StyleResolutionTrait::inlineGeometryProperties()is the allowlist of inline-style declarationspreserved onto a generated
be-inline-geometry-*carrier class. It was sizing-only:displayis absent, so an inlinedisplayhas no carrier and is dropped.HtmlTransformer::CSS_OWNED_GRID_CARRIER_PROPERTIESdoes includedisplay, but that path only fireswhen the element IS a css-owned grid or flex container. It never fires for a
display:blockwhoseentire purpose is to TURN OFF a grid the element inherits from a class rule.
Why it matters
A consumer that generates HTML with reusable classes commonly re-purposes a layout container by
neutralizing it inline:
which transformed to:
The class survives, the neutralizing
displaydoes not, so.hero-inner{display:grid; align-items:center}applies with nothing opposing it and every child becomes a grid item. In onereal page that single dropped declaration produced four separate visible defects: a pill stretched
to fill its track, an
<h2>pushed into grid column 2, a heading taking the wrong track's scale, andthree cards sitting side by side instead of stacked full width.
Occurrences of inline
displayacross seven real generated sites: 52, 46, 10, 6, 2, 0, 0. The twosites at zero were the only two with no layout complaint.
What changed
displayand its layout companions are carried through the existing geometry-carrier mechanism, viaa new
inlineLayoutCarrierProperties()list merged intoinlineGeometryProperties().Two things keep it narrow:
flex-direction,flex-wrap,align-items,justify-content,gap) ridealong only when the inline
displayisflex/inline-flex. A plaindisplay:blockcarriesdisplayalone.otherwise they are dropped as before.
Carried layout declarations emit as their own rule at specificity (0,2,0):
That beats a plain class selector like
.hero-inner(0,1,0) so the neutralization wins, and it usesno
!important— an authored!importantkeeps its normal cascade priority. Pre-existinggeometry properties keep their separate
!importantrule at (0,1,0) exactly as before.Verification
test:parity— 275 fixtures passed, 0 failedstatic-parityoutput is byte-identical to trunk (diffclean across all 87 fixtures). Thischange alters no static-site output, which is expected: the conflict it resolves only exists in a
WordPress layout context. It also confirms the pre-existing
failrows in that matrix are notintroduced here.
left column, and the cards stack full width, matching the source design.