Connected segments and split halves wrote their full corners as
--md-sys-shape-corner-full, 9999px, beside 8px inner corners on the same
element. A box whose radii add up to more than its side has every radius
scaled by one factor, so the inner corners shrank to a hundredth of a pixel
and drew square. Full is now half the size's height (--group-full), which no
side can overflow. A browser test fails on any radius in a group or split
button larger than half its height.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
<x-app-shell> passed no badge label to its rail and bar items, so a badge
like 0/3 reached a screen reader bare; a destination's badgeLabel now does.
The head script indexed the profiles with a null scheme when none are
configured, which PHP 8.5 reports as a deprecated null array offset on every
page with theme.meta on. It reads the default scheme without the lookup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
ARIA defines aria-pressed for buttons, not links, so a selected <x-button
link> keeps the selected look without announcing itself as a toggle; the
caller marks the page with aria-current.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
A status label came tonal (the colour's container) or outlined; a label that
has to stand out in the colour itself fell back to the filled count, which is
16px and hidden from screen readers. solid draws the label shape in the
colour's filled pair and is spoken like the others.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
A drawer that is a pane from xl ignored Escape, whatever close-on-escape
said: the page beside a pane stays in use, so that is the right default. An
application whose pane is a transient detail (ReStride's activity and
workout panes) can now pass pane-close-on-escape and have Escape close it at
every width.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
The phone picker of <x-section-nav> drew the current section as a checked
menuitemcheckbox in tertiary-container, which a screen reader announces as a
choice rather than where you are, and it dropped the sections' badges, so an
open support count showed on the tabs but not on a phone. <x-menu-item> gains
current (aria-current="page", the selected shape in secondary-container,
the navigation indicator's colour) and badge (a dot or a count at the end of
the row), and the picker uses both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
materialDatepicker set its first day, format, min, max and year range in
init() without declaring them, and materialAppBar did the same with its
resize observer and scroll handler. Alpine writes an undeclared property to
the outermost x-data scope, so two pickers inside one page scope (ReStride's
plan setup wraps its form in x-data) shared the last one's min and first day,
and a second app bar would have taken the first one's observer. Both now
declare them; a browser test puts two pickers in an outer scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
<x-menu>, <x-fab-menu> and <x-rich-tooltip> gave their popover an id
that is new with every render, and Livewire's morph matches an element
without a wire:key by its id. So every render of the component around
them swapped the popover for a closed copy: an open menu closed, its
listeners stayed behind on the old element (Escape or a press outside
then left aria-expanded="true" on the trigger and focus unreturned), a
keep-open item's wire:click closed its menu when the response came, and
a rich tooltip went on showing the detached bubble, so it never opened
again. <x-carousel>'s row had the same kind of id: after a render it
scrolled without its listeners, and its items stopped re-masking.
The popover, the bubble and the row now carry a wire:key, so the morph
patches them in place and changes the id and anchor name together with
the trigger's, as it already did for everything else. The key goes
through an attribute bag: Livewire compiles a wire:key written in a
template into the key of the loop iteration around it, which would have
given every child component after the menu in a row the same key.
A morph also removes the menu button's ARIA attributes, which only
script writes. menu.js now writes them again after every morph, so the
button of a menu that stays open, and a FAB menu's close look, still
say it is open, and aria-controls names the popover's new id.
A second press on an open menu's button opened it again, render or not:
the popover closes on the press, and the guard against the click that
follows was timed from the toggle event, which is queued and arrives
after that click. It is timed from beforetoggle now.
Browser tests with Livewire probes in Chromium, Firefox and WebKit, and
a render test for the keys and the keys of the child components after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
<x-group hint-class> adds classes to the hint, as the text fields'
hint-class does, and a validation message still replaces the hint.
<x-menu-item icon-class> adds classes to the leading icon, for an icon
whose colour means something of its own, such as a sport's glyph.
A colour class there has to win over the component's own colour, and
which of two colour utilities wins depends on the order Tailwind emits
them (text-error comes before text-on-surface-variant). So when either
prop is given, the component's own colour is written with a :where()
variant that carries no specificity, as the fields' hint colour sits
in the components layer. A disabled item's icon stays disabled. Without
the props the markup is unchanged. The skill now also lists the fields'
hint-class, which it had left out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
<x-menu> named its CSS anchor on the <span> around the trigger slot.
A trigger taken out of the flow, such as <x-button fab> fixed to the
bottom corner of a phone's window, left that span behind as an empty
box where the page put it, and the menu opened there. menu.js now moves
the name onto the menu button, beside any name the button already
carries for its tooltip, and moves it again whenever a Livewire morph
puts the server's attributes and a fresh name back. The wrapper keeps
the name until Alpine starts, or when there is no menu button.
A menu that fits neither below nor above its start edge now also tries
the opposite side and end together, so a default-position menu on a
FAB in the bottom-right corner opens above it, end-aligned. Before, it
fell back to its base position and overflowed the window.
This also applies to <x-fab-menu>, <x-account-menu>, <x-split-button>
and the <x-section-nav> picker, which share menu.js.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
An application that lets each person choose when their week starts and
how dates are written needs the picker to follow that choice rather
than the locale. <x-datepicker> takes week-start, 0 (Sunday) to 6
(Saturday), and format, dd, MM and yyyy each once around one delimiter
(dd.MM.yyyy, dd/MM/yyyy, MM/dd/yyyy, yyyy-MM-dd). They replace what
Intl derives for firstDayOfWeek() and inputFormat(), so the field, the
server-rendered value, the typed-date reader (year first too), the
calendar's columns and weekday header, Home and End, the dialog's text
fields and ranges all follow them. Month and weekday names stay the
locale's, and wire:model still stores Y-m-d. Values that are neither
are ignored, and without the props nothing changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
<x-section-nav> and <x-app-shell> mark an item current when its URL is
the request's. While a Livewire component on the page re-renders, the
request is Livewire's update endpoint, so no section or destination
stayed lit after the morph. Both now compare with the page's URL from
Livewire::originalUrl() during a Livewire update request; a normal
request compares exactly as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
An application with its own artwork for an empty collection puts it in
the `illustration` slot, which is drawn in place of the shape and icon.
The slot's attributes go on the element around it, so its class sets the
colour an SVG's currentColor takes. A slot holding only whitespace or
comments counts as empty (hasActualContent), and without the slot the
empty state renders as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
An application keeps a disclosure's state in a Livewire property
(`<x-collapse wire:model="fineTuning">`) or an Alpine one. Either binding
now works both ways: toggling the details writes the property, and the
property changing, in an action or in Alpine, opens or closes it.
`wire:model` is entangled as the dialog and sheets entangle theirs, and
taken off the element. The server renders `open` from the property, so
the first paint matches it. `x-model` binds through `x-modelable`, with
`open` as the first paint until Alpine starts. The state is named
`collapseOpen` so it never hides an `open` that a dialog in the slot reads
from a scope around it.
Without a binding the element has no Alpine and renders as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
The slot was cast to a string and escaped, so an icon beside a badge's
word showed as literal markup. It renders as HTML now; `value` stays
escaped, and a slot holding only whitespace or comments is still a dot
(hasActualContent rather than isNotEmpty).
`color="neutral"` draws neutral ink on every variant: a dot or count in
on-surface-variant with surface text (the ink the outline badge already
writes in), a tonal label in surface-container-high with
on-surface-variant text, and an outline in outline-variant.
`color="plain"` emits no background, text or border colour in any
variant, keeping shape, size and type, so an application's own colour
classes paint it without racing the badge's.
Every existing colour renders the same classes as before, and an unknown
colour still falls back to error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
A sticky toast stays until it is dismissed or its action pressed, for a
question that must be answered ("A new version is ready" with Reload).
It is kept aside rather than queued, so it never holds up ordinary
toasts: one that arrives while it shows takes its place, and the sticky
toast comes back once the queue is empty. One is kept at a time; a newer
sticky toast replaces it. Every other toast keeps today's queue.
An action's `event` names a window event dispatched when it is pressed,
beside `handler`, for toasts whose detail cannot carry a function. The
snackbar now closes before either runs, so a toast they show is not the
one dismissed.
`data-toast` and `data-toast-action` give applications stable hooks for
their tests.
The queue now forgets a cleared timer in next(): a toast dismissed by a
click that neither hovered nor focused it first, as a screen reader
activates a button, left its timer running and cut the next toast short.
The showcase's snackbar buttons had no Alpine scope and did nothing; they
are wrapped in one, with a sticky example.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
Components read the device's safe-area insets straight from env(), which a
browser test cannot fake and an application drawing its own status strip
cannot extend. Every inset the package reads, in its CSS and its views, is
now var(--material-safe-top|bottom|left|right, env(safe-area-inset-…)):
unchanged while the variables are unset.
The app shell's --material-bottom-bar also adds
var(--material-bottom-extra, 0px), so an application that docks something on
the phone's navigation bar (an offline banner) sets its height once and the
snackbar, a fab button and the page's bottom padding clear it.
A guard test fails on any env(safe-area-inset-*) outside such a variable, and
AppShellTest's assertion on the bar height's class follows the new value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
An app installed as a PWA, and a mobile browser, colour their bar from
<meta name="theme-color">, which the package left alone: the bar kept the
server's one colour whatever theme the visitor chose.
With the new `theme.meta` option (off by default), <x-theme-script> sets the
content of every theme-color meta without a `media` attribute to the
resolved theme's surface, from the scheme data and for the profile in
<html data-scheme>, adding one to <head> when the page has none. A
MutationObserver on <html> follows every later change of data-theme or
data-scheme: $store.theme's set() and toggle(), an OS change while
`system`, a profile preview, an application's own script. wire:navigate's
head merge puts the next page's server-rendered meta in place, so it is
painted again as the page is swapped in and on livewire:navigated. Turned
off, the script is byte-for-byte what it was.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
The scheme script always generated with material-color-utilities' 2025 spec.
An application whose palette was generated with M3's original 2021 colour
(ReStride) could not reproduce it. The script now takes `spec` ('2025' by
default, or '2021') and refuses anything else, and the command takes
`--spec`, validated before Node runs.
Colour profiles may set their own `spec`, `success`, `warning` and `info`;
without them the command's options apply. The stylesheet header writes out
every option that differs from its default, so the recorded command
regenerates the file, and a profile whose spec differs from the header's
names it. Default output is byte-for-byte unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
The check sat beside the name and truncated it at four columns; it now
sits in the tile's corner under the swatch dots. Attributes other than
the binding (data-test, class) reach the fieldset, and the dots' ring
follows the selected tile's colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
An application lists named schemes in livewire-material.profiles, and
material:scheme without a seed generates them all into one stylesheet:
the default profile as the plain blocks, every profile under
<html data-scheme>, with descendant selectors so a nested data-theme
panel keeps the page's profile. The JSON keeps the 1.0 top level.
Scheme::resolveProfileUsing() lets the application name the active
profile; it is asked on every use and falls back to the default for an
unknown name or a resolver that throws. The head script writes it before
the first paint and keeps it through wire:navigate, and the mail theme
and the error pages' fallback draw it. <x-scheme-picker> chooses one and
previews it through $store.theme.previewScheme(); the showcase previews
the Workbench's profiles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Fields read their errors only under the wire:model name, so a Fortify
login form (name="email", no wire:model) never showed "These
credentials do not match". Without wire:model they now read the name,
turning brackets into dots.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
A search app bar looks through every section, example and component,
opens the section, or the example at its anchor on its page, and is
reached from anywhere with / or Ctrl+K. Section pages now carry their
own heading.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
CI's WebKit twice left the carousel on its first item after Next. A
scroll started by the buttons or keys that comes to rest anywhere but
its item is now sent there once more; a scroll the person starts
themselves cancels that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
The showcase is now an overview and one page per section behind a
grouped navigation rail, moved between with wire:navigate, instead of
one long page under a scrolling top bar. The switch exposed a WebKit
bug in the menu: inside a focusable region, WebKit moves focus out of
the closing popover before its toggle event, so Escape no longer
returned focus to the trigger. The menu now reads it on beforetoggle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Shows the plain tooltip on its own, checks that every component appears
in the showcase, and records the namespace change and the fresh
application check in the plan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Package views now write <x-livewire-material::button>, so a configured
prefix (which Blade spells <x-m::button>) and an application component
of the same name can no longer break or shadow them. The showcase
rewrites its examples to the configured prefix. Adds the README, and
waits for the adaptive rail to hear a resize before the navigation
tests press its menu button.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Error pages for 401 to 503 in an error-view root appended to view.paths,
with an inline fallback when the Vite build is missing, and a mail theme
rendered from the application's scheme JSON, with opt-in header and
message components. Completes Phase 9.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
M3 Expressive's flexible navigation bar, the collapsed, expanded and
modal navigation rail with its state applied before the first paint,
and an adaptive app shell composing them. The head script now restores
the theme and rail attributes that wire:navigate strips from <html>.
Completes Phase 8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Docked, modal and modal input date pickers for single dates and ranges,
with locale month names, week start and typed format from Intl, min and
max, and the APG grid keyboard. Completes Phase 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
The dial and input time picker in a modal dialog, with the hour cycle
from the locale, steps, a min and max that may span midnight, and a
landscape layout, ported from Compose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
A data table styled from descendant selectors, a Livewire sort header
with aria-sort, and Laravel's and Livewire's full and simple paginators
drawn in M3, put in front of their own views.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
M3 Expressive top app bars (small, centered, medium and large flexible,
search) that collapse with CSS sticky, docked and floating toolbars,
primary and secondary tabs with a view-transition indicator, section
navigation, the account menu and theme toggles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
M3's assist, filter, input and suggestion chips with chip sets; choices
as filter chips or a searchable combobox whose list is an anchored
popover; and the search bar that opens into a docked or full-screen
search view.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Outlined and filled text fields (input, password, auto-growing textarea,
native select with the customizable select menu, file), checkbox with an
indeterminate state, radio buttons, the M3 switch and form, ported from
ReStride and extended. The first half of Phase 6.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Multi-browse, hero, uncontained and full-screen layouts on a native
scroll-snap row, with Compose's keyline maths masking each item as it
scrolls. Completes Phase 5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
<x-card> (filled, elevated, outlined), <x-list> and <x-list-item>
(plain or M3 Expressive's segmented list), <x-divider>, <x-collapse> on
<details>, <x-modal> on the native <dialog>, <x-drawer> as a side sheet or
list-detail pane, and <x-bottom-sheet> with drag to dismiss. Dialogs and
sheets bind to a Livewire flag or id and write back false or null on close,
or use the surrounding Alpine scope. Rows open from anywhere on them through
data-list-row and data-list-open. DesignGuard now also reports Blade
directives written inside a component tag, where they do not compile.
Browser test helpers wait for a complete document with Alpine and Livewire
running: in Firefox, networkidle alone could return before a repeated visit
had loaded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
<x-progress>: linear or circular, flat or wavy, determinate or
indeterminate, 4px or thick, in any colour role, following a server value
through Livewire morphs or an Alpine expression in the browser. The first
frame is server-rendered SVG; Compose Material 3's drawing and keyframes
are ported to an Alpine component that animates only while something
moves and the indicator is on screen.
The previous commit already imported progress.js and included its
showcase section without the files; this adds them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
<x-toast> hosts the snackbar queue for the Toasts concern and
window.materialToast(), one at a time, paused on hover and focus, with
an optional action; it listens from the moment its script loads, so a
toast dispatched before Alpine starts is shown rather than lost.
<x-badge> is M3's dot and count, plus a tonal or outlined status label;
<x-rich-tooltip> is transient or persistent; <x-alert>, <x-stat> and
<x-empty-state> are built from M3's parts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
<x-button> (label buttons, icon buttons and toggles in five sizes, with
filled, tonal, outlined, elevated and text variants in any colour role),
<x-tooltip>, <x-menu> with items, groups and separators, <x-button-group>,
<x-group> as a connected button group, <x-split-button>, <x-fab>,
<x-fab-menu> and <x-loading>. Sizes, colours and shapes come from
androidx Compose Material 3's tokens; the loading indicator ports its
Morph into SVG + SMIL.
Menus follow WAI-ARIA's menu button pattern on popovers placed by CSS
anchor positioning. Browser tests run in Chromium, Firefox and WebKit.
The showcase fetches the icon names on demand: inlined, they tripped
Pest's test server into HTTP 431s under Firefox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
Colour, shape, type, elevation and motion as tokens and Tailwind
utilities; `php artisan material:scheme`, which generates an app's colour
roles with Google's material-color-utilities (spec 2025); the theme head
script with light, dark and system and its Alpine store; Google Sans Flex;
every Material Symbol (4,135, outlined and filled) drawn by <x-icon>
without blade-icons; all 35 M3 Expressive shapes, ported from androidx, as
<x-shape>; the x-figure directive; the Toasts concern; DesignGuard for
applications' tests; and a showcase with every token, both themes side by
side and an icon search.
Colour utilities are `@theme inline`, so a section with its own
data-theme repaints; without it they resolve once on :root.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
The skeleton for nonameweb/livewire-material: service provider and config
(unprefixed components, blade-icons' own <x-icon> switched off, an opt-in
showcase at /material), the Testbench Workbench with its Vite build, the
CSS and JS entry points applications import, the Boost guideline and skill
with a drift test, and Gitea CI running Pint, feature tests on PHP 8.4 and
8.5, and browser tests in Chrome, Firefox and Safari.
The plan moved here from SealShare: docs/plans/livewire-material.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy