Commit Graph
84 Commits
Author SHA1 Message Date
Andreas Reinhold / reiniandClaude Opus 5 d64fb91303 Announce a selectable list as M3's list box of options
Plan step 12, containment.md C-03. `selected` was colour alone on a
`listitem`, which cannot carry a selected state at all. `<x-list
selectable>` (or `selection="single|multi"`) now makes the container a
`role="listbox"` and each item an `option` with `aria-selected`; a plain
list keeps `role="list"` and marks a selected item `aria-current`. A
selected option draws a trailing check as M3's second cue, which
`icon-right` replaces. The item reads its list's mode with `@aware`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:57:00 +02:00
Andreas Reinhold / reiniandClaude Opus 5 7f67856916 Stop a disabled list item answering the keyboard
Plan step 12, containment.md C-02. `pointer-events-none` blocked the
pointer but not Tab and Enter: the item's `<a href>` stayed in the tab
order and navigated, and nothing announced it disabled. A disabled item
now renders its title as text, is no longer a `data-list-row`, and
carries `aria-disabled="true"`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:54:25 +02:00
Andreas Reinhold / reiniandClaude Opus 5 f02fa1a78d Give the bottom sheet's drag handle a 48dp target
Plan step 12, containment.md C-01. The handle button was 32x4px: the
padding that makes M3's 48dp hit target sat on the wrapper, which is not
the control. `touch-target` on the button and 22px above and below it —
SheetDefaults.kt's DragHandleVerticalPadding — make the pressed area
48x48 while the handle is still drawn 32x4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:53:46 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 0adfb6fff4 Show M3's window size classes on a Layout page of the showcase
The page names the class this window is in, lights the five cards through the
compiled variants themselves, and tabulates what each class changes: navigation,
panes, dialogs and sheets, margins. Plan step 28, in part.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:44:53 +02:00
Andreas Reinhold / reini 18023166bb Merge branch 'worktree-agent-a9537eabbf38e0608'
# Conflicts:
#	resources/views/components/scheme-picker.blade.php
#	resources/views/components/theme-script.blade.php
#	resources/views/showcase/sections/colour.blade.php
#	tests/Feature/Components/ThemeScriptTest.php
2026-09-14 05:43:37 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 4ba28b8579 Skip the swatch levels when the picker draws nothing
Without profiles the component renders nothing, so it should not read the scheme
file once per contrast level first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:41:14 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 6aa8a48084 Hold the mail theme to the standard contrast level
A mail client resolves no attribute, so the JSON's top level — the standard roles —
is what it wears, whatever levels the file carries beside them.
Plan: docs/plans/material-3-alignment.md, step 7 (core C2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:40:35 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 7a17e58ee6 Write the contrast levels and the harmonisation flag down
The skill's colour and theme sections and the README's scheme and theme.* bullets
now carry what the command writes: the three levels, --harmonize, the state colours
built as error is, data-contrast, theme.contrast, motion.scheme, and the store's
contrast, resolvedContrast and setContrast(). Plan: docs/plans/material-3-alignment.md,
steps 6 and 7 (core C1, C2, C15).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:39:46 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 57d9e0aa6f Offer the contrast level where the theme is chosen
<x-theme-toggle mode="contrast"> is the picker's row for M3's three levels, marking
the one in force so the operating system's setting shows while the choice is system.
The scheme picker's dots follow the level on screen instead of always promising the
standard colours, and the showcase's colour section puts the three levels side by
side — data-scheme beside data-contrast, the way a profile's level blocks key.
Plan: docs/plans/material-3-alignment.md, step 7 (core C2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:38:31 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 216a15307a Square the full-screen dialog with the M3 corner token, and guard every package view
The one rounded-none left behind by the scale clearing becomes rounded-corner-none,
so the design guard's test of the package's own views needs no filter any more:
the breakpoint migration landed too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:38:02 +02:00
Andreas Reinhold / reini f4f0ecb3b8 Merge branch 'worktree-agent-aadcbb716875e9282' 2026-09-14 05:37:13 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 ceaad7cfe9 Rewrap the drawer's pane paragraph
A comment-only follow-up to the pane's move to `expanded`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:36:42 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 9fb3126038 Scan for Tailwind breakpoints, and drive the shell by class in the browser
Plan steps 15-17's testing. `tests/Feature/BreakpointsTest.php` reads every
file under resources/views, resources/css and resources/js — showcase example
heredocs included — and fails on any Tailwind breakpoint prefix and on any
media query or `matchMedia` string at 40, 48, 64 or 80rem (or 640/768/1024/
1280px, or the 39.99rem that stood for "below 640"). Both scans were checked
against a deliberately reintroduced `sm:w-auto` and an 80rem query.

Every render test that asserted a `sm:`/`lg:`/`xl:` class now asserts the M3
class, the drawer's pane query is 52.5rem, and the theme script keeps
`data-rail-auto` through `wire:navigate`.

NavigationTest drives the shell at 599/600/839/840/1199/1200/1600 with the
expectation each class carries: the bar on a compact window, the 96px rail from
the medium class's first pixel, a standard rail collapsed through expanded whose
menu button widens it in place with no scrim, and the rail expanded to begin
with from large and at extra-large. (Not run here — the Browser suite is out of
scope for this stream.)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:35:43 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 afc8e199ae Teach the M3 window size classes in the skill and the README
Plan steps 15-17. Every breakpoint the skill named is now a window size class:
the wrapper idiom (`max-medium:hidden`), `<x-app-shell>`'s section gets a table
of the four classes with their navigation and margin, `<x-drawer pane>` is
"from expanded" at 360dp, and `<x-section-nav>`, `<x-search>`, `<x-datepicker>`,
`<x-modal fullscreen>`, `<x-button fab|responsive>`, `<x-navigation-bar>`,
`<x-navigation-rail mode="adaptive">`, `$store.rail` and the FAB and fab-menu
margins follow. A new Tokens bullet states the rule once: `medium:` 600,
`expanded:` 840, `large:` 1200, `extra-large:` 1600, Tailwind's screens cleared,
scripts through resources/js/breakpoints.js, a container query (`@md:`) a
different thing.

The README gains the same warning where it tells an application how to import
the package's CSS, since a leftover `sm:` from another project now compiles to
nothing rather than to 640px.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:35:43 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 7084aac788 Resolve the contrast level and the motion scheme before the first paint
The head script reads livewire-material.theme.contrast (standard, medium, high or
system, kept under its own storage key), asks the operating system while the choice
is system and follows it, and writes <html data-contrast> — nothing for standard,
which is what the stylesheet's plain blocks already are. It writes
<html data-motion="standard"> for motion.scheme, and both attributes, with the new
choice and key ones, survive wire:navigate. $store.theme gains contrast,
resolvedContrast and setContrast(); the theme-color meta takes its surface from the
resolved level. Plan: docs/plans/material-3-alignment.md, steps 5 and 7 (core C2, C9).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:34:29 +02:00
Andreas Reinhold / reini 56e28ef54a Merge branch 'worktree-agent-a221f2b34317526ab' 2026-09-14 05:33:13 +02:00
Andreas Reinhold / reiniandClaude Opus 5 e2d249a409 Say what the design guard now reports and what to write instead
The "Testing the design" section of the README and of the Boost skill list the
cleared breakpoints and scales with their M3 replacement, the arbitrary-colour
check, and the two opt-in checks for the absolutes and for opacity on ink.
Plan: docs/plans/material-3-alignment.md, step 9 (core C4a, C13).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:32:31 +02:00
Andreas Reinhold / reiniandClaude Opus 5 faf5132c7a Name the M3 replacement for every value the theme cleared
DesignGuard gains checks for Tailwind's breakpoint prefixes, for the radius,
shadow, type-size, weight, leading, tracking, easing and duration scales
tokens/theme.css clears, and for a colour written as a value; each violation
carries path:line, the token and the M3 utility to use instead. forbidAbsolutes()
and forbidOpacityInk() are opt-in like forbidColours(), because the package's own
disabled styles are drawn with M3's 38 %/12 % opacities. A fixture and a test per
pattern and per hint; the package's own views are asserted against every check
but the breakpoint one, which plan step 15 migrates.
Plan: docs/plans/material-3-alignment.md, step 9 (core C4a, C13).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:32:25 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 4263982369 Give success, warning and info the 2025 spec and three contrast levels
The three state colours are DynamicColors on their own tonal palette now, built
exactly as Google builds error/on-error/error-container/on-error-container in both
specs, so the scheme's contrast level, dark tones, spec version and platform reach
them as they reach every other role; --harmonize (and a profile's harmonize) pulls
each source towards the seed, off by default. material:scheme also writes M3's
medium (0.5) and high (1.0) levels for both themes and every profile, keyed on
data-contrast and never on a media query, and --contrast now moves the standard
block alone. Scheme::load() takes the level; the mail theme stays on standard.
Plan: docs/plans/material-3-alignment.md, steps 6 and 7 (core C1, C2, C15).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:30:48 +02:00
Andreas Reinhold / reini 39823d71df Merge branch 'worktree-agent-afa50210f14592c5f' 2026-09-14 05:30:12 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 5b0ca26857 Sample the springs over M3's web durations, and add the Standard scheme
bin/springs.mjs is the sampler behind motion.css: damping ratio and stiffness in,
the 49-point linear() and the settle time out. Run with --settle it reproduces
the six curves the file carried byte for byte, so only the window changed.

Each spring is now sampled over the duration M3 publishes for the web (spatial
350/500/650 ms, effects 150/200/300 ms) instead of stopping at its own settle
time, so the *-duration tokens read Google's numbers. Sampling stays in real
time — point i is the spring at duration × i / 48 — so the bounce lands at the
same millisecond as before; a longer duration just holds the tail flat at 1, and
a shorter one pins the last point, at most 0.3% of the travel in one frame. Each
token's comment records damping, stiffness, settle time and sampled duration.

M3's second motion scheme is here too: [data-motion="standard"] swaps the three
spatial springs (damping 0.9, 0.2% overshoot instead of 9%) and their durations;
the effects springs sample identically in both schemes, so they are shared. The
reduced-motion block names both selectors, so it still zeroes every duration.

Plan: docs/plans/material-3-alignment.md step 5, findings core C8, C9.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:29:32 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 fd1e063d4c Give the app shell one shape per M3 window size class
Plan step 17, on N-06, N-07 and C-07. The shell now changes at 600, 840 and
1200 and nowhere else: a compact window keeps the navigation bar and the modal
rail; `medium` (600-839) gets the collapsed rail in the layout and no bar;
`expanded` (840-1199) gets a standard rail, collapsed, whose menu button
expands it in place rather than over a scrim; `large` and above start it
expanded, which is what M3 prefers once there is room.

`data-rail` alone could not say "collapsed at expanded, expanded at large",
since it carries `rail.default` for a visitor who never chose. <x-theme-script>
now also writes `data-rail-auto` while nothing is stored, the `rail-collapsed:`
variant reads it in the 840-1199 band, and `$store.rail.auto` mirrors it for
Alpine; the first press of the menu button drops it, so a remembered choice
still wins in both bands. `rail.default` and the rest of `$store.rail` are
unchanged, and the attribute rides through `wire:navigate` with the others.

`--material-margin` carries M3's window margin on the shell -- 16px compact,
24px from `medium` -- and the content region is padded with it, so the showcase
pages drop their own gutters.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:29:17 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 2feb623ec0 Take the typescale's tracking from Compose, and reset roundness
Tracking now comes from androidx's TypeScaleTokens.kt as sp/16 rem, so Display
Large is -0.0125rem, Title Medium and Body Medium 0.0125rem where material-web's
pre-Expressive numbers stood. The emphasized set gets its own
--md-sys-typescale-emphasized-<style>-tracking, which the emphasized utilities
read: M3 widens four of them and takes Display Large back to zero. Every regular
utility sets `font-variation-settings: normal`, because the property inherits and
body copy inside an emphasized heading is meant to read as itself.

bin/check-font.mjs (fontkit) prints the packaged woff2's fvar axes, so a
re-subset cannot silently drop the ROND axis the emphasized styles depend on;
`npm run check:font` runs it and tests/Feature/FontTest.php runs it through the
configured node binary, skipping when node cannot run (PHP cannot open a woff2
without Brotli).

Plan: docs/plans/material-3-alignment.md step 4, findings core C5, C6, C7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:29:09 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 17723d2a76 Ask breakpoints.js, not Tailwind's screens, for a window class
Plan step 16. `navigation.js` takes its standard-rail threshold from
`from('expanded')` (840px) instead of the 64rem it had, and `navigation.css`'s
three 64rem queries follow, so the whole expanded class gets the in-layout
collapsible rail M3 asks for rather than one that opens over a scrim (N-06);
its two 40rem queries become 37.5rem, the compact/medium boundary the bar's own
container query already used (N-07). `search.js` and `datepicker.js` swap their
39.99rem media strings for `upTo('medium')`, so the full-screen search view and
the modal date picker end at 600px, not 640px.

The time picker's landscape layout stops keying on width at all: M3 swaps it on
orientation and viewport height, so it is now landscape plus a window too short
for the upright dial (35rem, the dialog's own height), and the two dial-shrink
rules key on height alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:06:35 +02:00
Andreas Reinhold / reini 125465c9cb Merge branch 'worktree-agent-ac2cdea6121b46ea6' 2026-09-14 04:05:33 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 1134dbac0b Show both symbol cuts in the showcase icon search
The search draws its 4,135 symbols as CSS masks through the symbol route, so
the route learned `?optical=20` — the 24 cut otherwise, as on <x-icon> — and
the section got a checkbox that switches the cut and draws the grid at 20px,
where the difference in stroke weight is the point.

Plan step 10; finding C16.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:04:42 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 699827d696 Let <x-icon> pick the optical size 20 cut
M3's optical size axis redraws a symbol so its strokes look equally heavy at
every size; drawing the 24 cut at 20px thins them by about a sixth (styles.md
§ Icons). `optical` picks the cut — 24 by default, 20 for an icon drawn at 20px
or smaller — and combines with `filled`; anything else falls back to 24, so no
caller can land on a folder that does not exist. SvgFile::symbol() takes the
size as a fourth argument and resolves the `-20` folders; the catalogue is the
same in both cuts, so symbolNames() and DesignGuard's icon-name check are
unchanged. The callers that size their own icons pass it in a later step.

Plan step 10; finding C16.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:04:32 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 5fc75a32eb Test that the guideline and the design skill cannot drift, and attribute Google
BoostResourcesTest: the guideline stays under 120 lines and verbatim, the skill
carries its frontmatter and every guideline section as a chapter, names every
--md-sys-*/--md-ref-* token family declared in resources/css/tokens and every M3
breakpoint, and ends with an Attribution chapter. NOTICE records that the rules
and wording come from m3.material.io (CC BY 4.0) and the token values from
androidx Compose (Apache-2.0), since docs/ is export-ignored and the skill ships.
Plan steps 29 and 30.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:04:25 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 b3960d5eca Add the material-3-design skill, the concept reference behind the guideline
resources/boost/skills/material-3-design/SKILL.md: the tables the guideline
compresses — every colour role with its purpose, pair and utility, the surface
container hierarchy, contrast levels, elevation by component, the corner scale
and the corner of each component, the type scale with roles and the emphasized
set, the spring tokens and direction rules, the state table and targets, the
window size classes with navigation, panes and margins, spacing tokens, icon
axes, an accessibility checklist and the don'ts — each M3 name beside the
library's utility, each chapter with Google's source pages, and an Attribution
chapter. Loaded on demand by Boost. Plan step 27 (core C24, C19).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:04:17 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 16fe29839f Write Material 3's rules as an always-on Boost guideline
resources/boost/guidelines/material-3.blade.php: the rules that change what an
agent writes — colour roles and pairs, surface containers and elevation, the
corner scale by component, type roles, spatial vs effects motion, states and
48px targets, the five window size classes, accessibility, icons, and the
don'ts M3 states — one line each with the utility to use, under 120 lines
because Boost inlines it into every session. Plan step 26 (core C24).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:04:09 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 b1642aed1c Key the views on M3's window size classes, not Tailwind's
Plan step 15. Every `sm:`/`md:`/`lg:`/`xl:` in the components, the showcase
(sections, layout, shell) and the pagination and error views becomes the M3
window size class the audits' breakpoint maps prescribe: the full-screen
dialog, the FAB, the snackbar's width, `radio inline`, the section picker and
the page numbers move from 640px to `medium` (600px, M3's compact boundary,
N-07 / C-07 / IN-28); `button responsive` moves from 1024px to `expanded`
(840px); `section-nav`'s grid-to-row switch from 1280px to `large` (1200px,
N-07); and `drawer pane` from 1280px to `expanded`, where M3 shows two panes
for list-detail (C-07). The pane's default width is now 360dp, M3's fixed
supporting pane at expanded.

Showcase example grids take two columns from `medium` and three or more from
`expanded`; the showcase's own theme picker swaps at `expanded`, the nearest
class boundary to the 768px it used (no M3 rule applies to it).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:03:54 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 c22d693334 Ship Material Symbols in the 20 optical-size cut
M3 draws icons at 20 dp from the optical size 20 cut, whose strokes stay as
heavy as the 24 dp design's instead of thinning with the glyph (styles.md
§ Icons, the optical size axis). The package only shipped the 24 cut, so every
20 px icon was a scaled-down 24 (core audit C16). bin/fetch-symbols now checks
out both cuts from the same commit of google/material-design-icons and writes
the new one to resources/svg/symbols/{outlined-20,filled-20}: 4,135 symbols
each, the same names and the same normalisation as the 24 cut.

Plan step 10; finding C16.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 04:00:16 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 25ff5a8d71 Key breakpoints, scales, inks and states on M3's tokens
Tailwind's sm…2xl are cleared for M3's window size classes (medium 600, expanded
840, large 1200, extra-large 1600; resources/js/breakpoints.js for scripts), and
its radius, shadow, text-size, weight, leading, tracking and easing scales are
cleared like its palette, so only M3's utilities compile. The semantic inks are
roles rather than opacities (M3 reserves 38% for disabled). state.css declares
M3's state opacities as tokens, adds the dragged layer and a touch-target utility.
Plan: docs/plans/material-3-alignment.md, steps 1, 2, 3 and 8.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 03:54:21 +02:00
Andreas Reinhold / reiniandClaude Fable 5.1 651a513d1e Plan the Material 3 alignment, with the audits and Google's references
Every foundations, styles and components page of m3.material.io (238, from the
sitemap) extracted into docs/reference/m3, five audit reports with 142 findings in
docs/audits/m3-alignment, and the 2.0.0 plan in docs/plans/material-3-alignment.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 03:53:40 +02:00
Andreas Reinhold / reiniandClaude Opus 5 b1fc0c9cfa Keep a connected group's inner corners round
tests / feature (8.4) (push) Successful in 1m17s
tests / feature (8.5) (push) Successful in 1m21s
tests / browser (chrome, chromium) (push) Successful in 3m55s
tests / browser (firefox, firefox) (push) Successful in 5m34s
tests / lint (push) Successful in 1m5s
tests / browser (safari, webkit) (push) Successful in 5m54s
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
2026-09-13 22:46:10 +02:00
Andreas Reinhold / reiniandClaude Opus 5 a980f5b0d0 Expect the section picker to mark its page, not a checked item
The browser test still looked for the menuitemcheckbox the picker drew before
it marked the current section with aria-current.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
2026-09-13 22:41:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 a4bc046d1d Speak a shell destination's badge in words, and stop a null offset on PHP 8.5
<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
2026-09-13 22:20:37 +02:00
Andreas Reinhold / reiniandClaude Opus 5 433ddb2baa Keep aria-pressed off a selected link button
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
2026-09-13 21:36:35 +02:00
Andreas Reinhold / reiniandClaude Opus 5 e6f4357a2a Add a solid status label to the badge
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
2026-09-13 21:08:55 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ea529bf785 Let a list-detail pane close on Escape when asked
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
2026-09-13 20:31:33 +02:00
Andreas Reinhold / reiniandClaude Opus 5 869ccdf447 Mark a section picker's current page as the page, with its badge
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
2026-09-13 19:56:54 +02:00
Andreas Reinhold / reiniandClaude Opus 5 65788d0c61 Keep a date picker's and an app bar's settings on their own component
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
2026-09-13 19:17:11 +02:00
Andreas Reinhold / reiniandClaude Opus 5 8640d815c8 Keep open menus and rich tooltips open through a Livewire render
<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
2026-09-13 18:50:14 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ab7317faae Add hint-class to the group and icon-class to menu items
<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
2026-09-13 18:18:39 +02:00
Andreas Reinhold / reiniandClaude Opus 5 a83d7f62ea Hang a menu on its menu button rather than the trigger's wrapper
<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
2026-09-13 18:18:39 +02:00
Andreas Reinhold / reiniandClaude Opus 5 fb29169d44 Let the date picker take a first day of the week and a format
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
2026-09-13 18:18:39 +02:00
Andreas Reinhold / reiniandClaude Opus 5 fdb13313f7 Keep the current section lit while a Livewire component updates
<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
2026-09-13 18:18:39 +02:00
Andreas Reinhold / reiniandClaude Opus 5 d77ecf5e2a Add an illustration slot to the empty state
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
2026-09-13 18:11:00 +02:00
Andreas Reinhold / reiniandClaude Opus 5 e63c0f684c Bind the collapse's open state through wire:model and x-model
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
2026-09-13 18:10:59 +02:00
Andreas Reinhold / reiniandClaude Opus 5 973cc0b68c Render badge slots as HTML and add neutral and plain badge colours
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
2026-09-13 18:10:59 +02:00
Andreas Reinhold / reiniandClaude Opus 5 07e008d942 Add sticky toasts, action events and toast hooks
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
2026-09-13 18:10:59 +02:00
Andreas Reinhold / reiniandClaude Opus 5 53bb000425 Let an application replace the safe-area insets and dock on the bottom bar
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
2026-09-13 18:03:20 +02:00
Andreas Reinhold / reiniandClaude Opus 5 bcf0d4f4e3 Keep the theme-color meta in step with the theme
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
2026-09-13 17:59:34 +02:00
Andreas Reinhold / reiniandClaude Opus 5 3a7aa96ec7 Add a colour spec choice to material:scheme
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
2026-09-13 17:52:50 +02:00
Andreas Reinhold / reiniandClaude Opus 5 db179e6b4e Compile the showcase's views with the showcase off
tests / lint (push) Successful in 1m6s
tests / feature (8.4) (push) Successful in 1m18s
tests / feature (8.5) (push) Successful in 1m17s
tests / browser (chrome, chromium) (push) Successful in 3m27s
tests / browser (firefox, firefox) (push) Successful in 4m57s
tests / browser (safari, webkit) (push) Successful in 5m29s
php artisan view:cache compiles every view in the package's namespace,
the showcase's pages included, but the showcase:: component prefix was
only registered while the showcase was enabled. In production, where it
is off, view:cache failed with "Unable to locate a class or view for
component [showcase::example]" and an application caching its views on
start would not boot. The prefix is now always registered; the routes
still are not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 17:40:33 +02:00
Andreas Reinhold / reiniandClaude Opus 5 8afe9bac6a Keep planning notes out of the repository
tests / lint (push) Successful in 1m1s
tests / feature (8.4) (push) Successful in 1m17s
tests / feature (8.5) (push) Successful in 1m19s
tests / browser (chrome, chromium) (push) Successful in 3m30s
tests / browser (firefox, firefox) (push) Successful in 4m57s
tests / browser (safari, webkit) (push) Successful in 5m23s
docs/plans is ignored from now on; the notes stay on the machine that
wrote them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 16:25:35 +02:00
Andreas Reinhold / reiniandClaude Opus 5 d805dbc407 Lay the scheme picker out as tiles and pass its attributes to the group
tests / lint (push) Successful in 1m6s
tests / feature (8.4) (push) Successful in 1m12s
tests / feature (8.5) (push) Successful in 1m16s
tests / browser (chrome, chromium) (push) Successful in 3m17s
tests / browser (firefox, firefox) (push) Successful in 4m43s
tests / browser (safari, webkit) (push) Successful in 5m22s
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
2026-09-13 14:39:26 +02:00
Andreas Reinhold / reiniandClaude Opus 5 fb42f004b1 Add colour profiles
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
2026-09-13 14:35:49 +02:00
Andreas Reinhold / reiniandClaude Opus 5 7798352cfc Compile unprefixed components to the same view on every machine
tests / lint (push) Successful in 1m3s
tests / feature (8.4) (push) Successful in 1m14s
tests / feature (8.5) (push) Successful in 1m14s
tests / browser (chrome, chromium) (push) Successful in 3m16s
tests / browser (firefox, firefox) (push) Successful in 4m36s
tests / browser (safari, webkit) (push) Successful in 5m8s
Without a prefix, Blade named the components' view namespace after the
folder's absolute path, while compiled views are named relative to the
application. A view compiled on a laptop and served from a container
sharing storage/framework/views (or from another release directory)
printed 'eb5f…::input' as text. The components now register under the
prefix livewire-material when none is configured, which unprefixed tags
still resolve through, and the path's namespace stays registered for
views compiled before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:16:52 +02:00
Andreas Reinhold / reiniandClaude Opus 5 cb3aa798a0 Keep a full-screen dialog's subtitle on a phone
tests / lint (push) Successful in 1m20s
tests / feature (8.4) (push) Successful in 1m13s
tests / feature (8.5) (push) Successful in 1m13s
tests / browser (chrome, chromium) (push) Successful in 3m17s
tests / browser (firefox, firefox) (push) Successful in 4m37s
tests / browser (safari, webkit) (push) Successful in 5m10s
The bar names the dialog there, so only the headline hides; the subtitle
used to disappear with it, and a subtitle without a title never showed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 10:57:36 +02:00
Andreas Reinhold / reiniandClaude Opus 5 648a4cfe0f Show a plain form field's errors under its name
tests / lint (push) Successful in 1m5s
tests / feature (8.4) (push) Successful in 1m9s
tests / feature (8.5) (push) Successful in 1m9s
tests / browser (chrome, chromium) (push) Successful in 3m19s
tests / browser (safari, webkit) (push) Successful in 5m0s
tests / browser (firefox, firefox) (push) Successful in 4m34s
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
2026-09-13 10:41:37 +02:00
Andreas Reinhold / reiniandClaude Opus 5 51bf034b74 Add a search to the showcase
tests / feature (8.5) (push) Successful in 1m14s
tests / lint (push) Successful in 1m1s
tests / feature (8.4) (push) Successful in 1m9s
tests / browser (chrome, chromium) (push) Successful in 3m22s
tests / browser (firefox, firefox) (push) Successful in 4m37s
tests / browser (safari, webkit) (push) Successful in 5m4s
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
2026-09-13 09:56:00 +02:00
Andreas Reinhold / reiniandClaude Opus 5 7ebcb0565f Send a carousel scroll on when it comes to rest short of its item
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
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
2026-09-13 09:40:40 +02:00
Andreas Reinhold / reiniandClaude Opus 5 889f8a8aa1 Put the showcase in the app shell, with a page per section
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
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
2026-09-13 09:30:26 +02:00
Andreas Reinhold / reiniandClaude Opus 5 23218431bf Add a clear button to the date picker
`clearable` empties a date, or both ends of a range, closes an open
picker and hands focus back to the field, as the time picker's does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 09:30:26 +02:00
Andreas Reinhold / reiniandClaude Opus 5 eca6b8fb05 Give browser tests on CI more time and wait for Alpine in the theme test
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
CI's WebKit job failed twice on a slow runner: a carousel scroll that had
not settled within five seconds, and a press on the theme switch before
Alpine had revealed it. BROWSER_TIMEOUT raises the assertion timeout, and
the workflow sets it to fifteen seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 09:14:11 +02:00
Andreas Reinhold / reiniandClaude Opus 5 cbf874a5a6 Complete the showcase and record Phase 10
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
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
2026-09-13 09:08:35 +02:00
Andreas Reinhold / reiniandClaude Opus 5 1e580c573e Resolve the package's own components through its namespace
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
2026-09-13 09:05:24 +02:00
Andreas Reinhold / reiniandClaude Opus 5 8f174520eb Add M3 error pages and the Markdown mail theme
tests / feature (8.5) (push) Successful in 1m8s
tests / browser (safari, webkit) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / lint (push) Successful in 1m5s
tests / feature (8.4) (push) Successful in 1m11s
tests / browser (chrome, chromium) (push) Failing after 6m9s
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
2026-09-13 08:55:58 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ab692b66bb Add the navigation bar, navigation rail and app shell
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
2026-09-13 08:54:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 f4be9848e2 Add the M3 date pickers
tests / lint (push) Successful in 1m5s
tests / feature (8.4) (push) Successful in 1m10s
tests / feature (8.5) (push) Successful in 1m7s
tests / browser (firefox, firefox) (push) Failing after 8m28s
tests / browser (chrome, chromium) (push) Failing after 5m58s
tests / browser (safari, webkit) (push) Failing after 7m24s
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
2026-09-13 08:48:56 +02:00
Andreas Reinhold / reiniandClaude Opus 5 b8239dd215 Add the M3 time picker
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
2026-09-13 08:47:42 +02:00
Andreas Reinhold / reiniandClaude Opus 5 83ed671c4e Add data tables, sort headers and M3 paginators
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m10s
tests / feature (8.5) (push) Successful in 1m6s
tests / browser (chrome, chromium) (push) Successful in 3m28s
tests / browser (firefox, firefox) (push) Successful in 4m20s
tests / browser (safari, webkit) (push) Failing after 6m16s
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
2026-09-13 08:32:59 +02:00
Andreas Reinhold / reiniandClaude Opus 5 f90695cedd Add app bars, toolbars, tabs and the account and theme controls
tests / browser (chrome, chromium) (push) Successful in 3m34s
tests / browser (safari, webkit) (push) Successful in 6m2s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m6s
tests / feature (8.5) (push) Successful in 1m9s
tests / browser (firefox, firefox) (push) Successful in 4m20s
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
2026-09-13 08:26:54 +02:00
Andreas Reinhold / reiniandClaude Opus 5 bf00e4c40a Add the M3 Expressive slider
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m5s
tests / feature (8.5) (push) Successful in 1m5s
tests / browser (chrome, chromium) (push) Successful in 3m17s
tests / browser (firefox, firefox) (push) Successful in 4m1s
tests / browser (safari, webkit) (push) Failing after 5m38s
Standard, centered and range sliders on native range inputs, drawn as
Compose draws them, in five sizes with ticks, value labels and inset
icons. Completes Phase 6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 08:13:04 +02:00
Andreas Reinhold / reiniandClaude Opus 5 0fee2a0952 Add chips, choices and search
tests / lint (push) Successful in 1m4s
tests / feature (8.4) (push) Successful in 1m8s
tests / feature (8.5) (push) Successful in 1m8s
tests / browser (chrome, chromium) (push) Successful in 3m6s
tests / browser (firefox, firefox) (push) Successful in 3m45s
tests / browser (safari, webkit) (push) Successful in 5m0s
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
2026-09-13 08:07:59 +02:00
Andreas Reinhold / reiniandClaude Opus 5 7f92f1cb29 Add M3 text fields, selects and selection controls
tests / feature (8.4) (push) Successful in 1m5s
tests / feature (8.5) (push) Successful in 1m4s
tests / lint (push) Successful in 59s
tests / browser (chrome, chromium) (push) Successful in 2m54s
tests / browser (firefox, firefox) (push) Successful in 3m3s
tests / browser (safari, webkit) (push) Successful in 4m13s
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
2026-09-13 07:53:14 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ad724662ca Add the M3 Expressive carousel
tests / browser (chrome, chromium) (push) Successful in 2m24s
tests / browser (firefox, firefox) (push) Successful in 2m48s
tests / browser (safari, webkit) (push) Successful in 3m42s
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m2s
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
2026-09-13 07:40:30 +02:00
Andreas Reinhold / reiniandClaude Opus 5 fef20a9178 Add cards, lists, dialogs, sheets and the rest of M3's containment
tests / lint (push) Failing after 2m6s
tests / feature (8.4) (push) Successful in 1m3s
tests / feature (8.5) (push) Successful in 1m7s
tests / browser (safari, webkit) (push) Successful in 3m14s
tests / browser (chrome, chromium) (push) Successful in 2m9s
tests / browser (firefox, firefox) (push) Successful in 2m29s
<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
2026-09-13 07:28:05 +02:00
Andreas Reinhold / reiniandClaude Opus 5 e57063b0f4 Add M3 Expressive progress indicators
tests / browser (chrome, chromium) (push) Successful in 2m8s
tests / browser (firefox, firefox) (push) Failing after 1m56s
tests / lint (push) Successful in 57s
tests / feature (8.4) (push) Successful in 1m5s
tests / feature (8.5) (push) Successful in 1m1s
tests / browser (safari, webkit) (push) Successful in 2m52s
<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
2026-09-13 07:05:52 +02:00
Andreas Reinhold / reiniandClaude Opus 5 648ad8efaf Add the snackbar, badges, rich tooltips, alerts, stats and empty states
tests / lint (push) Successful in 1m3s
tests / feature (8.4) (push) Failing after 1m5s
tests / feature (8.5) (push) Failing after 1m4s
tests / browser (chrome, chromium) (push) Failing after 1m5s
tests / browser (firefox, firefox) (push) Failing after 1m1s
tests / browser (safari, webkit) (push) Failing after 1m1s
<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
2026-09-13 06:59:40 +02:00
Andreas Reinhold / reiniandClaude Opus 5 cd64f4f371 Add buttons, menus and the rest of M3 Expressive's actions
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
<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
2026-09-13 06:09:28 +02:00
Andreas Reinhold / reiniandClaude Opus 5 b48e879254 Add the Material 3 Expressive foundation
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m0s
tests / feature (8.5) (push) Successful in 1m1s
tests / browser (safari, webkit) (push) Successful in 1m59s
tests / browser (chrome, chromium) (push) Successful in 1m49s
tests / browser (firefox, firefox) (push) Successful in 1m54s
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
2026-09-13 05:24:11 +02:00
Andreas Reinhold / reiniandClaude Opus 5 9b53891a8e Scaffold the Livewire Material package
tests / lint (push) Successful in 1m14s
tests / feature (8.4) (push) Successful in 56s
tests / feature (8.5) (push) Successful in 56s
tests / browser (chrome, chromium) (push) Successful in 2m9s
tests / browser (firefox, firefox) (push) Successful in 1m34s
tests / browser (safari, webkit) (push) Successful in 1m41s
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
2026-09-13 04:59:21 +02:00