From `expanded` a standard side sheet sits in the layout beside the
content, and the view took it out of the layout the moment it closed:
`data-md-drawer-collapsed` (`display: none` on the root) followed `! open`
at once, so its fade was never seen and the content beside it took the
sheet's width, and the gap, in one jump — in every engine. M3's side
sheets: opening a standard sheet shrinks the body beside it, and closing
gives the room back.
drawer.css: the standard root is a clipping flex box whose `inline-size`
springs between none and the sheet's width, with a negative margin as
wide as its flex parent's gap (`--md-drawer-gap`), on the sheet's own
exit and entry timings; the sheet keeps its width at the root's far edge,
so it is uncovered from its inner edge while it fades. drawer.blade.php:
`settle()`, from `x-effect`, measures the gap on each change and holds
the collapse (`closing`) for the root's and the sheet's closing
durations, read a frame on; reopening lets a pending end go by. The
binding reads `collapsed`, which only `settle()` and the window's width
write: a binding reading `open` ran before `settle()` in the same flush
and, already queued, did not run again once `closing` changed.
ContainmentTest closes the showcase's sheet at 1000px and samples, in the
page, for the root part-way to none while the column beside it has grown
part of the way, then checks the collapse, the column at the row's full
width, and a reopen part-way through the exit ending fully open; it fails
on the previous code in Chrome, Firefox and Safari. OverlayTest pins the
root's declarations and the view's bindings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`fullScreen` followed `open`, so the moment a full-screen search closed,
`data-md-full-screen` went with it: the fixed header bar went back to its
resting pill — or to `display: none`, behind the search icon — and the
view, still fading out under Alpine's hold, dropped to the docked layout
for its exit. In every engine the bar vanished on the first frame while
the view went on fading somewhere else.
search.js: a close from full screen now `hold()`s the layout — `leaving`
keeps `fullScreen`, and with it `data-md-full-screen`, the back arrow and
the fixed bar, for the view's own closing duration, read from its
computed style a frame on (zero under reduced motion); reopening lets a
pending end go by. search.css fades the header bar out with the view on
the view's spring and keeps the root above the page while it leaves,
and the icon trigger's bar stays displayed until the layout settles. The
focus trap now binds to `open && fullScreen`, so it still lets go at the
close and its return of focus lands inside RETURN_GUARD_MS rather than
reopening the view at the end of the exit.
PickingTest samples both exits in the page — the icon trigger, and the
bar trigger on a compact window — for a moment where the root is still
full screen with a fixed bar and both bar and view part-way through their
fade, then checks the settled layout and focus back on the icon or the
field; both fail on the previous code in Chrome, Firefox and Safari. The
tests wait for the entry's own transitions first: Firefox reads a
transition's end value until its next refresh tick.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
collapse.css animated `<details>`' `::details-content` from `block-size: 0`
to `auto`, which needs `interpolate-size: allow-keywords` (Chrome 129 and
nothing else) and a `content-visibility` held through the close with
`allow-discrete` (not Firefox), so in Firefox and Safari the section
snapped open and shut. No stylesheet can do it there.
resources/js/collapse.js animates the `<details>` itself, the same way
in every engine: a Web Animation of its `block-size` from the height it
is drawn at to the height it is going to, on the fast spatial spring's
tokens, with `overflow: clip` and a `min-block-size` that keeps the
summary whole through the spring's overshoot, all inside the keyframes,
so nothing is left in `style`. Content below moves with it. Opening sets
`open` at once and grows the height; closing keeps `open` while the
height goes, marks `data-md-collapse-closing` so the chevron turns back
at the start, and drops `open` (and fires `toggle`) when the height has.
A press mid-way turns it round from the height it has reached. The CSS
animation is gone, so Chrome takes the same path and never animates
twice.
Routes: a press on the summary (pointer, Enter, Space) is taken over;
the Alpine and Livewire bindings call `materialCollapse()` from the
view's `x-effect` (its first call, as Alpine starts, sets the state at
once); a `name` group's open member closes on the same spring, its
`name` lifted for the close so the browser's exclusivity does not shut
it first and put back once closed. Anything else that sets `open`
(find-in-page, an application's own script) stays instant, as the
browser draws it, and is followed. Under reduced motion every route is
the browser's own.
Browser tests in Chrome, Firefox and Safari catch the height part-way
open and shut (and what is under it moving) from a press and from the
Alpine binding, the chevron turning at the start of a close, the clip
while it moves, Enter on the summary, a reversal mid-way, a name group,
and reduced motion. The four motion tests fail on main in Firefox and
Safari.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The side sheet, the bottom sheet's panel and the docked search's scrim
and view kept `display` alive through their exit with
`transition-behavior: allow-discrete`. Firefox does not transition
`display` (Chrome 117 and Safari 18 do), and `x-show` sets
`display: none` in the frame the exit starts, so in Firefox the sheets
vanished instead of sliding out and the search view and scrim vanished
instead of fading. The docked search had a second problem in every
engine: neither the view nor the scrim had a closed state to transition
to, so where `display` was held (Chrome, Safari) the view stood at full
opacity for its duration and then disappeared.
Each element now carries `x-transition:enter`/`:leave="md-transition"`,
the approach the two sheet scrims already took (renamed from
`md-scrim-transition` to one name for all of them). The class only
switches Alpine to CSS-transition mode, so `x-show` holds `display` for
the element's computed transition-duration before hiding it, in every
engine, and a reopen during the exit cancels the pending hide; nothing
styles it. `display` and `allow-discrete` leave the transitions so
Chrome and Safari do not hold a second time. Alpine reads the first
`transition-duration` listed, which is the closing slide or fade in each
list (the preset panel lists translate before height). The search view
now closes back into the bar (opacity 0, `scale: 1 0.9`, the reverse of
its `@starting-style` entry) and its scrim fades out on close and when
the search turns full screen. Under reduced motion the durations are
zero and every one of them closes at once.
Four browser tests sample each exit mid-way in the page, in the same
round trip as the close: the sheets part of the way to their closed
offset, the search scrim and view part of the way faded, each still
displayed, then `display: none`. All four fail on main in Firefox (the
two search tests in Chrome too) and pass in Chrome, Firefox and Safari.
OverlayTest pins the drawer's new transition and the view's markup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A rail that was open over the page left it on `transition: … display …
allow-discrete`: the compact adaptive rail's slide-out, the slide-out of
a rail that hides when collapsed, and every rail scrim's fade. Firefox
does not transition `display`, even with `allow-discrete` (Chrome 117
and Safari 18 do), so there the panel and the scrim vanished on the
first frame. Unlike the sheets' scrims these are drawn by attribute
rules on `data-md-open`, not `x-show`, so Alpine had nothing to hold.
navigation.js now marks a rail that closes `data-md-closing` — `sheet`
when the panel leaves the window, `scrim` when it stands in the layout
again — and drops it once the panel's `translate` and the scrim's
`opacity` transitions have finished (at once under reduced motion, or
when the rail opens again). navigation-rail.css keeps what is leaving
displayed, and a sliding panel in its open geometry, while the
attribute is set, and no longer transitions `display` anywhere, so
Chrome and Safari do not hold a second time. The view sets it from
`x-effect`, beside the `x-bind` that drops `data-md-open`, so both
attributes change in one flush: a `$watch` a microtask later let a
style read in between settle the scrim as already hidden. The collapsed
branches and `--md-navigation-rail-value` are untouched.
NavigationTest samples each exit part-way in the page (the compact
slide, the hide-when-collapsed slide, a modal rail's scrim) and checks
it ends hidden; all three fail on main in Firefox and pass in Chrome,
Firefox and Safari. They wait for the entry to finish first: Firefox
creates a transition on its next refresh tick, so straight after a
change its computed style already reads the end value. NavigationRailTest
pins the holds and that no rule transitions `display`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Menus, submenus, tooltips, rich tooltips and the FAB menu held their
exit with `transition-behavior: allow-discrete` on `display` and
`overlay`. Firefox transitions neither (MDN browser-compat-data,
`display.is_transitionable`: Chrome 117, Safari 18, Firefox none), so
every one of them vanished on its first frame there. A script cannot
hold a popover open instead: `beforetoggle` is not cancellable on the
way out, and the browser's own light dismiss (Escape, a press outside)
never asks.
resources/js/popover-exit.js: a popover marked `data-md-popover-exit`
closes for real at once — focus, aria-expanded and toggle stay the
browser's — and a copy taken in `beforetoggle`, while it is still drawn,
stands in for the exit. The copy is decoration: a manual popover in the
top layer (closing no other popover), inert, aria-hidden, without ids or
nested popovers, `x-ignore`d so Alpine starts nothing in it, pinned to
the popover's box with its resolved colours. It is shown with its
transitions off, so `@starting-style` does not replay the entry, then
marked `data-md-popover-closing`, which each stylesheet turns into its
closed values (`:popover-open:not([data-md-popover-closing])`, and the
FAB menu's items' sink), so it moves on the component's own tokens. It
is removed once the longest of them has run, and opening the popover
again takes it away. Under reduced motion every duration is zero and no
copy is made. `display`, `overlay` and `allow-discrete` leave the
transitions, so Chrome and Safari take the same path.
Browser tests in Chrome, Firefox and Safari slow the motion tokens so a
round trip still finds the exit on screen: a menu after Escape and after
a press outside (the real menu closed and focus back on its button, the
copy inert, fading, with no Alpine state, and gone after), a reopen
part-way through, reduced motion, a submenu while its menu stays open, a
tooltip, the FAB menu's items part-way down their sink, and a persistent
rich tooltip.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With nothing focusable inside, showModal() focuses the <dialog> itself.
Chrome's scroll containers are keyboard-focusable, so there the scrolling
body became the focus delegate and modal.css drew its inset ring; Firefox
and WebKit focused the dialog, where the arrow keys scroll nothing, and
WebKit never lets Tab reach a scroll container, so a keyboard could not
read a long text-only dialog there at all.
<x-modal> now opens through materialShowModal() (resources/js/dialog.js):
showModal(), and when the dialog took the focus itself and its body
overflows, the body gets tabindex="0" and the focus, as in Chrome. The
tabindex goes when the dialog closes. The body is wire:ignore.self, like
the dialog: a Livewire render would otherwise morph the tabindex away and
WebKit dropped the focus out of the dialog. ContainmentTest now also
checks the focus stays on the body through a render.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps found rewriting the Boost skill for 2.0's plain CSS, each
closed the way M3 describes it.
`<x-loading size="96">`: M3 gives the loading indicator a responsive
size, 48dp by default and flexible from 24 to 240dp ("never exceed that
range"), with the container and the active shape in a fixed ratio. The
size had no prop, so an application wrote a width and height of its own;
`size` now takes a whole number of px in that range, written as
`--md-loading-size`, and the SVG keeps the 48:38 ratio as it scales. A
value outside the range is ignored, as `<x-icon>` and `<x-shape>` ignore
theirs, and an application's own width and height still win.
`--md-navigation-rail-value`: M3 Expressive's rail has two values,
collapsed and expanded (Compose's WideNavigationRailValue), and content
in a rail follows it. Without a hook an application copied the rail's
seven conditions — mode, `data-rail`, `data-rail-auto`, open, and the
window band — out of navigation-rail.css. The rail now publishes the
answer from the same branches that narrow it: `expanded` by default,
`collapsed` wherever it is drawn collapsed, so a style query in the
application's CSS switches at the first paint and at the same moment
as the rail's own items. A rail open over a scrim reads `expanded`, and
outside a rail the property is unset. A JS attribute would have missed
the first paint; a width container query would have lagged the collapse
animation.
Style queries on a custom property need Firefox 151, so the browser
floor moves from Firefox 147 to 151 (README, the CI note, UPGRADE's new
2.1.0 section); Chrome 125 and Safari 18.4 are unchanged.
Browser tests pin both in Chrome, Firefox and Safari: the indicator's
drawn box at 96 and 32px, and the rail's value — with a style query
acting on it — at the first paint for fixed modes, across the window
classes and the menu button for an adaptive rail, and open and closed
for a modal one. NavigationRailTest pins the value in each of the five
collapsed branches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ItemSegmentedContainerColor get() = ColorSchemeKeyTokens.Surface`
(ListTokens.kt, C-21) was taken literally, but foundation/base.css
paints the page `surface` too — so a segmented list was invisible on any
default page. The token reads against a Compose scaffold, not against
this package's own page.
The tile is `surface-container` now, the step M3 names for a container
that has to read against the page, and what 1.x drew. Found in
SealShare: the files chosen for an upload list under the drop zone with
nothing to set them apart from it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
M3's colour list for a standard toolbar ends "Standard button
(Primary)" and the vibrant list ends "Standard button (On primary
container)" (N-13) — both name the *standard* button. The two rules
painted every icon button in the toolbar instead, so a button that
brings its own container got a primary label on a primary fill and its
icon disappeared: SealShare's floating navigation marks the current page
with a filled button, and that button came out a blank circle.
A button that fills its own container is now left alone — filled, tonal,
or either of them selected — and keeps the label colour that goes with
the fill. A disabled button still falls back to button.css's disabled
ink in a toolbar of either colour, as before. AppBarTest pins the
exclusion on both rules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plan step 46, M3 § Tabs: "Labels: single row by default; may wrap to a
max second line if needed with truncation, or use scrollable tabs to
give longer titles more room." In fixed tabs (<x-tabs>, <x-section-nav>
up to four sections) the label's text, now its own data-md-tab-text in
both views, wraps and truncates at two lines. The tab's 48px (64px
stacked) is a minimum, the bar stretches its tabs to one height, and the
content stretches with them, so the indicator stays on the divider.
Scrollable tabs keep one row. SealShare's "Two-Factor Auth" and
"Appearance" showed an ellipsis at 600px.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
<x-card> always titled itself with an <h3>, so a card straight under a
page's <h1> skipped a level, which SealShare's download page did with
no way round it from the application. `heading` takes h2 to h6, or div
or p for a title that is not a heading, as <x-pane> and <x-app-bar>
already do; h3 stays the default. The skill also says a card holds one
subject, as M3 does (plan step 46).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
<x-stack> stretches its children across by default and <x-form> is a
one-track grid, so a button written directly in either drew as a
full-width pill. M3 keeps a button's width "dynamic to fit label" and
says not to "stretch buttons into long flat shapes on large windows".
A button there now sits at the start edge at its own width; a stack
aligned start, centre or end is untouched, and an application's own
width still wins (a cap on the button itself was rejected for exactly
that: it would have limited an app's inline-size, which CascadeTest
guards). Chromium test in both directions, failing without the rules.
Decided with the user (plan step 46).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SealShare's only navigation is a floating toolbar at place="bottom". In
1.x it set --material-bottom-bar to lift the snackbar over it; in 2.0.0
the toolbar reads that variable to place itself, so an application can
no longer set it, and the snackbar landed on the toolbar. M3 nudges a
snackbar "upward to avoid overlapping FABs/docked toolbars" and never
puts one in front of navigation.
A bottom-placed toolbar now publishes --material-bottom-toolbar, the
distance from the window's bottom edge to its top: floating 64px (as
tall as an md or lg FAB beside it), docked 64px plus the safe area it
pads, rounded docked floating from 840px. It is declared on :root and
again on the scaffold's root, the only place its formula sees the bar.
The snackbar clears whichever of the bar and the toolbar reaches higher,
and a page pads its end with it. Two Chromium tests, both failing on the
old formula: a docked toolbar on a scaffold's navigation bar, and a
floating toolbar without a scaffold (plan step 46).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Action/Input/Containment/NavigationStylesheetsTest.php each carried the same
"imports the stylesheet of every component its view renders" check on their
own group's dataset. StylesheetsTest.php now runs it once, over every
resources/views/components/*.blade.php backed by a stylesheet of its own
(icon and shape included, no longer needing a group to run in at all;
tabs/tab's shared stylesheet, N-16, still handled) — a new component gets
the check by existing, not by being added to the right group's array too.
Each group file keeps its own dataset for the checks that stay group-shaped
(shape, tokens, breakpoints, the block it imports from).
Plan step 42 (Phase F), Part A.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
StylesheetsTest.php's new test runs DesignGuard's own family table (check
(i)) over resources/views, resources/js, src, tests/Browser and
tests/Feature — the guard turned on the package's own source, so the two
never drift apart. tests/Feature/DesignGuardTest.php and
tests/Fixtures/design-guard/ carry Tailwind on purpose and are left out.
The scan found Tailwind-shaped class strings used as caller classes in 21
test files (a component test proving a caller's class lands on the root or
a caller's size wins, mostly), left over from before Tailwind cleared the
whole stack: replaced with neutral application-style names (`app-hero-icon`)
or, where one already carries the right meaning, an `md-*` class
(`md-text-end`, `md-ink-warning`). BreakpointsTest.php's own heredoc probe —
which needs a genuine Tailwind-shaped breakpoint prefix to prove its
detection works — now builds that one string from a placeholder at runtime,
so its own source stays clean for this same scan.
Plan step 42 (Phase F), Part A.
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: 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 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: 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
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
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 (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 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
Plan step 36 review of 76abdd62. The old view put the filled variant's
16px padding on its own summary and body; the stylesheet matched them as
descendants of any filled collapse, so a plain collapse nested inside
one took the padding too (and an open one turned every nested chevron).
Child combinators now tie each rule to its own <details>. The 12px gap is
a literal like alert.css's, not an average of two spacing tokens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of f04e31b3. The card rendered md-state-layer only
on a row, so a card the application marks data-md-dragged without being
a row, as the showcase's "A card being dragged" is, lost the 16% dragged
layer its old inset shadow drew and its header still promises. Every
card now renders the class, since the attribute arrives from script
after render, and card.css keeps the layer off a card that is neither a
row nor dragged. The divider under the header is matched as its child.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 review of 9ff5a7a8. The rewrite dropped the unlayered
selected rule into material.components beside the segmented fill and the
row tint, so on equal or greater weight a selected item in a segmented
list drew surface instead of secondary-container, and hovering, pressing
or focusing any selected row (or a table's selected row) swapped its
fill for a translucent tint. Each fill now names itself
--md-list-row-fill, and the row's layer is its ink mixed over that fill
at the state tokens, which is also C-08's second suggested fix.
A disabled item inks the whole row at the disabled-content token, as the
old root colour did, so the check, the leading slot and the end slot dim
with the text; the three-line padding is a literal 12px like alert.css;
list-rows.js named list.css.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (containment group): <x-carousel>'s and <x-carousel-item>'s
class lists move into resources/css/components/carousel.css and
carousel-item.css, keyed on data-md-carousel (its value is the layout)
and the parts' data-md-carousel-* hooks (-probe, -scroller, -controls
with "auto"/"always", -previous/-next, -item, -surface, -content,
-label/-label-text). Every selector uses a `>` combinator rather than a
bare descendant one, because a carousel item can itself hold a nested
carousel whose own root would otherwise match its parent's layout
rules too (list.css already solves the same problem for segmented
list rows).
Behaviour is unchanged: resources/js/carousel.js (the keyline maths,
C-05's reduced-motion fix, C-11's vertical full-screen layout, C-12's
padding, C-18's item-as-tab-stop, the multi-aspect layout) is touched
only where it reads or writes the renamed hooks and dataset
properties; every inline custom property it writes
(--material-carousel-*) is untouched. The item renders the shared
md-focus-ring class (foundation/interaction.css) instead of a
hand-rolled ring, refined to an inset offset since an outward one
would draw under the neighbouring item. The previous/next buttons
mirror whole in RTL from carousel.css rather than through
<x-icon mirror-rtl>, which <x-button icon> has no prop to reach (a
component outside this batch); the technique matches how the
Tailwind-era markup already mirrored the whole button. The overlay
label's literal white ink over the scrim (C-25) is kept, with the
same reasoning as before.
Hooks renamed data-material-carousel(-item/-surface/-content/-label)
-> data-md-carousel(-item/-surface/-content/-label), data-padding(-end)
-> data-md-padding(-end), data-centered -> data-md-centered, updated in
the same commit: resources/js/carousel.js, tests/Feature/Components/
CarouselTest.php (rewritten on data-md-* and ComponentStylesheet) and
tests/Browser/CarouselTest.php. Browser tests owed by
docs/plans/material-3-browser-tests.md, added but not run: the
multi-aspect carousel's previous/next, arrow keys, Home and End
(scoped by data-md-carousel="multi-aspect" rather than a position in
the showcase, so reordering its examples cannot silently mis-target
the wrong carousel); a reduced-motion click on an item cut off only by
the row's own edge, which documents rather than fixes a real gap —
isMasked()'s inset check is always false once C-05 zeroes every
item's inset, so the click-to-reveal affordance does not fire there
(found by the Chromium baseline, step 32; fixing it is outside a hook
rename).
Imported from the Containment block of components.css.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 36 (containment group): <x-bottom-sheet>'s class lists move
into resources/css/components/bottom-sheet.css, keyed on
data-md-bottom-sheet (data-md-open, data-md-standard) and
data-md-bottom-sheet-scrim/-probe/-panel (data-md-preset while
heights/snap gives it stops)/-handle/-grip/-announce/-body/-title/
-actions. x-transition's enter/leave classes are replaced the same way
as drawer.css: @starting-style + transition-behavior: allow-discrete
on the x-show-driven panel, the asymmetric open (spatial-default,
emphasized-decelerate) vs close (effects-default, emphasized-
accelerate) timing coming from the more specific open-state selector's
own transition-duration/-timing-function, which a CSS transition
always takes from the after-change style. data-md-preset adds the
height-between-stops transition (the spatial spring) alongside it,
independent of the open/close slide.
The drag handle (C-01, already fixed in the Tailwind-era markup this
replaces) renders the shared md-focus-ring and md-touch-target classes
instead of a hand-rolled ring: unlike the datepicker's day, its own
visible bar is not a smaller indicator drawn inside a bigger box those
classes cannot reach, so nothing here refines them further. Its 22px
vertical padding (SheetDefaults.kt's DragHandleVerticalPadding) is a
literal, matching alert.css's 12px for the same reason: 22 is not on
the measurement scale.
Hooks renamed data-drag-handle -> data-md-bottom-sheet-handle,
updated in resources/js/bottom-sheet.js and
tests/Browser/ContainmentTest.php; data-sheet -> data-md-drawer in
that test's comment. <x-menu sheet-at-compact> renders this component
unchanged; menu.css now imports bottom-sheet.css, and
tests/Feature/Components/MenuTest.php's stale Tailwind-class assertion
for the embedded sheet is replaced with a hook check. The two
sheet-at-compact browser tests in ActionsTest.php stay on
assertVisible(): the sheet is still x-show-driven (a deliberate choice
for this batch, keeping the existing modal/side-sheet/bottom-sheet
architecture and converting only their styling), so open still shows
as visibility rather than :popover-open; their comments no longer say
"not rewritten in this batch".
Imported from the Containment block of components.css. Browser tests
owed by docs/plans/material-3-browser-tests.md, added to
ContainmentTest.php but not run: dialog divider marks on scroll
(including that a nested dialog keeps its own marks), the full-screen
phone bar, separator always, no fade under reduced motion, the
scrolling body's inset focus ring; bottom sheet preset-height cycling
with its announcement and a drag settling on the nearest stop; the
standard side sheet from 840px without a scrim or a focus trap, modal
below it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9