Repository navigation
fix(1448): require base btn token alongside every btn--* modifier - #1453
Merged
Merged
Conversation
The mobile burger menu in `core/title.tpl` shipped
`class="btn--ghost btn--icon"` (modifier-only, no base `btn`).
`.btn--ghost` and `.btn--icon` are CSS-custom-property overrides;
only `.btn` itself reads those properties and applies them as
`background` / `color` / `border` / `display: inline-flex` /
`padding` / `height`. Without `.btn` in the chain the variables
get set but nothing reads them, so `<button>` falls back to the
user-agent default chrome — a glaring grey 1px-border pill that
fights the topbar's dark background on mobile.
Sister sites swept in the same PR: `core/footer.tpl`'s palette
`Esc` close button (live bug — same wrong-chrome shape) and
`partials/player-drawer.tpl`'s drawer-close `X` (reference
template — the live drawer chrome is rendered by `theme.js`'s
`renderDrawerBody()` / `renderDrawerLoading()` family, which
always shipped the correct three-class chain).
Regression guard: `web/tests/integration/ButtonClassChainTest.php`.
Parser-style sweep across `web/themes/`, `web/pages/`,
`web/includes/View/`, `web/install/`, `web/updater/`,
`web/api/handlers/`, AND `web/scripts/` + `web/themes/default/js/`
for `*.tpl` / `*.php` / `*.js` — every `class="…"` attribute body
carrying a `btn--*` modifier MUST also carry `btn`. Strips Smarty
`{* *}` comments (default + `-{* *}-` non-default delimiters),
PHP comments via `php_strip_whitespace()`, and JS `/* */` + `//`
comments so AGENTS.md-style explanatory blocks don't false-match.
Negative-lookbehind on the `class` keyword skips `data-class=` /
`aria-class=` siblings. Sanity-check assertions (>100 files
scanned, >200 attribute bodies extracted) guard against silent
zero-scan if `ROOT` or `scanRoots()` ever drift.
The JS-side scan coverage is what makes the gate's "panel chrome"
protection actually structural — `theme.js` ships seven runtime
`<button class="btn btn--ghost btn--icon[ btn--xs]">` strings via
template concatenation; without scanning them the gate would
paper over the live bug surface.
AGENTS.md additions:
- "Anti-patterns" entry with the user-reported regression details,
the sister-site rationale, and the regression-guard scope.
- Paired Conventions block ("Button class chains") with the
positive-shape contract.
- "Where to find what" row pointing at the regression guard.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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
Fixes #1448 — the mobile-viewport burger button rendered with the user-agent default grey 1px-border chrome instead of the panel's ghost-button styling.
The button in
web/themes/default/core/title.tplshippedclass="btn--ghost btn--icon"— modifier-only, no basebtn. Intheme.css:.btnis the load-bearing rule: it declares the--btn-bg/--btn-color/--btn-borderdefaults AND applies them asbackground/color/border/display: inline-flex/padding/height..btn--ghost/.btn--primary/.btn--secondary/.btn--dangerare pure custom-property overrides — they SET the variables but don't apply them..btn--sm/.btn--icon/.btn--xslayer geometry on top but still don't carry the chrome declarations.Without
.btnin the chain, the variables get set but nothing reads them —<button>falls back to UA-default chrome (visible grey pill that fights the topbar's dark background on mobile, where[data-mobile-menu]flips fromdisplay: nonetodisplay: inline-flexat<=1024px).Sister sites swept in the same PR
core/footer.tpl— the paletteEscclose button shipped the same modifier-only chain. Same live wrong-chrome shape behind the<kbd>Esc</kbd>child.partials/player-drawer.tpl— drawer-closeXbutton. Reference template, not live render path — the live drawer chrome is rendered byweb/themes/default/js/theme.js'srenderDrawerBody()/renderDrawerLoading()(which always shipped the correct three-class chain). Documentation sync, not a behaviour fix.Regression guard
New test
web/tests/integration/ButtonClassChainTest.php— single-method parser-style sweep across:web/themes/(every Smarty template + the install wizard chrome)web/themes/default/js/(the runtime HTML emitter)web/scripts/(panel-side JS)web/pages/(page handlers)web/includes/View/(view DTOs)web/install/,web/updater/,web/api/handlers/(preventive coverage for any future inline-HTML emission)For
*.tpl,*.php, AND*.js. Everyclass="…"/class='…'attribute body carrying abtn--*modifier MUST also carry the basebtntoken. The JS-side coverage is what makes the gate's "panel chrome" protection structural —theme.jsships seven runtime<button class="btn btn--ghost btn--icon[ btn--xs]">strings via template concatenation; without scanning them the gate would paper over the live bug surface.Implementation details:
{* *}comments (default +-{* *}-non-default delimiters used bypage_login.tpl/page_blockit.tpl/page_kickit.tpl/page_admin_servers_rcon.tpl), PHP comments viaphp_strip_whitespace(), and JS/* */+//comments so AGENTS.md-style explanatory blocks don't false-match.(?<![-_\w])on theclasskeyword skipsdata-class=/aria-class=siblings.>100 files scanned,>200 attribute bodies extracted) guard against silent zero-scan ifROOT/scanRoots()ever drift.{) are skipped; false negatives bounded to chains with NO base in any branch (none in the codebase today).I deliberately introduced a regression in
theme.jsduring development to verify the gate fires; it caught the bug at the right line with the right message.AGENTS.md changes
Adversarial review trail
Spawned an adversarial reviewer subagent on the initial diff. It surfaced:
theme.jsruntime-emitted button strings were outside the scan scope (MAJOR — fixed by extendingscanRoots+ adding JS comment stripping).Test plan
./sbpp.sh phpstanpasses (level 5, baseline unchanged)./sbpp.sh ts-checkpasses./sbpp.sh composer api-contractis in sync (no diff)./sbpp.sh test— full suite passes (891 tests, 3338 assertions, including the 3-assertion regression guard)theme.js'srenderDrawerLoading()button, watching the gate fire, then revertingCLA: I have read the CLA Document and I hereby sign the CLA