Skip to content

fix(php-transformer): read a button that controls a sibling link panel as a submenu - #2781

Merged
ellenbauer merged 3 commits into
trunkfrom
fix/nav-aria-controls-dropdown-item
Oct 14, 2026
Merged

ellenbauer merged 3 commits into
trunkfrom
fix/nav-aria-controls-dropdown-item

Conversation

@ellenbauer

@ellenbauer ellenbauer commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Refs #2726. Follow-up to #2743.

What was wrong

Checking #2743 at 1280 on St Francis Pet Hospital (Squarespace) showed a desktop change: the "About Us" folder's four links now sit in the top row, and the "About Us" label is gone. The source shows "About Us" with a dropdown.

Squarespace writes a desktop folder as:

div.header-nav-item--folder
  button.header-nav-folder-title[aria-controls="about-us"]  About Us
  div.header-nav-folder-content#about-us
    div.header-nav-folder-item > a   Our Mission & Commitment
    div.header-nav-folder-item > a   ...

No navigation reading covered a button that names its sibling link panel with aria-controls. buttonDropdownItemParts() only accepts a panel wired by the capture (data-blocks-engine-capture-dialog-panel plus aria-haspopup="menu"). So the folder was read by accident:

What changed

NavigationPattern::buttonDropdownItem() also accepts controlledDropdownItemParts(): an item whose only children are a labelled button and the sibling element its aria-controls names, where every panel entry is a link with a destination, bare or as the only child of its own wrapper. The button label becomes the submenu label and the panel's links its children, through the existing button dropdown path. The button's own declaration ties the panel to it, so no class vocabulary is used.

Tests

  • New tests/unit/navigation-controlled-dropdown-item.php: the controlled panel becomes submenu:About Us with its two links, and a button that names another element does not claim the panel. On trunk the first fails (the folder is dropped); both pass here.
  • navigation-peer-item-wrappers, navigation-anchor-cluster-submenu and navigation-button-dropdown-submenu still pass.
  • capture-evidence-equivalence: 349/349 unchanged.
  • composer test (memory_limit -1) passes on this branch, on trunk 2a963f0 (v0.40.0).

Evidence

Review follow-up (second commit)

Review of #2743 found two shapes that fell through once peer wrappers stopped reading as submenus. Both are fixed here because they are the same reading as the aria-controls panel:

  1. Squarespace desktop folder titled by a link (div.header-nav-item--folder > a.header-nav-folder-title + div.header-nav-folder-content > div.header-nav-folder-item > a). The anchor-only cluster reading accepted only bare links, so the entries came out as top-level links in the bar. The panel's class was lost, and Services lost header-nav-item with its padding and font size. Now the item's own link owns a sibling panel of links that are bare or one per wrapper (isWrappedLinkPanel(), shared with the aria-controls reading). Result: Home, Services (Alpha, Beta), Contact, and Services keeps header-nav-item header-nav-item--folder.
  2. Parent link in a label wrapper (<div><span><a>Parent</a></span><div><a>One child</a></div></div>). The guard wanted the link as the item's direct child, so on trunk the whole menu was dropped. Now a label wrapper of another tag whose only content is the link also owns the cluster. A wrapper of the same tag beside it is still a peer item, so the St Francis root folder stays flat.

Nit from the review: the aria-controls submenu label now leaves out screen-reader-only text (sr-only, visually-hidden, screen-reader-text, hidden).

Tests added: tests/unit/navigation-owned-submenu-panel.php (4 assertions, 3 fail on the PR base) and a screen-reader label case in navigation-controlled-dropdown-item.php (fails on the PR base). The reviewer's probes (sqs71-folder-anchor-title, sqs-desktop-folder, sqs71-folder-button-aria, sqs-folder-bare-anchors) all produce the folder as a submenu. Full unit run (memory_limit -1, one process per test): 430 pass, 0 fail. capture-evidence-equivalence: 349/349 unchanged.

Review round 2 (third commit, 23fe60c)

The wrapped-label reading from the second commit also matched peers and footer columns:

  • div.list > span.item > a beside div.item > a items became submenu:Home;
  • p.item > a beside div.item > a,a did the same;
  • nav.cols > div.col > h4 > a + div.links > a,a became closed submenus.

A wrapped label now owns the cluster only when the item holds exactly the two of them, the label is not a heading, and the two share neither a tag nor a class. The orphaned isAnchorOnlyCluster() docblock is back on its method.

Reviewer probes (cases5.php) against trunk 2a963f0: the three shapes above, mega-li-span-div, li-span-beside-li-div, span-parent-*, div-parent-cluster, div-nested-items, stfrancis-root-folder, the WordPress and Wix menus and sqs-desktop-flat are now identical to trunk. The only differences from trunk are the intended ones: sqs-desktop-folder, sqs71-folder-anchor-title, sqs71-folder-button-aria, nav-btn-aria-controls-links and button-sronly-label read the folder as a submenu.

