Files
livewire-material/src/Support/ErrorPage.php
T
Andreas Reinhold / reiniandClaude Opus 5 247c596c3a 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>
2026-09-17 19:29:21 +02:00

111 lines
5.1 KiB
PHP

<?php
namespace NoNameWeb\LivewireMaterial\Support;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Foundation\Vite;
use Illuminate\Support\HtmlString;
use Throwable;
/**
* What the error layout needs from PHP, written so that nothing in it can fail the page: an
* error page is shown precisely when something else already went wrong.
*
* The page carries its own component rules whatever the application imports. An application's
* CSS entry imports the stylesheets of the components its own views render, and no view of its
* renders this layout — the framework does, from the package's error-view root — so nothing ever
* told an application to import `components/error-page.css`: `DesignGuard::missingStylesheets()`
* reads the application's views, not the package's, and `unusedStylesheets()` would call such an
* import unused. So an application imports nothing for its error pages: with a build the layout
* takes the foundation, tokens, scheme and font from the application's Vite tags (`assets()`) and
* inlines its own rules beside them (`layoutStyles()`); without one it inlines everything
* (`fallbackStyles()`).
*/
class ErrorPage
{
/**
* The application's Vite tags, or null when they cannot be made. They bring the foundation,
* the tokens, the scheme and the font; the layout's own rules come from `layoutStyles()`
* beside them, since the application's entry need not import them.
*
* A 500 during a deploy is the moment the manifest is missing, half-written or names files
* that are not there yet — a missing manifest throws a ViteException, a truncated one a
* PHP warning turned exception — so every failure means "no build", never a second error.
*/
public static function assets(): ?Htmlable
{
try {
$tags = app(Vite::class)(config('livewire-material.showcase.vite', []));
} catch (Throwable) {
return null;
}
return $tags instanceof Htmlable ? $tags : new HtmlString((string) $tags);
}
/**
* Where the Back button goes: the page the visitor came from, when there was one other than
* this page and the home page Home already offers. Null when the session behind it cannot
* be read.
*/
public static function backUrl(): ?string
{
try {
$previous = url()->previous();
$current = url()->current();
} catch (Throwable) {
return null;
}
$isHome = rtrim($previous, '/') === rtrim(url('/'), '/');
return $previous === $current || $isHome ? null : $previous;
}
/**
* The error layout's own rules for a page with its build: `components/error-page.css` and the
* stylesheets it imports, prebuilt into resources/dist/error-page.css (`npm run
* build:stylesheets`) and inlined beside the application's Vite tags, which bring everything
* else.
*
* Nothing is dropped from it, because nothing in it needs a build: the package's only
* `@font-face` is `tokens/font.css`'s, which only the foundation reaches and the application's
* build serves, and no stylesheet the layout imports writes a `url()` a request would have to
* resolve (tests/Feature/ErrorPagesTest.php checks both). An application whose entry also
* imports this layout (through `all.css`, say) gets the same rules twice, in the same layers and
* with the same declarations; the second copy decides nothing the first had not, so that is
* harmless.
*/
public static function layoutStyles(): HtmlString
{
return new HtmlString(self::dist('error-page.css'));
}
/**
* A stylesheet for a page without its build: the foundation and the error layout's own rules,
* prebuilt into resources/dist/error-page-fallback.css — the same inlining a Vite build does,
* so the fallback can never drift from `resources/css/components/error-page.css` the way a
* hand-written copy did, and without the `@font-face` (there is no build here to serve the font
* file; the brand typeface's stack in tokens/type.css falls back to the system fonts) — with
* the application's colours appended in `material-scheme.css`'s own shape
* (`SchemeStylesheet::withProfiles()`): the standard level, the medium and high contrast levels
* under `[data-contrast]`, and a block per colour profile under `[data-scheme]`, built from
* `Scheme::forStylesheet()` on every call. `<x-theme-script>` has already written
* `data-theme`, `data-contrast` and `data-scheme` onto `<html>` by the time this tag is parsed
* (it renders first), so the browser resolves the right block on its own — nothing here decides
* an active profile in PHP.
*/
public static function fallbackStyles(): HtmlString
{
return new HtmlString(self::dist('error-page-fallback.css').SchemeStylesheet::withProfiles(...Scheme::forStylesheet()));
}
/**
* A prebuilt stylesheet under resources/dist/.
*/
protected static function dist(string $file): string
{
return (string) file_get_contents(dirname(__DIR__, 2)."/resources/dist/{$file}");
}
}