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
+16 -41
View File
@@ -226,34 +226,18 @@ const isLastFocalItemAtEndOfContainer = (list, space) => {
}
const firstIndexAfterFocalRangeWithSize = (list, size) => {
for (let index = list.lastFocalIndex; index < list.keylines.length; index++) {
if (list.keylines[index].size === size) {
return index
}
}
const index = list.keylines.findIndex((keyline, i) => i >= list.lastFocalIndex && keyline.size === size)
return list.keylines.length - 1
return index === -1 ? list.keylines.length - 1 : index
}
const lastIndexBeforeFocalRangeWithSize = (list, size) => {
for (let index = list.firstFocalIndex - 1; index >= 0; index--) {
if (list.keylines[index].size === size) {
return index
}
}
const index = list.keylines.findLastIndex((keyline, i) => i < list.firstFocalIndex && keyline.size === size)
return 0
return index === -1 ? 0 : index
}
const keylineBefore = (list, unadjustedOffset) => {
for (let index = list.keylines.length - 1; index >= 0; index--) {
if (list.keylines[index].unadjustedOffset < unadjustedOffset) {
return list.keylines[index]
}
}
return list.keylines[0]
}
const keylineBefore = (list, unadjustedOffset) => list.keylines.findLast((keyline) => keyline.unadjustedOffset < unadjustedOffset) ?? list.keylines[0]
const keylineAfter = (list, unadjustedOffset) => list.keylines.find((keyline) => keyline.unadjustedOffset >= unadjustedOffset) ?? list.keylines.at(-1)
@@ -817,7 +801,7 @@ document.addEventListener('alpine:init', () => {
target: null,
targetAt: 0,
settle: null,
listeners: [],
controller: null,
mutations: null,
resizes: null,
reducedMotion: null,
@@ -828,17 +812,20 @@ document.addEventListener('alpine:init', () => {
const scroller = this.$refs.scroller
state.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')
state.controller = new AbortController()
this.listen(scroller, 'scroll', () => this.scrolled(), { passive: true })
const signal = state.controller.signal
scroller.addEventListener('scroll', () => this.scrolled(), { passive: true, signal })
// A scroll the person makes themselves is theirs to end wherever it ends.
for (const type of ['pointerdown', 'wheel', 'touchstart']) {
this.listen(scroller, type, () => (state.target = null), { passive: true })
scroller.addEventListener(type, () => (state.target = null), { passive: true, signal })
}
this.listen(scroller, 'keydown', (event) => this.navigate(event))
this.listen(scroller, 'focusin', (event) => this.reveal(event))
this.listen(scroller, 'click', (event) => this.open(event))
this.listen(state.reducedMotion, 'change', () => this.schedule())
scroller.addEventListener('keydown', (event) => this.navigate(event), { signal })
scroller.addEventListener('focusin', (event) => this.reveal(event), { signal })
scroller.addEventListener('click', (event) => this.open(event), { signal })
state.reducedMotion.addEventListener('change', () => this.schedule(), { signal })
state.resizes = new ResizeObserver(() => this.refresh())
state.resizes.observe(scroller)
@@ -859,11 +846,6 @@ document.addEventListener('alpine:init', () => {
return element === this.$root || state.items.some((item) => item.element === element || item.surface === element)
},
listen(target, type, handler, options) {
target.addEventListener(type, handler, options)
state.listeners.push(() => target.removeEventListener(type, handler, options))
},
/** Measures the container and items, and builds the strategy for this width. */
refresh() {
const root = this.$root
@@ -1098,13 +1080,6 @@ document.addEventListener('alpine:init', () => {
})
},
/** The item nearest the current scroll position. */
current() {
const scroll = this.scrollOffset()
return state.snaps.reduce((best, snap, index) => (Math.abs(snap - scroll) < Math.abs(state.snaps[best] - scroll) ? index : best), 0)
},
/** Where the carousel is headed: an unfinished scroll's target, or where it is. */
heading() {
const moving = state.target !== null && performance.now() - state.targetAt < TARGET_MS
@@ -1235,7 +1210,7 @@ document.addEventListener('alpine:init', () => {
destroy() {
cancelAnimationFrame(state.frame)
clearTimeout(state.settle)
state.listeners.forEach((remove) => remove())
state.controller?.abort()
state.resizes?.disconnect()
state.mutations?.disconnect()
},