Skip to content

fix(magecommand-less): at-rule selector scoping follows the less.php version - #117

Open
pingiun wants to merge 2 commits into
mainfrom
fix/less-at-rule-selector-scoping
Open

fix(magecommand-less): at-rule selector scoping follows the less.php version#117
pingiun wants to merge 2 commits into
mainfrom
fix/less-at-rule-selector-scoping

Conversation

@pingiun

@pingiun pingiun commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Magento's own _rules-temp.less nests an @-moz-document url-prefix() fieldset fix inside .admin__scope-old. Whether that nesting carries the enclosing selector into the at-rule's body is isRooted in the parser's directive table — and it is version-dependent, which we modelled as a constant.

dialect non-rooted set that fix deploys as
less.php 3.x (no isRooted concept at all) .rule-tree .fieldset
less.php 5.x @document, @supports .admin__scope-old .rule-tree .fieldset
less.js 4.x + @starting-style, @layer

We hardcoded the less.js 4.x set for every profile, so a 3.x store got a rule scoped to .admin__scope-old that the real deploy applies globally. That is a cascade difference, not formatting — the Firefox fieldset fix silently stops applying outside that scope.

The set is now data on LessOptions (non_rooted_at_rules), chosen by the profile the deploy already detects from the store's installed wikimedia/less.php.

One wrong turn worth recording

I first "fixed" this by matching on the raw at-rule name instead of the vendor-stripped one — reasoning that @-moz-document misses a case "@document" arm. That produced the right answer on the 3.x store and broke the 5.x one, because both versions compute a nonVendorSpecificName before the switch. The versions differ in the table, not in prefix handling. Caught it by checking the second store rather than shipping on one green result.

Validation

Against a real setup:static-content:deploy on two stores running different less.php versions:

  • 5.5.1 store: styles-old.css 15 semantic findings → 0
  • 3.2.1 store: loses the two findings the at-rule contributed (its remaining 13 are the separate :extend over-generation)

The vendored less.js fixture suite is unchanged — it runs the LessJs profile, whose set keeps all four entries. Workspace tests and clippy green.

The conformance test pins both directions plus a rooted control (@media still nests by its own path, so the fix didn't make everything wrap), and I confirmed it fails when the 3.x entry is removed.

Still open on these stores

Unrelated and untouched: :extend over-generating selector lists (13 and 106 findings on the 3.x store), the cssmin #ffffffeb#fff eb serialization difference (where Magento's output is the broken one), and url() relocation — on which I owe a correction: both magecommand's and Magento's URLs 404 there, the working path being a third one, so that one is not the visible breakage I first called it.

🤖 Generated with Claude Code

pingiun and others added 2 commits September 9, 2026 13:01
…version

Magento's own `_rules-temp.less` nests an `@-moz-document url-prefix()` fieldset
fix inside `.admin__scope-old`. Whether that nesting carries the enclosing
selector into the at-rule's body is `isRooted` in the parser's directive table —
and it is VERSION-DEPENDENT, which we modelled as a constant:

- less.php 3.x has no `isRooted` concept at all, so nothing carries the
  selector, and a 3.2.1 store deploys `.rule-tree .fieldset`;
- less.php 5.x sets `$isRooted = false` for `@document`/`@supports`, and a 5.5.1
  store deploys `.admin__scope-old .rule-tree .fieldset`;
- less.js 4.x adds `@starting-style` and `@layer` on top.

We hardcoded the less.js 4.x set for every profile, so the 3.x store got a rule
scoped to `.admin__scope-old` that the real deploy applies globally — a cascade
difference, not formatting. The set is now data on `LessOptions`
(`non_rooted_at_rules`), chosen by the profile the deploy already detects from
the store's installed `wikimedia/less.php`.

Matched on the vendor-STRIPPED name throughout: every version computes a
`nonVendorSpecificName` before its switch, so `@-moz-document` is `@document`.
(I briefly "fixed" this by matching the raw name instead, which produced the
right answer on the 3.x store and broke the 5.x one — the versions differ in the
TABLE, not in prefix handling.)

Verified against a real `setup:static-content:deploy` on both: the 5.5.1 store's
`styles-old.css` goes from 15 semantic findings to 0, and the 3.2.1 store loses
the two the at-rule contributed. The vendored less.js fixture suite is unchanged
— it runs the LessJs profile, whose set keeps all four entries.

The conformance test pins both directions and a rooted control (`@media` still
nests by its own path); it fails if the 3.x entry is removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ss.php, and the extend gap it found

`wikimedia/less.php` 3.x and 5.x are different compilers, and Magento 2.4.7 and
2.4.8 ship them respectively — so a single source file legitimately deploys
different css on two stores. Reasoning about which side is "right" from a
deployed stylesheet alone does not work; both times I tried, I got it backwards
once before checking the second store.

`scripts/diff-against-less-php.sh` compiles a `.less` with a STORE's own
library and diffs it against ours. Pointed at two stores on different majors it
separates "we are wrong" from "the versions disagree", which is the distinction
that decides whether anything needs fixing at all. It found the at-rule scoping
difference and the one below.

The one below: `&:extend` inside a MIXIN called more than once. Each call site
registers its own extend, and less.php 5.x lets the second match selectors the
FIRST call site generated, emitting `.abs .sub .sub` from

    .mx() { color: red; .sub { &:extend(.menu all); color: blue; } }
    .menu { .mx(); }
    .abs  { .mx(); }

Writing that structure out literally, or calling the mixin once, produces no
such selector on either version — the duplicated mixin is the whole trigger.

We match 5.x byte for byte, which the new conformance test pins. less.php 3.x
does NOT emit them, so a 2.4.7 store deploys a smaller stylesheet than we
produce: ~17KB of extra selectors across one real store's admin theme, 106
findings in `styles.css` and 13 in `styles-old.css`. Bloat rather than a cascade
change — every selector we add is a descendant chain matching nothing the
reference matched — but it is real, and the `Magento247` profile does not model
2.5.3's extend semantics yet.

Not attempted here on purpose: the two versions' `ProcessExtends` visitors are
byte-identical modulo formatting, so the divergence originates upstream of the
extend pass, and guessing at it risks the 5.x path that currently matches
exactly. The minimal case is pinned and reproducible; that is the right place
for the next person to start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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