`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>
Found while doing plan step 46: withoutFontFace() searched the raw
bundle, so the `@font-face` that foundation/base.css's and
error-page.css's header comments mention read as the rule. Each cut from
inside its comment to the end of the next block and left the comment
open, so the no-build fallback lost base.css's html rule and icon.css's
rules. The search and the brace count now read Stylesheets::mask(), and
only tokens/font.css's real block leaves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plan step 46, from SealShare's breakpoint walk: an application imports
the stylesheets its own views render, and none renders the error layout,
so under a real build its 403/404/500 pages drew unstyled. With a build
the page now keeps the app's Vite tags (foundation, tokens, scheme, font)
and inlines Stylesheets::bundle() of components/error-page.css and its
imports beside them (ErrorPage::layoutStyles(), cached). Without a build
the fallback is unchanged. An application imports nothing for its error
pages; one that imports all.css gets the same layered rules twice, which
is harmless.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
missingStylesheets() names an import the views need, but nothing named
one they had stopped needing: when SealShare's last card left its views,
card.css stayed in every page until a reviewer noticed by hand.
unusedStylesheets($cssEntry) reads the entry's direct package imports
and reports each one no scanned view needs, counting what a needed
stylesheet imports itself and foundation.css always; an entry that
imports all.css is left alone. Opt-in, beside missingStylesheets()
(plan step 46).
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>
A transition-duration or -timing-function listing one token per property
(a spatial spring for scale, an effects spring for opacity) was reported
as literal: blanking each var() left a bare comma, which is not one of
the safe values. Every item of a list is now checked on its own. A
message also quoted the blanked shell, `var( ), 200ms`; it now
quotes the declaration as written. SealShare's drop zone found it
(plan step 46).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both applications show values a person reads or types character by
character: SealShare's share tokens and recovery codes, ReStride's file
and folder names and its keyboard shortcuts. The package wrote the
platform's monospace stack twice (the reset's <code>, <kbd>, <samp> and
the mono field) and gave everything else nothing, so an app copied the
stack by hand. --md-ref-typeface-mono now holds it, read by the reset,
the field and a new md-mono text class; the guard points font-mono at
<code> or md-mono. Not an M3 token: M3 names a brand and a plain
typeface and nothing for code. Decided with the user (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>
The showcase and the error pages set grayscale font smoothing on
themselves, but the foundation did not, so an application that dropped
Tailwind's antialiased class rendered heavier text on macOS than the
package's own pages, silently. base.css now sets it on the page; the
showcase's and error page's copies go, and the guard tells a leftover
antialiased class to go too. Decided with the user (plan step 46).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guard said a directive does not compile inside a component tag but
not what to write instead, and the skill's note said only "use {{ }}".
SealShare's two-factor challenge had @js in an x-data on <x-stack>, and
its implementer had to work out {{ Js::from() }} from Laravel's source.
Each message now names the form: Js::from for @js, json_encode for
@json, Arr::toCssClasses and toCssStyles for @class and @style,
$wire.entangle for @entangle, a bound attribute for @disabled and its
kin, and a prop, an echo or a plain element for control structures
(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>
One probe page per claim: an application's unlayered .app-wide rule beats
button.css's layered inline-size and padding; [hidden] beats a component's
display on both <x-row> and <x-button>; the layer order holds when
Stylesheets::bundle() serves button.css before foundation.css, so the
reset's padding: 0 still loses to the button's own; a data-md-variant
attribute updates correctly through the same re-render/morph a plain
wire:click="$refresh" drives, toggled both ways; <x-icon mirror-rtl> mirrors
under dir="rtl" and does not under dir="ltr". Chromium: 6 assertions, all
green, alongside ShowcaseTest.php (5 tests, 18 assertions).
Plan step 42 (Phase F), Part A.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
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
Measured the way the showcase and the error page's fallback actually serve
it — Stylesheets::bundle() in PHP, then PHP's own gzencode() — so CI holds
the line with no Node build. Currently 106,838 bytes gzipped; the budget is
120,000, about 10% of headroom for organic growth before the failure
message points a maintainer at what grew.
Plan step 42 (Phase F), Part A.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Audits step 42's "every component view has a stylesheet" against the whole
of resources/views/components/: everything already has one, apart from two
that need none, now documented in the test itself — theme-script renders an
inline <script> only, and tab is drawn by tabs.css, its parent <x-tabs>'s
own stylesheet.
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
Check (iii) flagged two shapes the package's own stylesheets (and their
stylesheet tests) already treat as fine: `linear`, M3's own easing for
continuous motion (an indeterminate progress indicator's rotation), and a
box-shadow that is only an inset or outline-style ring — `[inset] 0 0 0
<n>px` in a `var(--md-sys-color-*)` role, or one `color-mix()`-dimmed for a
disabled ring — the shape a focused field's edge or a day's "current"
outline draws. A blurred shadow or one with a literal colour is still
reported.
Plan step 42 (Phase F), Part B.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
outsideTheScale() read ease-in/ease-out/ease-in-out/duration-* line by line
across every file, the way it still reads its other scale steps — but a
plain CSS keyword read out of an API into a JS string (matchMedia(…) ?
'linear' : 'ease-out') names a real value, not a Tailwind class, and the
same line scan read the words back out of a stylesheet test's own regex
literals (`ease-in`, `ease-out`) as if they were Tailwind too. Tested both
ways per the plan's instruction: Blade/PHP class lists have the identical
false-positive shape, so the two families move to FAMILIES, matched only
through a class list (literalClasses()/tailwindFamilyHint()) as the newer
families already are, in Blade and PHP as much as in JavaScript.
Plan step 42 (Phase F), Part B.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Different checks land violations for the same file in their own pass order
(colours before shadows before media queries, an application's CSS always
appended after its views) rather than the order a maintainer would read the
file in. DesignGuard::violations() now sorts everything it returns
deterministically before handing it back, and a fixture proves it crosses
both dimensions: a later-processed application-CSS file whose path sorts
first, and two different checks landing on the same line in the opposite
order from how they run.
Plan step 42 (Phase F), Part B.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review: "colour the theme does not declare `text-red-500`"
was the one family (i) finding without a 2.0.0 hint; it now says the
utility compiles to nothing and points at md-ink-* or var(--md-sys-color-*).
The foundation-only fixture view also carries an <x-dynamic-component> and
an <x-slot:title>, which the stylesheet check must neither report nor trip on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review: the committed smoke test scanned the package's views
without the stylesheet check, so the claim that the package, showcase and
Workbench pass it through all.css was untested; it now runs it, and
checks the showcase's plain tags do need imports from a foundation-only
entry, so the pass is not vacuous. Doing so exposed that a component class
one test evals leaked into every later check in the process: the shadow
check now guesses the class the way Blade does (Blade::component()
aliases, component namespaces, the application's own root namespace), and
the test gives its class a namespace of its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review, check (iii): calc() hid every literal inside it and
now only one built on a var() does (min(), max() and clamp() too), so
calc(8px + 4px) and clamp(1rem, 2vw, 2rem) are reported. Declarations are
read whole, so a last one without a semicolon is seen and a custom
property named --card-border-radius is not mistaken for border-radius;
the font shorthand is checked; a 0s duration is fine. Media queries read
only width features: a height or prefers-* condition is not a breakpoint,
and (600px <= width < 840px) passes. The type hints no longer suggest
font-size: var(--md-sys-typescale-*), which is a font shorthand value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review: an application's CSS entry may import what a Vite
build resolves but Stylesheets::bundle() refuses (tailwindcss mid-
migration, a font URL, a layer() import); resolvedFiles() threw on each,
failing the application's whole design test. It now follows relative
imports only and skips the rest. Each missing stylesheet, shadowed tag
and ->links() is reported once, at its first use, instead of at every
occurrence; a CSS entry that does not exist says so; the @import line goes
through vendor/ when Composer symlinks the package.
missingStylesheets() also switched on the literal-value check for the
entry's whole import graph, vendor stylesheets included, with no way to
check imports alone. Check (iii) now reads the .css files scan() is given,
like every other check; the entry's imports still feed the class
exemptions, now escaped selectors (.hover\:underline) included.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review: run over SealShare's and ReStride's views, family (i)
missed about a quarter of the class tokens, most of them 1.x's own
utilities (type-body-md, text-meta, rounded-corner-lg, focus-ring, link)
and common Tailwind families (relative, inset-0, z-10, overflow-hidden,
border, opacity-50, shrink-0, -mx-4, cursor-pointer, font-mono). Each now
has a hint, and the ones with a layout component name its prop: gap-4 is
gap="space200" (16px), a step between two of M3's names both, items-center
align="center", grid-cols-3 :columns="3"; variants say which selector or
media query replaces them. Shapes are tightened to real Tailwind values,
so an application class such as content-header or p-card is not matched.
Class lists are also read from wire:*.class, x-transition:*, unquoted
:class keys, double-quoted PHP strings, 'class' => '…' and plain PHP
files; a string compared in a condition (view === 'grid') is skipped,
and each token carries its own line in a multi-line list.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review (the user, 2026-09-15): an application without Tailwind
names a role through its --md-sys-color-* custom property (CSS, an inline
style, a script), an md-ink-* class, or a component's color/tone prop, not
a bg-tertiary utility. forbidColours() keeps its name and signature and
reports each of those, the role's on- and container roles included;
a Tailwind leftover stays family (i)'s single report. ReStride's
forbidColours(['tertiary', 'primary-container']) keeps its meaning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review (the user, 2026-09-15): without Tailwind, bg-white and
text-on-surface/60 compile to nothing like any other utility, so family (i)
now reports every colour utility on an M3 role, a 1.x ink name
(text-meta, border-divider), white, black or current, with or without an
opacity, each with its 2.0.0 replacement (an md-ink-* class, <x-divider>,
<x-surface level>, md-state-layer or the role's var()). Check (iii) reads
named colours anywhere in a declaration's value (border: 1px solid white),
hex codes only in values (an id like #add no longer matches), and masks
strings and url() contents first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
Plan step 41 review (the user, 2026-09-15): SealShare and ReStride have
both left maryUI and daisyUI, and no application of 2.0.0 carries either,
so the <x-mary-*> tag check, the daisyUI class and colour lists and the
Tailwind look-alike exclusions they needed go, with their fixture lines
and the mentions in the header, README and development skill.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
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
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 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