Plan step 41(iii): fixtures/tests for every literal kind (colour, radius,
shadow, font size/weight, line height, letter spacing, easing, duration) and
an off-scale media query, plus material-scheme.css excluded by name and by
its generated header wherever it sits among the scanned paths. Also adds a
fixture proving Tailwind-shaped bare words in running prose are never read
as classes.
Two fixes found by writing the fixtures rather than only the implementation:
a directly-scanned .css file was also running through the Blade/PHP/JS checks
(the same file's `ease-in-out` in a `transition` value tripped the old scale
check a second time), so a CSS file is now check (iii)'s alone; and
`withoutTokenFunctions()` leaves a `var()`/`calc()` call's own name and
parentheses standing once its arguments are blanked, which
`isSafeLiteralValue()` did not yet recognise as the same "nothing to see
here" as an empty value.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41(ii): exercises the tag-to-stylesheet mapping (plain, prefixed and
namespaced spellings), a package stylesheet's own imports counting toward its
dependents (split-button.css satisfies button.css and menu.css too),
->links() needing pagination.css, the always-required foundation.css, and a
package tag shadowed by the application's own anonymous or class component.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41(i): every Tailwind utility or variant in a Tailwind-free
application's views now gets a 2.0.0 hint instead of the old Tailwind-utility
replacement (which no longer compiles either) -- a layout component and prop,
an md-* class, or a var(--md-sys-*) token. New families cover flex/grid/gap,
spacing, sizing, display, text-layout and the M3 role utilities
(text-on-surface-variant, border-outline-variant, bg-surface-*, bg-primary);
the breakpoint and scale checks fold in with their hints rewritten the same
way. A class the application's own stylesheets declare is exempt.
literalClasses() now returns each class token unstripped of its variant
prefix (`sm:`, `hover:`) so the new checks can see it; the daisyUI check
strips it itself via the new withoutVariantPrefix() helper, as it always did
internally before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 (ii): the design guard needs the set of files an application's
CSS entry pulls in, transitively, to check whether a package tag's
stylesheet is among them — reusing bundle()'s own import resolution and
cache rather than re-implementing @import parsing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 40. ErrorPage::fallbackStyles() now inlines
Stylesheets::bundle() of the foundation and error-page.css (which
imports button.css and shape.css) rather than a hand-copied
stylesheet, so the fallback can never drift from the built version.
Every @font-face block is dropped structurally (withoutFontFace(),
brace-balanced, not a text search) since there is no build to serve
the font file; --md-ref-typeface-brand already lists ui-sans-serif,
system-ui and sans-serif after the brand name, so the page still gets
a sensible system stack. The scheme half comes from
Scheme::forStylesheet() drawn through SchemeStylesheet::levels(), in
material-scheme.css's own selector shape: standard, the medium and
high contrast levels, and a block per colour profile keyed on
[data-scheme] (the theme script has already resolved and written the
active one to <html> before this stylesheet is read, so nothing here
picks one in PHP). Both halves are cached per worker, the scheme half
by the scheme file's path and mtime.
Tests: both render paths, the inlined CSS's shape (no @import, no
relative url(), no @font-face, the button/shape/error-page rules, the
scheme's roles, [data-contrast='high'] and a profile block), and a new
browser test that forces the fallback and checks the button, the
shape and a dark-mode repaint.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
SchemeCommand::levels()/selectors()/blocks() move to a new
SchemeStylesheet, and Scheme gains forStylesheet(), which resolves a
scheme (and every profile) at all three contrast levels together. Plan
step 40's fallback needs to draw the same colours material:scheme
writes, without duplicating its selector logic.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 39 (part 6). README's Requirements, Installation and
Stylesheet-and-script sections drop Tailwind CSS 4, the second Vite
entry, @source and the Tailwind-replaces-utilities framing; the plain
CSS entry keeps the layer statement (matching every package
stylesheet), and the Layout example's <body> loses classes
foundation/base.css already applies globally (surface, on-surface,
the brand typeface) since Tailwind is no longer there to write them.
UPGRADE.md §6 gets the same Tailwind-free setup in place of the
interim two-entry note step 37 left. The development skill's install
snippet was already Tailwind-free; its one stale paragraph describing
a second Tailwind entry is deleted (fixed with the build in the
"Delete Tailwind's half" commit). DesignGuard's own Tailwind-detection
docs stay: applications can still carry leftover Tailwind classes for
the guard to catch, which is unrelated to the build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 39 (part 5). Tailwind no longer clears its own breakpoint
scale anywhere in the stack, so the guard's rationale changes from "a
leftover sm: compiles to nothing" to "a browser drops the invalid
class or wrong-width rule just as silently" — the scans themselves
already worked over plain files and needed no change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 39 (part 4). With Tailwind gone the Workbench's Vite build
still turns every `:dir(rtl)` into a long `:lang()` list (its own CSS
minifier, not Tailwind's doing — confirmed by rebuilding and grepping
the output). Every RTL mirror rule across the eleven stylesheets that
had one now selects `:is([dir='rtl'], [dir='rtl'] *)` instead, matching
`:dir(rtl)`'s own specificity (one pseudo-class) and its inherited
"this element or a descendant of one carrying the attribute" reach; the
built CSS now keeps the selector as written (no `:lang(` or `:dir(` left).
RTL browser tests drop the `lang="ar"` workaround the old rewrite
needed (ContainmentTest's side-sheet probe, LayoutTest's shared
layoutPage() helper) and set only dir="rtl"; CarouselTest's probe
already did. Feature tests asserting the selector's literal text
(Progress, Overlay, Menu, ListDetail, Icon, Carousel) updated to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 39 (parts 1-3, folded into one commit: PHP tests read the
deleted files directly, so they cannot land apart from it). Tailwind
leaves the whole stack:
- Delete resources/css/tailwind.css, tokens/theme.css and
tokens/utilities.css. Neither token file declared an --md-sys-*
custom property of its own (both only referenced tokens declared
elsewhere), so nothing loses a value; the md-* interaction and text
classes already mirror utilities.css's declarations exactly
(foundation/interaction.css, text.css).
- npm uninstall tailwindcss @tailwindcss/vite; vite.config.js drops the
plugin and its import; composer.json drops the tailwindcss keyword
(no lock change — keywords are outside Composer's content hash).
- The Workbench now builds one CSS entry, workbench/resources/css/app.css
(all.css, showcase.css and the scheme; package.css is folded in and
removed) instead of two, used by ErrorPage::assets() and every
browser-test probe page's raw @vite() call; the showcase's own pages
still take their CSS from the bundle route.
- TokensTest and StylesheetsTest: the two facts theme.css and
utilities.css carried (every scheme role becomes a colour, resolved
on the element; md-type-* matches the type-* utilities) are asserted
directly against the scheme and text.css now that there is no second
copy to cross-check; StylesheetsTest gained a full-tree scan (every
.css file under resources/css/ is reached from all.css or
showcase.css, no exclusions left for Tailwind); the Workbench-entry
test and every "moved out of tailwind.css" assertion updated for the
single entry and its removal.
- DesignGuard.php's comments and the development skill's setup section
no longer name the deleted files or a second Tailwind entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 39 (part 0): the step 38 review found <x-scaffold> and
<x-tooltip> never referencing $attributes, so a caller's class and
style were silently dropped. The scaffold's root is its single
data-md-scaffold div; the tooltip's root, documented in its header, is
the standalone wrapper it draws around a trigger (data-md-tooltip-anchor,
merged with its own anchor-name) — passed an anchor instead, it renders
only a popover fragment beside another component's root and takes
nothing of the caller's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: ShowcaseController::layout() passes a title
("List-detail · Layout"), but the frame overwrote it with the section's
own, so all three pages and the Layout overview shared one title. The
frame keeps a title it was given; the layout page test asserts each.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: de-flaking the reduced-motion test moved the click
out of the retried assertScript, which was right, but also dropped the
arrival read in the click's own task, so a smooth scroll finishing
within assertScript's retries would now pass a test named "scrolls
instantly". The click script records at(1) at once and the assertion
requires it. It is read against the slot size measured before the
click: the row re-measures a frame later (222 against 217.7px at
1280px), which is why a later read in the same evaluation fails.
Chromium: CarouselTest 10 of 10, the test itself three runs green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: the Colour section dropped its "Ink and lines"
group with the retired Tailwind ink names and put nothing in its
place, so no page showed the md-ink-* classes that replaced them or
how to draw a line. A new "Ink and lines" example writes md-ink,
md-ink-variant and md-ink-quiet with their roles, the state inks, and
a line as <x-divider> and an edge as <x-surface outlined>.
Doc comments teach too: the Icons intro showed optical="20" alone for
a 20px icon, where <x-icon size="20"> picks the cut itself; icon's
header still spoke of a caller's size-5 class, the navigation rail's
usage wrapped it in flex/min-w-0 utilities, and the menu item's
icon-class example named a Tailwind-style text- class.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: the component sections' code samples, which an
application copies, carried 60 data-md-showcase-* hooks that only
showcase.css draws (a sized loading indicator, the plain badge's own
colours, a bordered bar frame, the navigation demos' frames, a table's
scroll wrapper...), so a copied sample did nothing in the application.
They now write what an application has: <x-surface outlined corner>
for a frame and a line, <x-row> for the FAB menu's corner, and an
inline style from the tokens for a size, a scroll box or a colour,
as the Containment batch already did. The side sheet demo takes
align="stretch", which holds in both of <x-row stack-below>'s modes,
instead of a hook that undid align="start" once stacked. The menu
icon colours go through icon-class with two unlayered application-like
classes, which the sample's comment names, and the fields' and date
picker's widths are inline styles now that style reaches their roots.
The radio hint said "from sm"; it is medium.
showcase.css keeps only the frame and the foundation specimens; its
header describes the file instead of the batches that wrote it. The
tests reject a showcase hook or a class outside md-* and the named
application classes in any example, a showcase.css selector outside
its own hooks, and a hook no showcase view renders.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: the showcase kept two unlayered width classes
because <x-input> and <x-datepicker> "don't forward style usefully".
They did not: the Phase F rule is that a caller's class and style land
on the component root, and the field family sent class to the field's
root but style to the inner control, where a width sizes nothing.
Input, password, textarea, select and file now pass style to the field
root with class; datepicker and timepicker put it on their root;
<x-group> dropped style entirely and <x-split-button> gave it to the
leading button; <x-search> rendered it twice, on the root and the
input. A textarea given a style also lost its autogrow rows, since
its own style attribute came second and the browser ignores it.
A dataset test renders each component with both and requires them on
the root, once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: nine sections' examples wrapped their components
in <x-livewire-material::stack>, ::row and ::grid (148 tags) beside an
unprefixed <x-button>, while bars, containment and the layout pages
wrote <x-stack>. The code sample is what an application copies, and
the example component rewrites only unprefixed tags to a configured
prefix, so the namespaced ones taught a second form and escaped it.
Every example now writes <x-stack>, <x-row> and <x-grid>; a test
rejects a namespaced tag inside an example heredoc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: the index reads example titles out of each
section's source with a regex, which once skipped all six Progress
examples without a sound; the existing test only checked that indexed
anchors exist. A new test renders every section and canonical layout
page and requires the index's examples for it to equal the titled
examples the page draws, both ways, so a format the regex misses (or
an anchor it invents) fails by page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: the frame's structural rule that kept a section's
own <h2> for screen readers (Tailwind's [&>section>h2]:sr-only before,
a showcase.css rule during the rewrite) was removed as dead weight
once every section had a real heading, so each page drew its title
twice, the <h1> and an identical headline under it. The <h2>s are
md-visually-hidden again, the frame's comment says why, and the
section page test asserts it. The Layout section's description said
"window size classes"; M3's term is breakpoints.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: a %00 in the asset path reached realpath(), which
throws on a null byte, so the route answered 500 instead of 404; a
directory whose name ended in a served extension would have reached
response()->file(). Both 404 now. The tests add what the review
probed: encoded dot segments and slashes, backslashes, absolute paths,
a directory, a very long path and a symbolic link pointing out of a
served folder, all 404.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 review: /material/shell still passed the whole configured
Vite list, so it loaded Tailwind and all.css a second time beside the
showcase bundle. Both frames now take ShowcaseAssetController::scripts():
every entry but a stylesheet, by the same extensions Laravel's Vite
treats as CSS (not only .css), and skip @vite() when nothing is left,
since a list of stylesheets alone would read a build manifest for
nothing. A test renders the overview, a section, a layout page and the
shell against a hot file. The frame's header said the rail collapses
from `lg`; it is `expanded` (840px). The config comment is shorter and
true.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Found running the full browser suite for plan step 38's last batch,
unrelated to its content changes (confirmed by reverting every
touched file and reproducing both failures against the pre-batch
commit): CarouselTest never pinned a window size, so the multi-browse
row's keyline math only lined "scroll by 2 slots" up with the open
keyline at some widths — the host's own default (here, wider than the
carousel was ever exercised at) left a remainder large enough to fail
four assertions. carouselShowcase() now resizes to 1280x900, matching
the width other browser tests already treat as "large desktop".
One test still failed after that: it put a `.click()` inside the
script assertScript asserts, and assertScript retries its whole
expression on a false result (this file's own comment says so) — a
retry re-fires the click, drifting the carousel one item further each
time and never recovering, unlike every other test here, which clicks
through a separate call first. Split the click out the same way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38's last batch cleared the one real medium: this premise
test relied on (containment.blade.php's Lists example, now
<x-grid :columns="['compact' => 1, 'medium' => 2]">) — its own goal,
not a regression, but it leaves nothing left over to find. The test
now builds a real two-heredocs-deep fixture (an @php block nesting a
<<<'BLADE' example, exactly how a showcase section is shaped) and
reuses the first test's own pattern and helper against it, so the
scan is proven on demand instead of by whatever Tailwind happens to
still be lying around.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch), plan step 42's target for tests/: every
Tailwind utility class in a tests/Browser/*.php probe's Blade string
or Livewire component render() (~60 class attributes across 14 files)
becomes an inline style built from --md-sys-color-*/--md-sys-shape-*/
--md-sys-measurement-* tokens or a literal px value for an arbitrary
demo size — PickingTest's clip box, every probe's <body
class="bg-surface">, the grid/stack/row wrapper divs, the carousel
item's coloured filler. No test's assertions, selectors or expected
text change; these are layout containers around the components under
test, not anything a test reads.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): only two of the previous batch's three
call sites actually need a plain unlayered class for their demo width
— <x-input> forwards `style` to the raw <input>, not the field wrapper
that is sized, and <x-datepicker> forwards neither, only `class`,
`wire:key` and `x-model` — so .showcase-w-sm and .showcase-w-xs stay.
<x-slider> does forward `style` to its root, so its vertical demo's
shared height is one now instead of the .showcase-slider-vertical
class. .showcase-w-narrow stays a class on purpose rather than an
inline style: fields.blade.php's "Narrower" input demonstrates an
application's own unlayered rule beating the package's layered
default, and an inline style would win regardless of layers, proving
nothing. The header explains all three.
StylesheetsTest gains showcase.css's own shape check — the same rule
every package stylesheet passes (header, layer statement, plain
imports, no Tailwind directive, breakpoints only at M3's four) — with
its three documented unlayered exceptions as the one allowed gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): every showcase view is now rewritten
without Tailwind, so the showcase's layout keeps only the JavaScript
entries of the application's configured Vite list (Livewire, Alpine)
before its own bundle link, instead of the whole array — its own CSS
already comes solely from ShowcaseAssetController's bundle
(Stylesheets::bundle() of all.css, showcase.css and the scheme), and
linking the application's build too meant the package's own CSS
(all.css) loaded twice on every showcase page (once through the
Workbench's package.css entry, once through the showcase's own
bundle), besides pulling in Tailwind's CSS for nothing. ErrorPage::
assets() keeps passing the whole configured list: the error pages have
no bundle of their own to fall back on, and step 40 is where their
fallback is redesigned. The config's own comment says so.
The frame's temporary visually-hidden <h2> rule goes too — it existed
only because the first batch could not reach into the still-Tailwind
sections/*.blade.php partials it @include's; every section now
carries its own real `class="md-type-headline-md"` heading, so the
structural rule that quietly hid all of them is dead weight.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): the section wraps in <x-stack as="section">
and the error-page button row in <x-row>, text on the md-* classes;
needs no showcase.css hook of its own. The error-page and mail links,
every route and every visible label stay as they were.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): the navigation-bar and navigation-rail
demos' bounded frames become data-md-showcase-nav-demo (`bar`,
`bar-wide` inside its own data-md-showcase-nav-scroll wrapper, `rail`,
`rail-wide`), the "the page" filler is data-md-showcase-nav-page and
the content pane beside a rail data-md-showcase-nav-content; a group
of demos moves onto <x-row>/<x-stack> with inline max-width styles for
the two arbitrary demo widths (400px, 800px) Tailwind's arbitrary
values gave them. The "Open the scaffold" link's md-link/md-ink-primary
classes match bars.blade.php's.
Every #navigation id, aria-label and visible label the Chromium
NavigationTest (against its own probes and /material/shell) relies on
is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): the app-bar/toolbar row becomes <x-stack>/
<x-row>, each bordered demo box a data-md-showcase-bar-frame hook
(`visible` for the search bar, whose docked suggestions must not clip;
`rounded` for the large-screen docked toolbar, which only gains its
edge and padding from `expanded`, 840px), and the search bar's own
avatar reuses the existing data-md-showcase-avatar hook. The
scrollable-tabs demo's forced-narrow box is
data-md-showcase-tabs-narrow, and the "Open the scaffold" link uses
the shared md-link/md-ink-primary classes in place of the old
Tailwind-era link/text-primary utilities.
Every #bars id and visible label/tooltip the Chromium BarsTest
(against its own probes) and the search index rely on is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): every item's colour role pair moves from a
Tailwind bg-*/text-*/bg-linear-to-br utility string to a plain
`background-color`/`color`/`background: linear-gradient(...)` inline
style built from --md-sys-color-* tokens (the same "caller's own
style" mechanism as the foundation pages' colour swatch), and each
<x-shape> takes its px size as a prop instead of a size-* class.
data-md-showcase-carousel-swatch only sizes and centres the shape
inside the item.
Every #carousel id and data-md-carousel-item/multi-aspect selector the
Chromium CarouselTest reads is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (last batch): every example moves onto the layout
components, the md-* text classes and tokens. Wrapper divs that only
sized or grouped a demo (grid/list widths, the divider-and-collapse
stack, the dragged-card scope) become <x-grid>/<x-stack>/<x-row> with
an inline style where a specific px width still matters; a video
thumbnail's Tailwind gradient utility becomes an inline
background/color pair since each list-item's own colours differ. The
standard side sheet demo keeps M3's row-then-column breakpoint through
<x-row stack-below="expanded">, with a data-md-showcase-side-sheet-demo
hook that restores the row's own stretch alignment once it collapses
to a column (an explicit align="start", needed for the row layout,
would otherwise leave it shrink-wrapped).
Every id, data-md-* selector and visible string the Chromium
ContainmentTest reads (#containment, its dialog/sheet/card queries) is
unchanged; no demo content removed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: text.css already has the three classes this section
needed (md-text-end, md-tabular, md-visually-hidden), so only the
table's own scroll wrapper and the pagination list move — the wrapper
to a data-md-showcase-table-scroll hook, the two paginators onto
<x-stack>.
Every #data id and data-md-list-row/aria-selected selector the
Chromium DataTest reads is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: single-column groups move onto <x-stack>, per-breakpoint
ones onto <x-grid>, and the "Vertical" row's shared height (M3's
second orientation needs one on the wrapper, which the label and hint
share) moves to a showcase-slider-vertical class — <x-slider> forwards
only class, style and wire:key to that wrapper, so a data attribute
would have been dropped silently.
Every #sliders id and data-bound span the Chromium SliderTest reads is
unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: both examples move onto <x-grid> with <x-stack>
columns, x-data landing on the bound example's own stack.
TimepickerTest's Chromium selectors run against an isolated timeProbe
fixture, not this page, so nothing there is at risk.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: every two-column layout moves onto <x-grid> with
<x-stack> columns, x-data landing on the grid itself; the small demo
picker reuses the showcase-w-sm class the Text fields section already
defines.
DatepickerTest's Chromium selectors run against isolated probe
fixtures, not this page, so nothing there is at risk.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: every two-column layout moves onto <x-grid> with
<x-stack> columns; the checkbox group's card, its legend and the two
search demos' bespoke geometry (an avatar chip, the compact
search-icon-button bar) move to data-md-showcase-* hooks. The two
small demo controls and the "Narrower" field's override move to
.showcase-w-sm/-xs/-narrow — plain, unlayered classes, since
<x-input> and <x-datepicker> forward only class and style to the
element that needs sizing, never a data attribute. "Narrower"'s hint
is reworded from "A max-w-* class" to "An unlayered rule from the
call site wins", since that is now the mechanism being shown.
FieldsTest's Chromium selectors run against an isolated fieldProbe
fixture, not this page, so nothing there is at risk; #fields and every
id (showcase-error, showcase-error-filled, showcase-audience,
showcase-radio-theme) is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the Alpine-scope wrapper divs become <x-row>/<x-stack>
carrying x-data directly, giving Filter and Input chips M3's
full-width stretch in place of w-full/space-y-6. The narrow demo that
forces its chip set to scroll moves to a data-md-showcase-chip-scroll
hook (384px, Tailwind's old max-w-sm).
Every id, value and derived aria-label the Chromium ChipsTest
selectors read (#chips, the filter checkboxes' values, "Remove
<email>") is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the "bind" example's wrapper div becomes <x-stack>/
<x-row>, and the one enlarged circular indicator moves to a
data-md-showcase-progress-lg hook. Along the way, $examples changes
from a ['Title' => [code, stack]] tuple back to the plain
'Title' => <<<'BLADE' every other section uses, with the row
exceptions listed separately: the tuple's leading `[` broke
Sections::index()'s regex, so every one of this section's examples
was silently missing from the showcase search index. Fixing the shape
recovers them — the Feature ShowcaseTest's search-index assertion
count moves from 259 to 266.
Every aria-label the Chromium ProgressTest reads off #progress
(Linear, Circular, Circular wavy, Bound, the indeterminate variants)
is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the badge/icon wrapper spans move to
style="position: relative; display: inline-flex", the exact pattern
<x-badge>'s own header documents; Alerts, Stats and the two empty
states move onto <x-stack>/<x-grid> in place of w-full and a
Tailwind grid, so a caller no longer sizes each child by hand. The
"plain" badge (no colour of its own, by design) and the illustration's
two decorative fills move to data-md-showcase-* hooks, as does the
tooltip demo span's focus-ring corner; the shortcut kbds now use the
data-md-showcase-kbd hook the frame batch already drew.
No demo content removed; #communication, the toast/snackbar hooks and
the rich-tooltip triggers (Press for details, About expiry) the
Chromium CommunicationTest selectors read are unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: text moves onto the md-* classes; the "Icons in their
own colour" example's two roles outside the fixed md-ink-* set
(tertiary, secondary) move to a data-md-showcase-icon-color hook in
showcase.css targeting the item's [data-md-icon] child, since
icon-class only reaches roles the fixed set already names. Swimming's
info icon keeps icon-class, now icon-class="md-ink-info".
Every id, aria-label and role selector the Chromium ActionsTest
selectors read against /material/menus (#menus, the More/Sort/Share/
Assign to/Photo triggers, the submenu and sheet-at-compact behaviour)
is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: every example moves onto the layout components and the
md-* text classes. Wrapper divs that existed only to give an Alpine
scope to a button group or a connected <x-group> ("A connected group
that owns its selection", "Square groups", "A choice as a connected
group") become <x-stack>/<x-grid> carrying the x-data directly, which
also gives them M3's full-width stretch in place of w-full. The
loading indicator's two sized, tertiary-coloured variants and the FAB
menu's bounded demo box move to data-md-showcase-* hooks in
showcase.css; hint-class="text-warning" becomes hint-class="md-ink-warning",
the exact example the <x-group> header itself documents.
No demo content removed; every id, aria-label and input[name] the
Chromium ActionsTest selectors read (#buttons, showcase-theme,
showcase-days, the "Tonal" tooltip, the progressbar, collapse-on-scroll,
data-md-selection) is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
InternalComponentsTest strips a showcase example's <<<'BLADE' heredoc
before checking that a package view only ever refers to its own
components through the livewire-material:: namespace, since an
example heredoc documents an application's own (unprefixed) usage.
The check only recognised an array's BLADE, terminator; the Layout
section's three dedicated pages (plan step 38) assign their one
example to $code with BLADE; instead, which the check now strips too.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the Layout section's Overview keeps its breakpoint
table and live "This window" readout (now a shared
<x-showcase::breakpoint-readout> partial, and the five breakpoint
cards a real range-queried data-md-showcase-breakpoint-card instead
of Tailwind's max-medium:/medium:max-expanded: variants), but the
three canonical layouts move off it onto a page of their own:
/material/layout/{list-detail,supporting-pane,feed}
(ShowcaseController::layout(), routes/showcase.php), each built on
the real component, sharing one <x-section-nav> Sections::
layoutPages() feeds. The search index gains an entry per page with
its own URL, and the component-homes scan now also reads the three
pages' own source so <x-list-detail>, <x-supporting-pane> and
<x-feed> keep a home.
The whole section moves onto the layout components and md-* classes:
no bespoke Tailwind grid or table styling remains.
LayoutTest's browser test now visits the three dedicated pages
instead of one shared /material/layout; a new Feature test asserts
each page renders and is linked from the Layout section.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the filter row is now real components — <x-input
icon="search"> and two <x-checkbox>s — instead of a bespoke bordered
label and accent-coloured native inputs; the result grid is
<x-grid min-item="120px"> of <x-surface as="li">/<x-stack> tiles.
The fetched symbol's mask stays the view's own x-bind:style (its
shorthand depends on the fetched name), but the size it toggles
between 24 and 20px is now the data-md-showcase-icon-size attribute
a script sets, never a class, drawn by data-md-showcase-icon-mask in
showcase.css.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the two demo buttons are now real <x-button>s (filled
for Move, text for Change the figure) instead of a hand-rolled state-
layer/focus-ring button; each spring's row and its travelling dot
move onto <x-surface>/<x-stack> plus data-md-showcase-motion-row/
-track/-dot in showcase.css — only the dot's size and colour are
fixed there, since the transition under test stays the spring token
in the view's own inline style.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the shadow-level grid moves onto <x-surface> and
<x-grid>; each tile is a data-md-showcase-elevation-tile keyed on
data-md-elevation, drawing var(--md-sys-elevation-1..5) — the same
tokens the label now names instead of the retired shadow-elevation-*
utility.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the corner grid and the shape gallery move onto
<x-grid> and <x-stack>; each corner swatch is a data-md-showcase-
shape-swatch keyed on data-md-corner, one showcase.css rule per
corner token; <x-shape :size="80"> replaces the size-20 utility, its
fill the caller's style="color: var(--md-sys-color-secondary-
container)" since a shape's colour is inherited text colour, not a
class.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: the typescale listing moves onto <x-surface> for its
container and a bespoke data-md-showcase-type-row (a fixed label
column beside two flexible samples from 840px, one column below it,
a rule between rows) since no layout component draws an asymmetric
grid track; the sample text itself is styled by the very md-type-*
class names the row's own label prints, so the code sample and the
rendered specimen are the same string.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: swatches move from Tailwind's bg-* role utilities to
data-md-showcase-swatch spans whose fill is the caller's own inline
style="background: var(--md-sys-color-{role})" (a swatch's colour
depends on the role it shows, so that one declaration cannot be a
class), sized and edged in showcase.css; the two grids and every
per-theme panel move onto <x-grid>, <x-surface> and <x-stack>; text
onto the md-type-*/md-ink-* classes. The legacy "Ink and lines" group
(bg-body, bg-meta, bg-quiet, bg-structure, bg-chrome, bg-divider) is
dropped — those Tailwind ink/line names end with Phase F and their
roles already show under Surface.
ThemeTest and ColourProfilesTest read the swatch's computed colour
through the new data-md-showcase-swatch="primary" hook instead of the
retired .bg-primary class.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (first batch): layout.blade.php, index.blade.php,
section.blade.php, shell.blade.php and the example component now
lay out through the layout components (pane, stack, row, grid,
surface) and their props, text through the md-* classes, and
everything else through showcase.css's data-md-showcase-* hooks — no
Tailwind utility remains in any of them. The head links the new
stylesheet route after @vite(...), not before: that entry opens the
document's first @layer statement, and linking the showcase's own
bundle ahead of it re-anchors the material layer before Tailwind's
own and lets its preflight's `padding: 0` beat the search view's
padding-top (found by a failing browser test). @vite(...) stays, its
Tailwind CSS entry included, only for the section views (still
Tailwind until the next batches) and the JS runtime; the config
comment says so.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38: resources/css/showcase.css, in material.components
beside components/error-page.css for the same reason (a whole page's
chrome, not a reusable component, still outranked by an application's
unlayered CSS by design). Keyed on data-md-showcase-* hooks; batch 1
covers only what the frame (layout, index, section, shell, the example
component) needs beyond the layout components and the text classes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 38 (first batch): a route bundles all.css, showcase.css and
the application's generated scheme (Stylesheets::bundle(), found next
to its configured JSON by swapping the extension, or a Scheme::load()
fallback in tokens/scheme.css's own shape) into one long-cached,
content-hashed CSS response; a stale hash redirects to the current
one. A second route serves the fonts and SVGs its relative url()s
point at, from the package's fonts/ and svg/ folders only, MIME-typed
by extension and 404ing on ".." or an unlisted extension. Both stay
unregistered with the showcase off.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. Folding the layout-only checks into the all.css-wide
ones dropped the layout test's requirement that a width query be written
as a range (`width >= 840px`), leaving only its unit and breakpoint
checked. The all.css-wide media query test now rejects min-/max- width,
height and aspect-ratio features in every stylesheet, which the package
already never writes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. Removing material.css left README.md, the
development skill, UPGRADE.md and the tailwind.css and foundation.css
headers telling an application to import foundation.css, tailwindcss and
tailwind.css in its place. material.css was also what brought layout.css
and components.css, so that install rendered every component unstyled,
and without a layer statement Tailwind's preflight would have outranked
the package's layers.
The skill's snippet is now the Tailwind-free one 2.0.0 ends with: the
foundation, then the stylesheet of each component the views render, or
all.css. README.md, whose layout and prose still assume Tailwind, shows
all.css in its own entry and Tailwind in a second, both opening with the
layer statement, the shape the Workbench now builds; the skill and
UPGRADE.md describe that interim in a sentence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. The Vite test counted each of four files' plain
root rule, which the minifier folds into one even when a build repeats
the whole stylesheet: it passed against the Workbench's Tailwind entry,
which repeated 231 rules. It now asserts that no innermost rule repeats
under the same at-rules, which that build fails and a deduplicating one
passes, and it builds a second entry shaped like an application's —
outside the package, the foundation, then four component stylesheets
that each import button.css, and button.css again — where button.css
must also keep its first position. The fixture config refuses to run
without DEDUP_OUT_DIR, so it never writes into the package tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. @tailwindcss/vite inlines the imports of an entry
that uses Tailwind without Vite's skipDuplicates, so with all.css beside
@import 'tailwindcss' every shared component stylesheet repeated later in
the cascade (231 repeated rules; button.css's hover and disabled rules
thirteen times), and every browser test ran against an order no
application gets. workbench/resources/css/package.css now holds all.css
and the scheme in an entry Tailwind never touches, and app.css keeps
Tailwind for the showcase's classes. The built package CSS repeats no
rule, and the Workbench's CSS shrinks from 559 to 406 KB.
Both entries open with the same layer statement, properties first,
because Tailwind hoists that layer to the top of its output; the order
is the one the single entry had, whichever the page links first. The
showcase's Vite config, vite.config.js and the error page test's probe
manifest name the new entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. The collapsed rail FAB's gap and minimum width tied
with fab.css's extended-size and collapse-on-scroll rules at three
attributes and won only by coming after them. A deduplicating bundle
keeps that order (navigation-rail.css imports fab.css), but a build that
repeats a shared stylesheet need not: all.css imports fab.css again after
the scaffold has reached the rail, and the Workbench's Tailwind entry
inlines every occurrence. Doubling [data-md-extended] settles it by
specificity, as the flat shadow already was, so the order no longer
matters.
A scan of every component and layout stylesheet pair for same-layer,
same-property ties on a shared hook found no other override that relies
on order: the remaining ties are on mutually exclusive elements or sit
behind an import edge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37 review. bundle() left an @import it did not recognise
exactly as written, in the middle of the output, where a browser ignores
it: a layer(), supports() or media condition was silently dropped, an
unquoted url(./x.css) import was left in place with its path mangled by
the url() rewrite, and a bare specifier or absolute URL vanished the same
way. An @import after a rule or inside a @layer block was inlined anyway,
nesting a whole file's layers inside another. Each now throws, naming the
file; unquoted url() imports and @IMPORT inline like the other forms.
The cache keyed only the top-level files' mtimes, so a changed
button.css left a cached all.css bundle standing in a long-lived worker;
a cached bundle is now served only while every file it inlined keeps its
mtime. A null and an empty $base no longer share a cache key, a leading
@charset or byte-order mark is dropped from each inlined file, and a
test pins the import graph free of cycles, the one case where a
depth-first bundle would place a stylesheet before a file it imports.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37: the showcase and the error page's fallback serve CSS
without the application's Vite build, so nothing deduplicates their
@imports for them the way Vite's bundled postcss-import does — a
browser's native @import fetches every occurrence, it does not skip a
file it already loaded. Stylesheets::bundle(array $files, ?string
$base = null): string does in PHP what that build step does: it
inlines every @import depth-first, each file once, first occurrence
kept; leaves a bare specifier or an absolute URL untouched; rewrites a
relative url() against $base (or, without one, against the directory
of $files[0]); breaks a cycle instead of looping; throws naming the
importer when an import is missing; and caches per resolved file list
and mtime, with resetCache() for tests.
tests/Feature/StylesheetsBundleTest.php covers bundle()'s own
behaviour (dedup, url() rewriting, cycles, the missing-import
exception, the cache) and, at the end, pins the Vite deduplication
bundle() is modelled on against a real build — of all.css alone
(tests/Fixtures/dedup.vite.config.mjs), whose docblock explains why:
the Workbench's own entry still shares one file with @tailwindcss/vite
until plan step 39 removes it, and that plugin bundles its whole
reachable module graph itself, without the same dedup guarantee.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37: an application on 1.x-style imports now imports
foundation.css before @import 'tailwindcss' and tailwind.css after it,
the way foundation.css's own header has described since Phase F
started — material.css only ever saved writing those two lines.
Updates every place that named it: tailwind.css's and
foundation/hidden.css's and tokens/utilities.css's header comments,
the development skill's install snippet and Tokens section, README.md's
install snippet, and a new line in UPGRADE.md's migration notes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 37: all.css replaces the two interim import lists with one
entry for an application that wants everything — the foundation, every
layout stylesheet and every component stylesheet, grouped under the
same block comments components.css used, plus a Layout block. It also
directly imports the three files nothing imported by name before
(layout/spacing.css, layout/visibility.css, components/selection.css),
so every file under components/ and layout/ is now one @import away.
Every test that read a block of components.css or layout.css now reads
the matching block of all.css through one shared helper (allCssBlock(),
in tests/Pest.php so it loads for any test run) instead of repeating
the same substr() search in each file. StylesheetsTest.php's shape,
Tailwind-free and breakpoint checks, previously run twice (once for
the foundation, once for the layout tree), now run once over the whole
tree all.css reaches, since every component and layout stylesheet is
plain CSS after step 36; a new test asserts all.css imports everything
under components/ and layout/ exactly once. The Workbench imports
all.css in place of layout.css and components.css.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
hint-class="text-error" and icon-class="text-error" named a Tailwind
utility Tailwind never generates: its content scanner does not read
tests/Browser/*.php, so a class used only in a route fixture there
never makes it into the built CSS, and the assertion compared two
unstyled defaults that happen to differ rather than two error-coloured
elements. md-ink-error is a real, always-present class (text.css) for
exactly this "an application's own colour" case group.blade.php's own
comment already recommends (plan step 36, closing the full run).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Review leftover (plan step 36): the searchable list's highlighted-row
hook was still the plain pre-Phase-F name, in both the view's binding
and its scroll-into-view lookup and the shared rule in menu.css that
paints it. No test named it directly, so none needed a change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Review leftover (plan step 36): none of the three foundation/shared
stylesheets was in a group dataset, so their shape, token and
breakpoint rules went unchecked. icon and shape have views of their
own and sit in components.css's first block, so they join
ActionStylesheetsTest's dataset outright, with that file's block check
narrowed to look for them there instead of the actions block.
selection.css has no view (checkbox, radio and toggle each import it)
and components.css never imports it directly, so it follows
navigation-item.css's pattern in InputStylesheetsTest: a second,
stylesheet-only dataset alongside the view-based one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
The reviewer asked this run to look closely at it; no test exercised
it. toolbar.css's primary recolour already excludes :disabled and
[aria-disabled] so button.css's own disabled colour wins there — this
pins that a disabled button in a standard toolbar resolves to the
disabled formula, not primary, while an enabled sibling resolves to
primary (plan step 36, navigation group).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
The reviewer asked this run to look closely at it; no test exercised
it. Checks the label span's clipped opacity/max-width and the FAB's
square shape while the rail is collapsed, then its read label and
wider-than-tall shape once the rail's menu button expands it, waiting
past the shared spatial-default spring both morph on (plan step 36,
navigation group).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Three FAB position tests chained ->assertScript() off a bare
$page->script(), which returns the script's value, not the page.
Fixing that surfaced a real settling issue: layoutPage() opens wider
than the 599/600px these tests probe before resize() narrows it, so
the FAB's inset-block-end (a spatial spring, scaffold.css) is still
animating in from the wider default margin when the page first
reports ready. Any baseline captured then is mid-flight, not the
resting value, so both tests now wait past the spring before taking
one. The safe-area test's inline-end (right) assertion also assumed
its margin and the safe area add together; the CSS is
max(margin, safe-right), an alternative to the margin, not additive
with it, unlike the bottom edge (plan step 36, navigation group).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
The overflow menu's trigger selector matched its own popover's menu
items too (they sit inside the same wrapper), so a click resolved to
5 elements instead of 1 — scoped to [data-md-menu-trigger]. Escape's
close is the browser's own light dismiss (menu.js), so a click right
after it needs to be past the 250ms reopen guard, as the same pattern
already is elsewhere (ActionsTest.php); the reopen test now waits.
Also adds the reviewer's requested coverage: the section nav's list
items share the row's width equally regardless of label length, and a
plain wire:model tab switches its panel locally without reaching the
server per click while wire:model.live does, one request that also
flushes the deferred property along with it (plan step 36).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
An app bar's overflow menu is two separate <x-menu> instances trading
places as the width crosses 600px, each display: none where the other
shows. Resizing away from a menu's trigger left its popover in an open
state forever, anchored to a wrapper nobody could see or reach anymore.
A ResizeObserver on the trigger now closes the menu once it has no box
left to render (plan step 36, navigation group).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
navigationExtrasProbe's pinned-snackbar test chained ->assertScript() off
$page->script(), which returns the script's value (null here), not the
page. Also adds the reviewer's requested browser coverage for an adaptive
rail with hide-when-collapsed across 839/840/1199/1200px: collapsed (not
away) at medium, away at expanded with data-rail-auto, standing again by
default from large (plan step 36).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. navigation-rail.css claimed each FAB
override beats fab.css by specificity alone; the collapsed FAB's gap
and minimum width tie with fab.css's extended-size rules and win by
order, which the file's own import of fab.css guarantees. The rail
view pointed at one file for the collapsed conditions that four files
write out. UPGRADE.md now says applications lose the `rail-collapsed:`
variant the development skill used to teach.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review sweep. <x-list-detail> renders an
<x-button> as the single-pane back action, but only pane.css picked up
button.css when the carry-over landed; an application importing
layout/list-detail.css alone got an unstyled back button. A new check
reads every layout view's component tags against its stylesheet's
imports, as the group stylesheet tests do for components.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. `[data-md-scaffold] main` carried only
step 35's `--md-layout-margin`; the rewrite added the region's flex,
padding and overflow clip to it, which would also land on a <main> an
application nests in the page. It is `[data-md-scaffold-content] > main`
now. The header said panes read `--material-margin` (only the region
does), that the bar wrapper carries `--material-bottom-bar` (the root
does), and that the old utility and the hidden-bar override both sit in
material.components (the scaffold's is material.layout).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. The Tailwind-era rail-collapsed:
variant is now plain selectors in 26 rules, each repeated in five
window bands, across navigation-rail.css, navigation-rail-item.css,
navigation-rail-section.css and layout/scaffold.css; the tests pinned
a few of them. A new test walks every rule whose :where() names the
rail and fails when a copy drifts from the seven conditions for its
band, or when a rule is drawn collapsed in some bands but not all five.
ComponentStylesheet gains rules() for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. The shared indicator and state-layer
stylesheet has no view of its own, so it was in no group dataset, and
since 9c134b2d the layout stylesheet tests skip the component files
scaffold.css reaches it through. Its layer, Tailwind, token, scoping
and components.css checks now run with the navigation components'.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. The toolbar's recolouring rules
(primary in a standard toolbar, on-primary-container and the pressed
fill in a vibrant one) outrank button.css's disabled rule by
specificity, so a disabled icon button in a toolbar drew in full
primary. They now skip `:disabled` and `aria-disabled`, and the header
no longer claims each override only meets a button's base selector.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. The rewrite kept `min-w-0` from the
view's `<li class="flex min-w-0">` but dropped `flex`, so tabs.css's
`li > [data-md-tab] { flex: 1 1 0% }` no longer applied inside the
section nav. The header also called body-large "the tab bar's own"
type, which is title-small.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36, navigation review. The tabs rewrite appended `.live` to
@entangle, so every tab set bound with a plain wire:model sent a request
on each click. @entangle already adds `.live` for wire:model.live, which
the view's own header promises is the caller's choice. Pinned for both
spellings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third batch, last component):
<x-scheme-picker>'s class lists move into resources/css/components/
scheme-picker.css, keyed on data-md-scheme-picker (its legend and
options grid, 2 columns below medium and 4 from it) and
data-md-scheme-picker-option, whose radio inside is read with :has()
for the chosen and the focus states — the input, not the label, is
the real control and carries the ring group.css's own segments keep
the same refinement for. `dark:bg-(--swatch-dark)` becomes
[data-theme='dark'] on the swatch (theme.css's own dark custom
variant reduces to a plain descendant selector here, since the
swatch never carries data-theme itself); the profile's name keeps its
label-large type as a text class on the view, since it is the
caller's own prose.
Hooks renamed: data-scheme-picker to data-md-scheme-picker,
data-scheme-option to data-md-scheme-picker-option, updated in
SchemePickerTest.php and tests/Browser/{ThemeTest,ColourProfilesTest}.php.
Browser tests added (docs/plans/material-3-browser-tests.md): the
contrast switch repaints at once and survives a wire:navigate, and
prefers-contrast: more picks high (Playwright's contrast context
option, the same shape as the existing reducedMotion tests).
This is the last navigation-group component off Tailwind. Every
package component is now drawn without Tailwind; tailwind.css keeps
only tokens/theme.css and tokens/utilities.css, which the showcase
still needs until step 38.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third batch): <x-theme-toggle>'s icon
button (mode="toggle"|"cycle") moves into resources/css/components/
theme-toggle.css, keyed on data-md-theme-toggle="toggle"|"cycle" and
still carrying data-md-icon-button, the hook toolbar.css matches
generically so the toggle takes a toolbar's own icon colour when
placed in one (N-13). The button renders the shared md-state-layer/
md-touch-target/md-focus-ring classes (N-01's 48px target); its
corner morphs from full to sm while pressed on the fast spatial
spring, the same motion <x-button>'s own icon buttons take.
mode="picker"|"contrast" already drew a connected group over native
radios (N-04, N-17's fix), so only its wrapper's hook needed renaming.
Hook renamed: data-theme-toggle to data-md-theme-toggle, updated in
AppBarTest.php, which carries theme-toggle's render tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third batch): <x-account-menu>'s class
lists move into resources/css/components/account-menu.css, keyed on
data-md-account-menu (the avatar trigger), data-md-account-menu-avatar
and data-md-account-menu-header. The trigger already carried the
target-extension and state-layer fixes from an earlier pass (N-01,
N-15); this rewrite only moves them onto the shared md-state-layer/
md-touch-target/md-focus-ring classes and their z-index-ordering
comment into the stylesheet. The header's name and email keep their
type and ink as the fixed text classes on the view
(md-type-title-sm/md-ink, md-type-body-sm/md-ink-variant), since they
are the caller's own prose.
Hooks renamed: data-account-menu to data-md-account-menu,
data-account-theme to data-md-account-menu-theme, updated in
AppBarTest.php, which carries account-menu's render tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third batch): <x-section-nav>'s class
lists move into resources/css/components/section-nav.css, keyed on
data-md-section-nav (data-md-section-nav-picker, its own trigger and
label) and <x-tabs>'s own hooks, reused whole rather than copied
(N-16's fix keeps the real scrollable tab bar it landed): the bar's
outline-variant divider, active indicator and aria-current colouring
are tabs.css's rules, not this file's. The picker's trigger takes the
shared md-focus-ring class and the fixed md-type-body-lg/md-ink/
md-text-start text classes for its own body-large type, since it is
prose on a plain button rather than a component's own type; icons
take a size prop instead of a size class.
Hooks renamed: data-section-nav to data-md-section-nav,
data-section-picker to data-md-section-nav-picker, updated in
TabsTest.php (which carries section-nav's render tests) and
tests/Browser/BarsTest.php.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third batch): <x-scaffold>'s own
styling moves into resources/css/layout/scaffold.css (the content
region, the bar and rail row, the banner, the actions row and its
rail-collapsed column layout, --material-bottom-bar and
--material-margin publishing, the skip link) alongside step 35's FAB
and content-margin rules already there. Every data-app-shell* hook
becomes data-md-scaffold-* (data-app-shell-bar, -actions, -banner);
the skip link is data-md-skip-link; data-app-shell itself is dropped,
data-md-scaffold already named the root.
The actions row's rail-collapsed:flex-col is written out branch for
branch as the navigation rail's own rewrite did for its internal
parts: the three width-independent conditions in one :where() group,
the four width-gated ones each in their own @media block. With that
gone, resources/css/tailwind.css's rail-collapsed custom-variant
shim (its last use) is removed; tailwind.css now carries only
tokens/theme.css and tokens/utilities.css, which the showcase still
needs until step 38.
navigation-bar.css's hide-on-scroll rule reading --material-bottom-bar
stayed unlayered only because <x-scaffold> published that variable
through a Tailwind utility, which no layered rule could outrank; now
scaffold.css sets it itself in material.layout, a layer
navigation-bar.css's own material.components always beats, so the
rule moves into the layer and the file fits one
@layer material.components block like every other navigation
stylesheet. navigation-bar rejoins NavigationStylesheetsTest.php's
dataset and NavigationBarTest.php's own duplicate shape test is
retired in favour of it.
Browser tests added (docs/plans/material-3-browser-tests.md): the
scaffold's FAB dropping the bar's own height once hide-bar-on-scroll
slides it away, at the trailing edge in a right-to-left document, and
clearing a safe area an application sets on its inline-end and bottom
edges.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 follow-up. Deleting navigation.css also deleted the only
definition of the rail-collapsed: custom variant, which
scaffold.blade.php's data-app-shell-actions row still uses
(rail-collapsed:flex-col) — scaffold's own rewrite is a later batch, so
it is still Tailwind. Left alone, that class would silently stop
matching anything. The definition moves into tailwind.css, updated to
the data-md-* hooks navigation-rail.css now renders (data-rail and
data-rail-auto are unchanged, so those branches needed nothing);
otherwise it reproduces the same seven branches navigation-rail.css's
own header documents, so the two cannot drift silently in what
"collapsed" means. It goes with the scaffold's own rewrite.
theme-script.blade.php's doc comment, which named the old variant, now
points at navigation-rail.css directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, second batch, cleanup). Every leftover
data-navigation-*, data-tall, data-hide-on-scroll, data-hidden,
data-width, data-align, data-divider, data-fill and component-level
data-open reference outside the rewritten stylesheets and views moves
to data-md-*, in the files the two component commits left alone:
tests/Feature/Components/ScaffoldTest.php (the scaffold's own rewrite
is a later batch, but it renders the bar and the rail today and asserts
their hooks); tests/Browser/NavigationTest.php, ShowcaseTest.php and
ColourProfilesTest.php (selectors only — document-level attributes,
data-rail, data-rail-auto and data-app-shell*, keep their names).
NavigationTest.php also gets the browser tests the plan owed this
group (docs/plans/material-3-browser-tests.md § Navigation): a tall
bar's vertical layout at a width where the short bar would go
horizontal; hide-on-scroll never firing while a pinned snackbar is on
screen; a narrow rail's 80px width and centred destinations; a rail
that hides when collapsed leaving the layout and coming back only
through an application's own menu button.
resources/css/components/navigation.css and its import in tailwind.css
are deleted now that nothing imports the file any more (the previous
commit's message said this already happened there; it did not — this
is where it actually lands).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, second batch): <x-navigation-rail>'s,
<x-navigation-rail-item>'s and <x-navigation-rail-section>'s class
lists move into navigation-rail.css, navigation-rail-item.css and
navigation-rail-section.css, keyed on data-md-navigation-rail (the
mode, data-md-width, data-md-align, data-md-hide-when-collapsed,
data-md-divider, data-md-fill, data-md-open), data-md-navigation-rail-
item (data-md-active) and data-md-navigation-rail-section. Every
Tailwind wrapper class in the view — the menu row's centring padding,
the FAB row, the two swapped menu glyphs, the brand's visibility —
becomes a hook the stylesheet draws instead; the menu button itself
renders the shared md-state-layer/md-focus-ring/md-touch-target
classes (N-01's pattern) since it draws its own layer on itself, not a
child. `<x-icon>` and `<x-badge>` take size and floating props instead
of size/position classes; a small data-md-navigation-icon hook (the
shared navigation-item.css) replaces the ad hoc "relative inline-flex"
wrapper a floating badge anchors to.
`rail-collapsed` (a Tailwind @custom-variant, forbidden in Phase F) is
reproduced as plain selectors, branch for branch: the three width-
independent conditions (a fixed collapsed mode; a collapsible rail the
visitor collapsed and not open; a modal rail not open) merge into one
:where() group, provably the same match set as three separate rules
since :where(A, B, C) on an element is true exactly when :where(A) or
:where(B) or :where(C) is; the four width-gated conditions stay
separate media blocks, since CSS cannot merge different `@media`
queries. Every rem length becomes px, since these are dp-based M3
tokens, not a text measure (unlike <x-pane>'s rem widths). The FAB
overrides for a rail's header — elevation 0 (N-03), morphing into an
extended FAB instead of swapping two by display (N-23) — move from
unlayered into this file's own material.components, like toolbar.css's
FAB override: fab.css's `[data-md-fab]` is one attribute, so a doubled
selector here always outranks it without needing to sit outside the
layer.
navigation.js: the arriving-indicator stylesheet and every code comment
follow the new hooks; resources/css/components/navigation.css is
deleted (nothing imports it any more) and its line in tailwind.css with
it. Behaviour is unchanged except one thing Tailwind's `rail-collapsed:`
variant could do that plain CSS in this shape cannot: it is gone for
consuming applications too, since the definition lived only in the file
this commit removes. The development skill's guidance for it is
rewritten to point at navigation-rail.css's own selectors instead of
teaching a Tailwind variant that no longer exists.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, second batch): <x-navigation-bar>'s and
<x-navigation-bar-item>'s class lists move into
resources/css/components/navigation-bar.css and navigation-bar-item.css,
keyed on data-md-navigation-bar (data-md-tall, data-md-hide-on-scroll/
-hidden) and data-md-navigation-bar-item (data-md-active), both in
material.components. The indicator's growing fill and its state layer,
shared with the rail's item, move into a new navigation-item.css both
files import — the day's own layer in datepicker.css is the precedent
for keeping it out of the shared foundation classes: the element
focused and pressed is the whole item, the layer drawn only on the
smaller indicator or pill inside it, which md-state-layer cannot do and
:focus-visible never matches. The wash colour moves from a dead
on-surface base overridden by both items to a shared on-secondary-
container declaration, and its opacities from literal 0.08/0.1 to
state.css's own tokens — no visible change, since the numbers matched.
Every size is px (16dp Tailwind quirks aside, this file had none); the
bar's own item-count and container-query layout, N-08's label-medium
fix and N-19's on-secondary-container wash already matched the audit,
so only hooks, units and layer needed to change.
The scaffold's hide-on-scroll offset rule (`--material-bottom-bar`,
navigation-bar.css) stays unlayered: <x-scaffold> still publishes that
variable with a Tailwind utility until its own rewrite, and a rule in
any layer loses to it regardless of specificity — unlike the FAB
overrides the rail commit moves into material.components, which only
have to beat another material.components rule.
navigation.css is not deleted yet (still Tailwind's for the rail); its
"Navigation" comment in tailwind.css narrows as each stream leaves.
navigation-bar.css is not in NavigationStylesheetsTest.php's dataset:
its one unlayered rule breaks that test's "every block is
material.components" assumption, so the same checks are in
NavigationBarTest.php instead, the same reason ErrorPagesTest.php
carries error-page.css's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, third and last stream): <x-tabs>'s
and <x-tab>'s class lists move into resources/css/components/tabs.css,
one commit for both since they share it. Root data-md-tabs; the bar
data-md-tabs-bar with data-md-variant/-stacked/-scrollable, holding
data-md-tab buttons (data-md-tab-content, the new data-md-tab-label
wrapping the label and badge, data-md-tab-indicator); the panel
data-md-tab-panel. The server-rendered hidden panel (N-05), the 52dp
scrollable offset (N-10), the indicator's 2dp inset (N-18), the ring
moved 2px out (N-20) and aria-current sharing the active colour
(N-21) already matched the audit; only hooks, units and layer
changed. Sizes (48/64px tab height, 90px minimum width) stay px;
spacing goes through the measurement tokens where the value matches
one (the icon-label and label-badge gaps, the panel's top padding).
Imports icon.css and badge.css for what the view renders.
Hooks renamed: data-tabs-bar, data-tab, data-tab-content,
data-tab-indicator, data-scrollable, data-stacked and the bar's
data-variant to data-md-*, updated in tabs.js (dataset.tab ->
dataset.mdTab), section-nav.blade.php (which reuses the tab bar's
hooks under its own, still-Tailwind, markup),
tests/Feature/Components/TabsTest.php and tests/Browser/BarsTest.php.
Added the owed browser test: a tab panel never flashes before Alpine
boots, read from an inline script that runs while the page is still
parsing, well before Alpine's own script (docs/plans/material-3-browser-tests.md).
This closes plan step 36: every navigation-group component is now
data-md-* and Tailwind-free; tailwind.css's Navigation block keeps
only navigation.css (the bar and rail, out of this batch's scope).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, second stream): <x-toolbar>'s class
lists move into resources/css/components/toolbar.css, keyed on
data-md-toolbar (data-md-variant/-vertical/-vibrant/-rounded/
-toolbar-place) and data-md-toolbar-group/-toolbar-fab. The docked
form with a FAB (N-02's --material-bottom-bar clearance), the vertical
24dp margin (N-12), the standard toolbar's primary icon buttons
(N-13), and the rounded large-screen form with dividers from 840px
were all already correct; only their hooks, units and layer needed to
change. The file no longer sits outside every layer: button.css and
fab.css now draw their own ink through data-md-* attributes rather
than a Tailwind utility, so a toolbar's more specific selector always
beats their single base color/background-color/box-shadow declaration
without needing to escape the cascade layers.
Sizes stay px (64px row, 40px divider); spacing goes through the
measurement tokens where one matches the value (4px leading/trailing
gap, 16/32px docked spread, 8px floating ends, 16/24px placed
margins).
Hooks renamed: every unprefixed data-toolbar* attribute to data-md-*,
updated in tests/Feature/Components/{AppBarTest,ToolbarTest}.php and
tests/Browser/BarsTest.php. toolbar.js needed no change: it reads the
bar through $root and role="toolbar", not by hook name.
Carry-over from the app-bar step: layout/pane.css now imports
app-bar.css and button.css for the pane's own top app bar and back
button, now that both are rewritten. That import crosses into
material.components for the first time from a layout file, so
tests/Feature/StylesheetsTest.php's two layout-shape checks now skip
non-`layout/` files reached through it — each already covered by its
own group's stylesheet test.
Added the owed browser tests (docs/plans/material-3-browser-tests.md):
a docked toolbar clear of the navigation bar below medium, and the
rounded form with its divider height from 840px, clear of the
window's edges.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (navigation group, first stream): <x-app-bar>'s class
lists move into resources/css/components/app-bar.css, keyed on
data-md-app-bar (with data-md-variant/-sticky/-subtitled/-scrolled/
-collapsed) and its parts data-md-app-bar-row/-leading/-headline/
-title/-subtitle/-search/-trailing/-action/-overflow/-expanded. The
overflow menu (two icon buttons below 600px, four from it, N-09's
312px search cap and N-11's three-column centred row) already matched
the audit; only its hooks, units and layer needed to change. Every
length is px, spacing through the measurement tokens where one
matches (M3's own "4dp leading/trailing space" and "16dp" headline
inset had no exact token, so those stay literal). Imports button.css,
menu.css and menu-item.css for the overflow menu the view renders.
Hooks renamed: every unprefixed data-app-bar-* attribute and
data-scrolled/data-collapsed to data-md-*, updated in
tests/Feature/Components/{AppBarTest,PaneTest}.php and
tests/Browser/{BarsTest,NavigationTest}.php. app-bar.js needed no
change: it reads the bar through $root, not by hook name.
tests/Feature/Components/NavigationStylesheetsTest.php is the
navigation twin of ContainmentStylesheetsTest.php, on the same
tests/Support/ViewClasses.php rule; its dataset starts at 'app-bar'
and a 'tabs' entry (once that commit lands) will read both
tabs.blade.php and tab.blade.php, since they share one stylesheet.
Added the owed browser tests (docs/plans/material-3-browser-tests.md):
the overflow at 599/600px, keyboard in its menu, a menu closing once
its width no longer shows it, and a wire:click action from both the
icon button and the menu item form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 browser-test run, reviewer checklist. Only a plain,
opening/closing sanity check existed for a selected row's background;
this proves the state layer mixes over the secondary-container fill
rather than in place of it, on the showcase's design-review.mp4 row —
the same guarantee list.css already has, from a real hover and focus
in the browser rather than only the Feature-level CSS declarations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 browser-test run. Five test bugs: a nested-dialog test
matched the wrong dialog by substring (the outer's textContent also
contains the inner's), a bottom-sheet test and a side-sheet test passed
Playwright-only `:has-text()`/a JS expression into APIs expecting a
CSS selector or ran page JS with `:has-text()`, a scroll-divider test
never actually scrolled (its content fit without it), and a full-screen
dialog test narrowed the viewport before an unrelated open side sheet
correctly became modal below it, leaving nothing else clickable.
The reviewer's review-checklist items without a covering test get one:
a selected segmented row keeps its fill under a real hover and focus
(pressed already has a Feature-level check); the side sheet mirrors
into a right-to-left page; a basic dialog nested inside a full-screen
one keeps its own title and padding at 400px; a dragged showcase card
shows the 16% state layer; the side and bottom sheet scrims are proven
to fade rather than vanish, sampled every few ms in the page itself
rather than after a fixed PHP-side wait (unreliable next to a 200ms
transition once round trips are counted); and the reduced-motion
dialog test now also proves a real, non-zero transition exists without
reduced motion, not only that none does with it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review. The list's header sent dividers to list.css, which
list-item.css draws since 9ff5a7a8; the carousel item's label overlay
shows the `label` prop, not the "n of m" name; the dialog's 560/280px
are its maximum and minimum width, in that order.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review. interaction.css read `data-dragged` beside
`data-md-dragged` while <x-card> still set the old name; the card now
sets the new one and nothing in the package renders the old one on an
element with the class, which 2.0.0 introduces. The Tailwind
`state-layer` utility keeps `data-dragged` until step 39 deletes it.
UPGRADE.md's card line named the old `data-card` hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of a8c2d88f. error-page.css drew a bare `body`, which
components.css already bundles into every Workbench page and step 37's
all.css would put in every application page: each would take the error
page's background, margin and system font. The rule now matches
body:has(> [data-md-error-page]); the fallback stylesheet, inlined into
the error page alone, keeps its own. A test in the containment harness
and in ErrorPagesTest rejects an unscoped html, body, dialog or :root.
The body's system font stack is gone: the old `font-sans` was the brand
typeface (tokens/theme.css), which the page inherits from the
foundation's html rule, and the header's claim otherwise was wrong. The
shape's slow turn, which moved from the view's inline style into the
stylesheet, is back in the fallback so the no-build page still turns.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of a149970d. Reduced motion writes a zero inset for
every item, so isMasked() never called one partly shown and a press on
an item the row cut off did nothing. It now reads the item's box against
the scrollport there, as it already did for multi-aspect, and the
browser test the rewrite added to document the gap asserts the reveal
instead (plan step 32's owed test).
A focused item's inset ring is 3px in again: Tailwind's
-outline-offset-3 is 3px, not the 12px the rewrite wrote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of 287340b0. The scrim had no closed opacity, so
allow-discrete held it at full strength for the length of the fade and
then dropped it, where x-transition.opacity had faded it out. The open
state now matches the root's own scrim and panel: a sheet nested in an
open one (a menu's sheet at compact inside a sheet) matched the outer
open rule, and closed in place after half a second instead of sliding.
The handle's ring stands 4px off its bar again, as the old handle's
outline-offset-4 drew it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of 9dd527f3. The old view hid a standard sheet's
scrim with `expanded:hidden`; the stylesheet lost that rule (and the
render test lost its assertion), so an open standard sheet at 840px and
up laid a full-window scrim over the page it sits beside, one click from
closing. The rule is back, pinned by a test.
The same review: the scrim now fades out as well as in (with no closed
opacity, allow-discrete only held it at 32% for 200ms and then dropped
it); the modal sheet slides without the fade the old enter and leave
classes never had; it slides from its own edge in a right-to-left
document, which translate-x-full never did; the state rules match the
root's own sheet and scrim, so a sheet nested in an open one keeps its
state; the script's breakpoint is 840px, not 52.5rem, which disagreed
with the stylesheet's px query once the reader's text size grew; and
`width` and the standard sheet's cap are M3's 400px.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of 3c235ecc. The rules that hide a full-screen
dialog's head and title below 600px, zero its subtitle's margin and
shorten its action bar matched descendants of the full-screen dialog, so
a basic confirm opened from inside its body lost its title and its
padding on a phone. They now go through > [data-md-modal-box] >, as the
divider marks already did (old dialog.css: "the child combinators tie a
mark to its own dialog's rows"), and the icon and subtitle checks look
only at the head's own children.
The computed data-md-modal-divider flag stays. It is correct (the old
view's data-dialog-head placement, one for one), and though a :has()
chain on the box could say when the bar owns the rule, the flag keeps
bar, head and actions on one hook the view already has the facts for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9