Hoist a branding anchor out of the promoted navigation - #887
Open
borkweb wants to merge 7 commits into
Open
Conversation
…ed navigation A nav landmark that authors a branding anchor beside a menu list collapses into one core/navigation with the brand as an extra menu item. The deferral guard only recognises a brand anchor whose class matches a hard-coded vocabulary, so a class outside that list leaves the collapse in place. Two consequences ride along: the brand emits `anchorClassName`, which is not a registered core/navigation-link attribute, and the menu list's className is copied onto the nav container where it outranks the landmark's own padding rule. Red first: the unit contract reports 10 failed / 7 passed and the parity fixture fails at `blocks.0.innerBlocks` with 4 children instead of 2. Covers gates N3 and N4.
Pattern recognizers could convert an element's children but not an element itself. A pattern that keeps a sibling out of its own block — a branding anchor beside a menu, for example — needs that sibling emitted exactly as the generic pipeline would emit it anywhere else, not rebuilt by hand from inner HTML. Adds a nullable `convertElement` callback to PatternContext and wires it to the element conversion entry point. No behavior change: nothing consumes it yet, and the probe context leaves it null.
A nav container holding a branding anchor beside its link cluster authors three elements, each with its own CSS rule. Promoting all three into one core/navigation made the brand a menu item, so the landmark's own box rules and the menu list's rules competed on a single element and the brand emitted `anchorClassName` — an attribute core/navigation-link does not register, which stops a consumer's block transformation on the whole part. The landmark is now emitted as a core/group carrier holding the brand block and a core/navigation built from the link cluster alone. The brand is detected structurally — a direct-child anchor outside the cluster, one anchor and one cluster, no other significant children — so no class vocabulary decides whether it survives, and it is converted by the generic pipeline rather than rebuilt from inner HTML. The cluster owns the navigation block's presentation, so the menu list's className stays with the menu. The existing deferral guard still runs first and keeps the shapes it already recognises unchanged. Any container the carrier cannot fully account for falls through to the previous behavior. Closes gates N3 and N4.
`navigationBlocks()` refuses to promote a container whose skipped chrome is a runtime DOM target, because a promoted navigation would drop an element that scripts drive. The brand carrier skipped chrome without that check, so it could have promoted a container the sibling path would have deferred. Mirrors the existing guard rather than inventing a second policy.
The carrier reused the deferral guard's rule that a branding anchor must hold no block-level markup. That rule exists because the guard's sibling path flattens the anchor into a menu item label, where a heading or div would be stripped. The carrier converts the anchor instead, so the restriction only kept a `<a class="wordmark"><h1>…</h1></a>` lockup absorbed into the menu — emitting the unregistered `anchorClassName` and losing the heading. Verified against the parent commit: both a heading lockup and a two-div lockup went from one absorbed menu item to a converted brand block, `anchorClassName` 1 -> 0.
…f a menu Structural position alone does not identify a brand. A nav that authors an ordinary menu link outside its list — `<nav><a>Home</a><ul>…</ul></nav>`, a common hand-authored shape — had that link hoisted into a sibling block: it left the editable menu, lost the promoted shared colour, and stopped counting as a menu item. The anchor must now also read as branding: a lockup built from element children, or an explicit brand/logo cue. Every shape the carrier targets qualifies — a name-plus-place lockup, a heading lockup, a single-line wordmark — while bare anchor text stays a menu item. A fixture pins that bare-text shape to the behavior it has without the carrier. The carrier is also restricted to a real `<nav>` landmark rather than any navigation-signalled container, which removes an untestable branch: a consumer's raw-anchor link resolution scopes by lexical `<nav>` ancestry, so a brand hoisted into a `div` carrier would fall outside both that pass and the block pass that rewrites navigation links, losing coverage the folded shape had. The reason now sits in the comment beside the tag, with a fixture pinning the nested landmark as harmless: core/navigation is dynamic, so saved content still holds one `<nav>`. Cheap structural checks now run before either conversion, so a bail no longer leaves provenance and runtime-island records behind for output never emitted. The comment no longer claims more coverage than the code has — an anchor holding only an image with no accessible name is still classified as chrome before it reaches the brand test.
Semantic parity counts every anchor under a source nav landmark on one side and
only core/navigation children on the other. Hoisting the brand out of the menu
therefore read as content loss: the azure-garden header went from parity `pass` to
`warning` with `navigation_item_count_mismatch` (source 6, block 5) — a false
positive on the exact shape the hoist exists to fix, and one the parity fixture's
`status == "success"` assertion could not catch.
When a navigation block's landmark is a `core/group{tagName:"nav"}` carrier, the
blocks hoisted out beside it now contribute their anchors to that menu's item
list. Counting them here rather than excluding them on the source side keeps a
brand that disappears entirely detectable as real loss.
Two existing parity policies already exclude outside anchors on the source side —
a landmark bearing mobile chrome, and one holding both a brand and a CTA beside
its list. The carrier's items are only added when neither applies, so the same
anchors are never counted on one side while being deliberately dropped on the
other. The fixture now pins `semantic_parity == "pass"`.
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.
What
A nav container that authors a branding anchor beside its link cluster is three
elements — the landmark, the brand, and the menu — each carrying its own CSS rule.
The transformer promoted all three into one
core/navigation, which made the branda menu item. Three consequences followed from that single collapse:
same element, because the list's
classNamewas copied onto the navcontainer. An authored
header nav{padding:22px}(specificity 0,0,2) then lost toa
.navlinks{padding:0}(0,1,0) that was never meant to apply to the landmark.header nav{justify-content:space-between}became a no-op.anchorClassName, whichcore/navigation-linkdoes notregister. A consumer that validates comment attributes stops transforming the
whole part on that attribute, so every other class on the saved wrapper never
arrives.
The deferral guard that should have prevented the collapse only fires when a
direct-child anchor's class matches a hard-coded vocabulary
(
brand|branding|logo|site-title|site-name|home-link|home-logo) and adirect-child
<ul>/<ol>parses as nav items. A brand class outside thatvocabulary, or a link cluster authored as a
<div>, slipped straight past it.How
NavigationPattern::match()now emits the landmark as acore/groupcarrierholding the brand block and a
core/navigationbuilt from the link cluster alone.link cluster — exactly one anchor, exactly one cluster, no other significant
children — so no class vocabulary decides whether it survives. The next designer
who picks a different class name does not reopen the defect.
core/navigationtakes itspresentation from the element it stands in for, so the menu list's
classNameand gap stay with the menu instead of being copied onto the landmark. The
transformer's own hooks — the injected
blocks-engine-list-navigationclass andthe
blockGap: 0pxdefault for list sources — are unchanged.PatternContextgains a nullableconvertElementcallback so the anchor is emitted exactly as the genericpipeline emits it anywhere else in the document, keeping its own class and its
inline-format projection. A brand that would only convert to an HTML fallback
makes the carrier bail, so the change can never trade a menu item for raw markup.
keeps today's behavior. The carrier covers exactly the containers that would
otherwise absorb their brand.
With the brand out of the link set,
commonNavigationLinkTextAttributes()sees amenu whose links all share one colour and promotes it to the navigation block as
customTextColor, which core honours — the authored link colour used to bediscarded because the brand, with no colour of its own, was the first link.
Tests
tests/unit/navigation-brand-anchor-hoist.php— 17 assertions: the unlistedbrand class, the
<div>link cluster with no list, the className placement, anda control proving a menu with no brand still promotes to a single navigation
block with no carrier group.
tests/fixtures/parity/html-nav-brand-anchor-hoist.json— product-neutral headershape pinning the emitted topology.
Committed red first: at the test-only commit the unit contract reported 10 failed /
7 passed and the fixture failed at
blocks.0.innerBlockswith 4 children insteadof 2.
No existing fixture was modified and no threshold was relaxed.