Cut duplicated and speculative code across the package

An over-engineering audit of the whole tree, applied in five reviewed
batches. Behaviour stays the same except where UPGRADE.md says otherwise.

PHP: the showcase and error-page stylesheets are prebuilt into
resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import
(first occurrence kept, the order an application's build gives), instead
of Stylesheets::bundle() inlining imports on every request; only the
import walk DesignGuard needs stays. SchemeStylesheet::withProfiles()
replaces three copies of the scheme-plus-profiles loop, material:scheme
leaves spec and contrast checks to the node script that already made
them, and the error page's scheme cache, the hashed view namespace, the
translations path with no lang/ folder and DesignGuard's 1.x-name hints
are gone.

JS: the androidx shape port progress.js and both bin scripts each carried
lives once in resources/js/shapes.js (the generated SVGs are unchanged);
util.js holds ringIndex(), ms(), reopenGuard() and remember(), which
were written out several times; listeners are released through
AbortController; tooltip.js's hoverPopover() serves the rich tooltip too.

CSS: every rule for an element inside the navigation rail queries
`--md-navigation-rail-value` instead of repeating the seven collapsed
conditions under five media branches; badge, alert, progress, slider and
button read one non-inheriting colour-role table (components/color.css);
the dialog chrome, the submenu's popover chrome, the chip's state layer
and touch target, and the visually-hidden inputs use the shared rules
they copied; foundation/tokens.css is folded into foundation.css.

Views: Support\Field and Support\Link replace the error-key, bound-value
and link-attribute blocks copied into the fields and link components;
the timepicker period group, the menu filter and the showcase head are
partials; the datepicker's steppers and entry fields are loops; component
docblocks no longer restate SKILL.md.

Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces
four per-group files, DesignGuardTest and the layout-component tests use
datasets, browser tests share one ready() helper, CSS parsing lives in
ComponentStylesheet alone. docs/audits and the finding IDs citing it are
removed, as are pestphp/pest-plugin-laravel, the unused composer scripts
and check:font; the lint job runs in the feature job, which now installs
node packages so the prebuilt-stylesheet staleness test runs in CI.

Feature suite 1177 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 19:29:21 +02:00
co-authored by Claude Opus 5
parent 471d927e64
commit 247c596c3a
233 changed files with 16635 additions and 10579 deletions
+8 -8
View File
@@ -22,7 +22,7 @@ it('renders the tablist on the server with the first enabled tab chosen', functi
->toContain('data-md-tab-indicator')
->toContain('--tabs-indicator: share-indicator')
->toContain('x-modelable="selected"')
// The panel's id is the server's, so aria-controls never dangles (N-05).
// The panel's id is the server's, so aria-controls never dangles.
->toMatch('/role="tabpanel"\s+id="share-people-panel"\s+aria-labelledby="share-people"/')
->toContain('>3</span>');
});
@@ -69,7 +69,7 @@ it('entangles the chosen tab with Livewire and renders the one the property name
->toContain(".entangle('tab').live")
->not->toContain('x-modelable')
->toMatch('/data-md-tab="people"\s+aria-controls="[^"]+"\s+aria-selected="true"/')
// The panel the property names is the one drawn, before Alpine entangles anything (N-05).
// The panel the property names is the one drawn, before Alpine entangles anything.
->toMatch('/id="[^"]+-files-panel"[^>]*style="display: none"/')
->toMatch('/id="[^"]+-people-panel"(?![^>]*style="display: none")/');
});
@@ -108,17 +108,17 @@ it('keeps the tab bar on M3\'s offsets, indicator inset and focus ring', functio
$css = ComponentStylesheet::read('tabs');
expect($css->declarations('[data-md-tabs-bar][data-md-scrollable]'))
// 52dp before the first of a scrollable set (N-10).
// 52dp before the first of a scrollable set.
->toHaveKey('padding-inline-start', '52px')
// A primary indicator is inset 2dp each side; a secondary one spans the tab (N-18).
// A primary indicator is inset 2dp each side; a secondary one spans the tab.
->and($css->declarations('[data-md-tab-indicator]'))
->toHaveKey('inset-inline', '2px')
->and($css->declarations('[data-md-tabs-bar][data-md-variant=\'secondary\'] [data-md-tab-indicator]'))
->toHaveKey('inset-inline', '0')
// The ring is 2px outside, as everywhere else in the package (N-20).
// The ring is 2px outside, as everywhere else in the package.
->and($css->declarations('[data-md-tab]:focus-visible'))
->toBe(['color' => 'var(--md-sys-color-on-surface)', 'outline' => '3px solid var(--md-sys-color-secondary)', 'outline-offset' => '2px'])
// A link marked as the page is the chosen tab too (N-21).
// A link marked as the page is the chosen tab too.
->and($css->declarations('[data-md-tab]:is([aria-selected=\'true\'], [aria-current=\'page\'])'))
->toBe(['color' => 'var(--md-sys-color-primary)']);
});
@@ -200,7 +200,7 @@ it('draws section navigation as secondary tabs and a picker', function () {
->toMatch('/role="menuitem"[^>]*aria-current="page"[^>]*href="\/settings\/security"/')
->toMatch('/data-md-section-nav-picker.*Security.*>\s*1\s*<.*<nav/s');
// The picker shows only below medium, the tab bar only from it (N-16's compact fallback).
// The picker shows only below medium, the tab bar only from it (the compact fallback).
$css = ComponentStylesheet::read('section-nav');
expect($css->declarations('[data-md-section-nav-picker]', ['@media (width >= 600px)']))
@@ -218,7 +218,7 @@ it('marks the section whose url is the request\'s, and scrolls many sections', f
$items = collect(range(1, 7))->map(fn (int $n): array => ['title' => "S{$n}", 'url' => $n === 3 ? url('/') : "/s/{$n}"])->all();
expect((string) $this->blade('<x-section-nav :items="$items" no-wire-navigate />', ['items' => $items]))
// Five or more sections are M3's scrollable tabs, not a grid of wrapped rows (N-16).
// Five or more sections are M3's scrollable tabs, not a grid of wrapped rows.
->toMatch('/<ul data-md-tabs-bar data-md-variant="secondary"\s+data-md-scrollable\s*>/')
->not->toContain('grid-cols-')
// Its label is `<x-tabs>`'s anatomy, drawn by tabs.css, not a one-line `md-truncate`.