navigation-owned-submenu-panel.php now also asserts that the three shapes get no submenu (7 assertions). The PR head before this commit fails those 3; trunk fails the 3 positive ones. capture-evidence-equivalence: 349/349 unchanged. Full unit run (memory_limit -1, one process per test): 430 pass, 0 fail.

Import proof (workbench, new-vocabulary captures)

Studio CLI create --from with the liberation workbench captures (DLA main 569c336). The baseline is SSI v1.31.2 with BE trunk 2a963f0 (wb-*-trunk). The proof package is the same ZIP with #2780, #2781 and #2783 merged on 2a963f0 (ssi-be-proof-2726-combined-e51205f.zip). iPhone user agent at 375; desktop at 1280. Quality gate passed on every import (quality_pass: true, fail_import: false). The proof sites are stopped, not deleted.

  • St Francis Pet Hospital at 1280, trunk (localhost:8954) against proof (localhost:8955): the desktop nav goes from Home, Our Mission & Commitment, Common Sense..., Our Team, St. Francis of Assisi, Services, ... (folder links flattened into the bar, three rows) to Home, About Us (submenu), Services, Telemedicine, Careers, Contact Us, Online Pharmacy (two rows, as in the source). The only differing pixels are in the nav row (54,234 to 905,460).
  • Still open: the "About Us" label paints in the default text colour instead of white.
  • St Francis at 375: 0 differing pixels with the menu closed. With the menu open, all root items are on screen at x=0 on both.
  • stevenheaney90 and Dunbar desktop: no change at 1280 (0 differing pixels).

🤖 Generated with Claude Code

…l as a submenu

A menu item made of a labelled button and the sibling panel it names with
aria-controls (`div > button[aria-controls=p] + div#p > div > a ...`) had no
reading. Squarespace's desktop folders use it. Before #2743 the first child
link was taken as the submenu label; after it the child links were
flattened into the top row; with generic names the folder was dropped.

buttonDropdownItem() now also accepts that shape when every panel entry is a
link, bare or in its own wrapper. The button label becomes the submenu label
and the panel's links its children.

Refs #2726

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e it

Review of #2743 found two submenu shapes that fell through once peer
wrappers stopped reading as submenus:

- A Squarespace desktop folder titled by a link (`div.item > a.title +
  div.panel > div.entry > a`). The anchor-only cluster reading accepted only
  bare links, so the panel's wrapped entries came out as top-level links in
  the bar and the title lost the item's classes.
- A parent link in a label wrapper (`div > span > a + div > a`). The guard
  wanted the link as the item's direct child, so the menu was dropped.

ownsSubmenuCluster() now accepts the item's own link as a direct child or as
the only content of a label wrapper of another tag, and a cluster of links
that are bare or one per wrapper (isWrappedLinkPanel(), shared with the
aria-controls panel reading). A wrapper of the same tag beside the label is
still a peer item.

The aria-controls submenu label also leaves out screen-reader-only text.

Refs #2726

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ellenbauer

Copy link
Copy Markdown
Contributor Author

Please hold: our independent review found the new label-wrapper rule in ownsSubmenuCluster turns mixed-tag peer items (span.item + div.item) and footer columns with linked h4 headings into submenus. Tightening in progress on this branch.

… label submenu reading

Review of 0fc24a5: a wrapped label of another tag also matched peers and
footer columns:

- `div.list > span.item > a` beside `div.item > a` items became a submenu
  of Home holding the rest;
- `p.item > a` beside `div.item > a,a` did the same;
- footer columns `div.col > h4 > a + div.links > a,a` became closed
  submenus.

A wrapped label now owns the cluster only when the item holds exactly the
two of them, the label is not a heading, and they share neither a tag nor a
class. The isAnchorOnlyCluster() docblock is back on its method.

Refs #2726

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ellenbauer
ellenbauer marked this pull request as ready for review October 14, 2026 06:54
@ellenbauer
ellenbauer merged commit 43fd4cb into trunk Oct 14, 2026
22 checks passed
@ellenbauer
ellenbauer deleted the fix/nav-aria-controls-dropdown-item branch October 14, 2026 06:54
@ellenbauer

Copy link
Copy Markdown
Contributor Author

Hold lifted. Merged after three independent review rounds. Fixes the #2743 desktop regression: Squarespace folders titled by a button (aria-controls) or by a link now read as submenus, a label link in a wrapper owns its sibling panel only for a two-element, non-heading, distinct-tag/class item, and screen-reader-only text is excluded from labels. Mixed-tag peers and footer columns with linked headings stay as on trunk. Import-proven on St Francis (About Us dropdown restored). Refs #2726. cc @chubes4

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.

1 participant