fix(php-transformer): preserve responsive grids and gate layout by block supports - #815
Merged
Conversation
## Summary Responsive CSS grids no longer collapse to single-column stacks after transform. Grids declared as repeat(auto-fit|auto-fill, minmax(W, 1fr)) map to native WordPress grid layout, grids WordPress layout cannot express keep their geometry under a css-owned-grid carrier, and preserved grids retain their gap and container background. ## Why The css-owned demotion introduced by #813 stopped invalid grid/columns combinations but resolved every author-owned grid to a flow group: the layout attribute was unset, no mechanism carried grid-template-columns for inline-styled grids, and hairline-divider grids (gap:1px plus a background painting through the gaps) lost both properties. On real sites every auto-fit gallery, process row, and client strip rendered as a vertical stack. ## How The layout resolver recognizes the one track list native grid layout expresses exactly, repeat(auto-fit|auto-fill, minmax(W, 1fr)), and emits layout {type:grid, minimumColumnWidth:W}; cssOwnedGroupAttributes keeps that layout instead of demoting, and carries the author gap as blockGap plus the container background as a color support so the block stays faithful without the materialized author stylesheet. BlockFactory stops stripping blockGap from grid-layout groups. Non-expressible grids take a new blocks-engine-css-owned-grid class (instead of the flow class) with a :where margin reset, and inline grid declarations ride to the generated stylesheet on the existing inline-geometry carrier. The #813 guard is untouched: grid layouts still decline core/columns. ## Testing - [ ] composer test (parity fixtures, contracts, and unit suites all pass; three new parity fixtures cover the auto-fit mapping, the css-owned-grid carrier, and gap/background carry)
…arry
## Summary
Layout attributes now emit only on blocks whose vendored block.json supports accept them, grids on unsupporting blocks (core/list) ride the css-owned grid carrier instead, named head metadata surfaces in source_reports.head_metadata, and the review pass hardened the grid-carry edges from the previous commit.
## Why
Stamping layout {type:grid} on core/quote (layout is block-managed, allowEditing:false) and core/list (no layout support) baked is-layout-grid classes into save markup the canonical block saves never emit, so downstream re-serialization rejected those blocks and reverted whole sections to unmodified markup. Separately, meta name=description was silently stripped with no fallback, diagnostic, or report, reading as a malformed design rather than a deliberate omission. The adversarial review of the grid-preservation commit also surfaced hardening gaps: gap:0 fell back to the theme default gap, a background shorthand could serialize as an invalid color support, forced carrier synthesis could invent a gap overriding explicit axis gaps, carried values containing comment openers could poison the generated stylesheet, and a dead blockGap assignment clobbered mapped spacing supports.
## How
BlockFactory::normalizeAttrsForBlock gains a supports gate derived from the vendored block-library block.json files: layout passes only for the allowlisted blocks and type:grid only where supports permit switching to grid, and gallery fixtures updated since their pinned grid layout was itself invalid (gallery pins a fixed flex default). Grid-declared list elements route through the extracted cssOwnedGridAttributes helper, so the clients-strip ul keeps its auto-fit tracks via the inline geometry carrier without a layout attribute, and the grid margin reset is class-scoped to cover list hosts. HtmlTransformer::headMetadataReport parses the original document head, reports named meta entries (excluding mechanical charset/viewport) under source_reports.head_metadata, and emits an html_head_metadata_not_carried info diagnostic. The hardening pass restricts carried properties to inline-present declarations, widens the carrier property list to grid shorthands, accepts unitless zero gap, whitelists single color tokens for background promotion, defers to author CSS when conditional rules touch the layout or background family, rejects zero minimumColumnWidth, and drops carried values containing comment openers.
## Testing
- [ ] composer test (269 parity fixtures incl. five new: layout gating, head metadata, zero gap, inline-leaf grid items, css-owned grid carrier; all contract and unit suites pass)
- [ ] Real-corpus check: portfolio-new home.html transforms with zero quote/list layout attrs, two native auto-fit grids, the clients list on the css-owned grid carrier, and zero fallbacks
…marker ingestion ## Summary Adversarial-review hardening of the grid-carry and layout-gating work: carried declarations emit in source order so the CSS cascade matches the author document, marker classes are filtered at ingestion so round-tripped transformer output cannot trip the grid heuristics, and the block-supports allowlists follow the vendored rule exactly. ## Why The generated carrier rule sorted declarations alphabetically with per-declaration !important, letting the alphabet pick shorthand/longhand winners: a grid shorthand declared after grid-template-columns was reversed, gap reset a later column-gap, and grid-row-gap aliases flipped. display:grid !important defeated the carrier detection entirely, silently dropping list grids. The head-metadata report was unbounded (a hostile document could inflate diagnostics arbitrarily). Re-ingested transformer output preserved blocks-engine-css-owned-grid as an author class, which matched the *-grid heuristic and turned stacked content into an auto-fill grid. The layout allowlist contradicted its own rule by including columns and gallery (both declare allowEditing:false, like quote) and omitting the layout:true blocks (accordion, tabs, term templates). ## How inlineGeometryClassName orders carried declarations by inline source position (non-inline fallbacks sort last); isCssOwnedGridElement strips a trailing !important before comparing display values; headMetadataReport caps output at 20 entries and 500 chars per content value; a new ingestion-only isTransformerMarkerClassName predicate excludes blocks-engine-*/be-inline-geometry-* tokens from preserved classNames and the grid-class heuristics (deliberately not in GeneratedGutenbergClassPolicy, which BlockFactory also uses to filter emitted classNames); the dl-to-list fallback gains the same grid-carrier branch as ul/ol; and the BlockFactory allowlists now match the vendored block.json supports rule exactly. Three test pins that encoded the old alphabetical serialization order were updated to the source-order output. ## Testing - [ ] composer test (269 parity fixtures, all contract and unit suites pass) - [ ] Adversarial repro probes: shorthand-after-longhand and gap-after-column-gap orderings match browser cascade; !important list grids get the carrier; marker-class round-trip produces no layout, no preserved marker, no spurious reset rule; meta report capped at 20x500
## Summary The WordPress site-plan integration test pinned the fixture87 custom-property carrier rule in alphabetical order; carried declarations now emit in source order, so the pin follows the inline declaration order (--h, --a, --b). ## Why The carried-declaration ordering fix (source order instead of ksort, so the CSS cascade matches the author document) changed the serialized rule layout. This test only runs under a WordPress test environment (REQUIRE_WP_TESTS=1), so the local composer test chain skipped it and CI caught it on the PR. ## Testing - [ ] Byte-exact replacement verified against the actual compiled carrier rule for the same fixture input locally - [ ] CI WordPress site plan integration re-run on the PR
# Conflicts: # php-transformer/src/HtmlToBlocks/BlockFactory.php
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.
Summary
Responsive CSS grids survive the transform instead of collapsing to single-column stacks, and the
layoutattribute is only emitted on blocks whose supports actually accept one — fixing both the auto-fit grid collapse and the downstream section reverts caused by invalid layout attributes oncore/quote/core/list. Named head metadata (meta name=description, social tags) now surfaces in the transform report instead of being silently stripped.Fixes
repeat(auto-fit|auto-fill, minmax(W, 1fr))maps tolayout:{type:grid, minimumColumnWidth:W}oncore/groupinstead of demoting to a css-owned flow group.core/list) keep their geometry via ablocks-engine-css-owned-gridmarker plus inline declarations carried to the generated stylesheet, instead of the flow demotion that dropped the tracks.gap:1px+ container background) ride block supports (blockGap, color) so they survive without the materialized author stylesheet;BlockFactoryno longer stripsblockGapfrom grid-layout groups.block.jsonfiles;type:gridonly where supports permit switching. Never oncore/quote(allowEditing:false) orcore/list(no layout support), whose invalid attributes were bakingis-layout-gridclasses into save markup and reverting whole sections downstream.source_reports.head_metadata+ anhtml_head_metadata_not_carriedinfo diagnostic (capped at 20 entries × 500 chars) replace the silent strip.Two adversarial review passes hardened the edges: carried declarations emit in source order (alphabetical sorting was flipping shorthand/longhand cascade winners),
display:grid !importantno longer defeats carrier detection, transformer marker classes are filtered at ingestion so round-tripped output can't trip the grid heuristics, and background promotion is whitelisted to single color tokens with responsive-override deference.Testing
composer test— 269 parity fixtures (8 new, each written red-first) plus all contract and unit suites pass.🤖 Generated with Claude Code