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:
co-authored by
Claude Opus 5
parent
471d927e64
commit
247c596c3a
@@ -288,24 +288,12 @@ it('sends an expired form back to its page to refresh', function () {
|
||||
/**
|
||||
* Plan step 36: `errors::minimal`'s class lists moved into
|
||||
* resources/css/components/error-page.css, keyed on `data-md-error-*`. Its view lives outside
|
||||
* resources/views/components/, so it is not in ContainmentStylesheetsTest's dataset (whose
|
||||
* "imports what its view renders" check reads a fixed resources/views/components/<name>.blade.php
|
||||
* path); this is its own small stylesheet-shape test instead, checking the same things.
|
||||
* resources/views/components/, so it is not in ComponentStylesheetsTest's view-scoped datasets
|
||||
* (whose "imports what its view renders" and "no class list" checks read a fixed
|
||||
* resources/views/components/<name>.blade.php path) — those two stay here, reading its real view
|
||||
* path, while the stylesheet-only checks (shape, tokens, block import) run there instead, on the
|
||||
* `error-page` entry of its dataset.
|
||||
*/
|
||||
it('draws the error layout from a stylesheet shaped like every package stylesheet', function () {
|
||||
$css = ComponentStylesheet::read('error-page');
|
||||
|
||||
expect($css->css)->toStartWith('/*')
|
||||
->and($css->statements()[0] ?? null)->toBe('@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;')
|
||||
->and(array_slice($css->statements(), 1))->each->toMatch('/^@import \'\.\/[a-z-]+\.css\';$/')
|
||||
->and($css->blocks())->each->toBe('@layer material.components')
|
||||
->and($css->css)->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b|--(?:theme|spacing|alpha)\(|\btheme\(/');
|
||||
|
||||
foreach ($css->imports() as $import) {
|
||||
expect(is_file(dirname(ComponentStylesheet::path('error-page')).'/'.$import))->toBeTrue("error-page.css imports {$import}, which does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
it('imports the stylesheet of every component the error layout renders', function () {
|
||||
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__.'/../../resources/views/error-pages/errors/minimal.blade.php'), $tags);
|
||||
|
||||
@@ -322,33 +310,6 @@ it('writes no class list into the error layout but the interaction and text clas
|
||||
expect(ViewClasses::violations(File::get(__DIR__.'/../../resources/views/error-pages/errors/minimal.blade.php')))->toBe([]);
|
||||
});
|
||||
|
||||
it('takes its values from the tokens and its breakpoints in px', function () {
|
||||
$source = (string) preg_replace('~/\*.*?\*/~s', '', ComponentStylesheet::read('error-page')->css);
|
||||
|
||||
expect($source)->not->toMatch('/#[0-9a-f]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(/i')
|
||||
->not->toMatch('/\bfont:(?!\s*var\(--md-sys-typescale-)/')
|
||||
->not->toMatch('/\btransition[a-z-]*:[^;]*(?:\d+m?s\b|\bease\b|ease-in|ease-out|cubic-bezier)/');
|
||||
|
||||
preg_match_all('/@media\s*([^{]+)\{/', $source, $queries);
|
||||
|
||||
foreach ($queries[1] as $query) {
|
||||
preg_match_all('/\(([^()]*)\)/', $query, $features);
|
||||
|
||||
foreach ($features[1] as $feature) {
|
||||
preg_match_all('/(\d*\.?\d+)(px|rem|em)\b/', $feature, $lengths, PREG_SET_ORDER);
|
||||
|
||||
foreach ($lengths as [, $number, $unit]) {
|
||||
expect($unit)->toBe('px', $query);
|
||||
expect(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$query} is not at an M3 breakpoint");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('is imported from the containment block of all.css', function () {
|
||||
expect(allCssBlock('Containment'))->toContain("@import './components/error-page.css';");
|
||||
});
|
||||
|
||||
it('styles the body only when it holds the error layout, and turns the shape on both paths', function () {
|
||||
$source = (string) preg_replace('~/\*.*?\*/~s', '', ComponentStylesheet::read('error-page')->css);
|
||||
|
||||
@@ -361,7 +322,7 @@ it('styles the body only when it holds the error layout, and turns the shape on
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 40: the fallback is `Stylesheets::bundle()` of the foundation and the error layout
|
||||
* Plan step 40: the fallback is the prebuilt bundle of the foundation and the error layout
|
||||
* (which pulls in button.css and shape.css) rather than a hand-built stylesheet, so it carries
|
||||
* their rules verbatim, with no `@import` (bundled away), no relative `url()` (the only one in the
|
||||
* bundle, `tokens/font.css`'s, leaves with the `@font-face` block it lives in) and no `@font-face`
|
||||
@@ -382,13 +343,15 @@ it('bundles the foundation and the error layout into the fallback, without an im
|
||||
->toContain('[data-md-error-shape]')
|
||||
->toContain('[data-md-button]')
|
||||
->toContain('[data-md-shape]')
|
||||
// The foundation's reset and tokens travelled in too, not only the error layout.
|
||||
->toContain('[data-md-icon]')
|
||||
// The foundation's reset, tokens and page travelled in too, not only the error layout.
|
||||
->toContain('box-sizing: border-box')
|
||||
->toContain('--md-sys-color-surface');
|
||||
->toContain('--md-sys-color-surface')
|
||||
->toMatch('/html \{\s*background-color/');
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 46: what the page inlines beside a build is `Stylesheets::bundle()` of the error layout
|
||||
* Plan step 46: what the page inlines beside a build is the prebuilt bundle of the error layout
|
||||
* alone. It reaches no `@font-face` — the package's only one is tokens/font.css's, which only the
|
||||
* foundation imports, and the application's build serves it — and no `url()` a request from the
|
||||
* page would have to resolve, so nothing needs dropping from it the way the fallback drops the face.
|
||||
@@ -398,33 +361,11 @@ it('inlines beside a build the error layout\'s bundle, which holds no font face,
|
||||
$css = (string) ErrorPage::layoutStyles();
|
||||
$plain = (string) preg_replace('~/\*.*?\*/~s', '', $css);
|
||||
|
||||
expect($css)->toBe(Stylesheets::bundle([$layout]))
|
||||
expect($css)->toBe(File::get(__DIR__.'/../../resources/dist/error-page.css'))
|
||||
->and(array_map(basename(...), Stylesheets::resolvedFiles([$layout])))
|
||||
->toEqualCanonicalizing(['error-page.css', 'button.css', 'icon.css', 'loading.css', 'tooltip.css', 'shape.css'])
|
||||
->toEqualCanonicalizing(['error-page.css', 'button.css', 'icon.css', 'loading.css', 'tooltip.css', 'shape.css', 'color.css'])
|
||||
->and($plain)->not->toContain('@font-face')
|
||||
->not->toContain('@import')
|
||||
->not->toMatch('/\burl\(/i')
|
||||
->toContain('@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;');
|
||||
});
|
||||
|
||||
/**
|
||||
* The bundle's comments name `@font-face` too (foundation/base.css's and error-page.css's headers).
|
||||
* Read as the rule, each mention cut from inside its comment to the end of the next balanced block
|
||||
* and left the comment open, so base.css's `html` rule and icon.css's rules went missing from the
|
||||
* fallback. Only the real block may leave: what remains is the bundle, rule for rule, without it.
|
||||
*/
|
||||
it('drops only the real font face from the fallback, not the rules after a comment that names one', function () {
|
||||
$files = [
|
||||
realpath(__DIR__.'/../../resources/css/foundation.css'),
|
||||
realpath(__DIR__.'/../../resources/css/components/error-page.css'),
|
||||
];
|
||||
$withoutComments = fn (string $css): string => (string) preg_replace('~/\*.*?\*/~s', '', $css);
|
||||
$bundle = Stylesheets::bundle($files);
|
||||
|
||||
expect(substr_count($withoutComments($bundle), '@font-face'))->toBe(1)
|
||||
->and(substr_count($bundle, '@font-face'))->toBeGreaterThan(1)
|
||||
->and($withoutComments((string) ErrorPage::fallbackStyles()))
|
||||
->toStartWith($withoutComments((string) preg_replace('/@font-face\s*\{[^{}]*\}/', '', $bundle)))
|
||||
->toMatch('/html \{\s*background-color: var\(--md-sys-color-surface\);/')
|
||||
->toContain('[data-md-icon] {');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user