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 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 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 (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
Plan step 36 (containment group): <x-card>'s class lists move into
resources/css/components/card.css, keyed on data-md-card (per variant),
data-md-card-figure/-body/-header/-heading/-title/-subtitle/-menu/
-content/-actions. A row (data-md-list-row) renders the shared
md-state-layer and md-focus-ring classes for its tint and ring
(foundation/interaction.css) instead of copying their rules; card.css
adds only the per-state elevation the shared class has no opinion on
(ElevatedCardTokens.kt/FilledCardTokens.kt/OutlinedCardTokens.kt), the
press/hover exclusion for a card's own nested buttons, and the ring for
a non-actionable row whose focus lands on its opener rather than the
card. data-md-dragged is read automatically by the shared class's own
16% tint; card.css only adds its elevation levels 3/4.
Hooks renamed data-list-row -> data-md-list-row, data-list-open ->
data-md-list-open, data-list-actionable -> data-md-list-actionable,
data-dragged -> data-md-dragged, updated in the same commit:
resources/js/search.js and list-rows.js's consumers, table.css's
comment, the showcase (index, data and containment sections),
tests/Browser/ContainmentTest.php, and the development skill's card
and table examples.
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 32. Phase E put a standard side sheet (containment.md, step 23)
above the modal one on the showcase page, so the first `[data-sheet]` scrim
the test pressed belonged to it; the test now presses the modal sheet's
own scrim. The preset-heights bottom sheet's button made "Bottom sheet"
match two buttons under Playwright's strict locators; it is matched by its
exact label. The actionable card test chained an assertion on script(),
which returns the script's value rather than the page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 19, containment.md C-13, C-23 and the directly actionable card
of C-13/C-18. A card had one elevation and never changed it, and its
hover replaced the elevated card's shadow with the state layer; it now
names its variant in `data-card` and moves a step on hover (elevated
1 to 2, filled and outlined 0 to 1) with the layer over it. The 12 to 16
corner morph on hover is gone: M3 gives a card one shape and lists shape
morph for buttons, FABs and list items only. New `actionable` prop for
M3's directly actionable card — the card is the one tab stop, with
`role="button"` (or `link`), its `title` as the name, and Enter or Space
reaching the opener from resources/js/list-rows.js.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
An application keeps a disclosure's state in a Livewire property
(`<x-collapse wire:model="fineTuning">`) or an Alpine one. Either binding
now works both ways: toggling the details writes the property, and the
property changing, in an action or in Alpine, opens or closes it.
`wire:model` is entangled as the dialog and sheets entangle theirs, and
taken off the element. The server renders `open` from the property, so
the first paint matches it. `x-model` binds through `x-modelable`, with
`open` as the first paint until Alpine starts. The state is named
`collapseOpen` so it never hides an `open` that a dialog in the slot reads
from a scope around it.
Without a binding the element has no Alpine and renders as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
The showcase is now an overview and one page per section behind a
grouped navigation rail, moved between with wire:navigate, instead of
one long page under a scrolling top bar. The switch exposed a WebKit
bug in the menu: inside a focusable region, WebKit moves focus out of
the closing popover before its toggle event, so Escape no longer
returned focus to the trigger. The menu now reads it on beforetoggle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
<x-card> (filled, elevated, outlined), <x-list> and <x-list-item>
(plain or M3 Expressive's segmented list), <x-divider>, <x-collapse> on
<details>, <x-modal> on the native <dialog>, <x-drawer> as a side sheet or
list-detail pane, and <x-bottom-sheet> with drag to dismiss. Dialogs and
sheets bind to a Livewire flag or id and write back false or null on close,
or use the surrounding Alpine scope. Rows open from anywhere on them through
data-list-row and data-list-open. DesignGuard now also reports Blade
directives written inside a component tag, where they do not compile.
Browser test helpers wait for a complete document with Alpine and Livewire
running: in Firefox, networkidle alone could return before a repeated visit
had loaded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy