Take Tailwind out of the package, and its detection out of the guard
tests / feature (8.4) (push) Successful in 2m0s
tests / feature (8.5) (push) Successful in 2m0s
tests / browser (chrome, chromium) (push) Failing after 8m3s
tests / browser (firefox, firefox) (push) Failing after 12m58s
tests / browser (safari, webkit) (push) Failing after 13m8s

Tailwind left the stack in 2.0.0, but the package still carried about 330
mentions of it. What the guard's Tailwind detection protected — a class
that compiles to nothing — is now protected by a check that does not care
where a dead class came from.

DesignGuard: about 500 lines of Tailwind tables, scales, palettes and
"2.0.0 replacement" hints give way to one check — a class a view or PHP
file writes that neither the application's stylesheets nor the package's
own declare. It catches a utility of any framework, a typo and a class
whose rules were deleted alike, so it also found two classes ReStride
draws nothing with. A stylesheet has to be in reach for it: the `.css`
files among the scanned paths, or what the `missingStylesheets()` entry
imports. The class reader no longer mistakes an array index for a class
list (`$block['base']`), and it reads the array a class helper is given,
where it read nothing before.

The package's own three Tailwind self-guards go with it. Only their one
unique check stays, as a test of its own: every `matchMedia` width in
resources/js is an M3 breakpoint.

The pagination views are `material.blade.php` and
`simple-material.blade.php`; only Laravel's and Livewire's default theme
names ever made them `tailwind`. The provider sets `Paginator`'s default
views and switches `livewire.pagination_theme` to `material` when it is
still Livewire's own default, so no application can forget the config; a
theme an application chose, and a component's own `$paginationTheme` or
`paginationView()`, still win.

The rest is prose: the layer-order guidance for an application that still
builds Tailwind, the Tailwind wording in the README, the Boost guidelines
and the development skill, and about 25 "this used to be a Tailwind
utility" comments, along with every "plan step NN" pointer into a
gitignored folder. The reset keeps its credit, and NOTICE now carries it
too.

Feature suite 1159 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 21:07:39 +02:00
co-authored by Claude Opus 5
parent 247c596c3a
commit fb7007c976
88 changed files with 649 additions and 1521 deletions
@@ -1,3 +0,0 @@
<div class="sm:flex md:grid lg:hidden xl:block 2xl:contents">
<span class="max-sm:hidden max-md:flex max-lg:grid max-xl:block max-2xl:contents"></span>
</div>
@@ -1,6 +0,0 @@
<div class="bg-[#1d7afc] text-[rgb(0_0_0)] border-[hsl(210_80%_50%)]">
<span class="fill-[oklch(0.7_0.1_250)] ring-[color-mix(in_oklab,var(--x)_50%,transparent)]"></span>
<span class="bg-white text-black border-white"></span>
<span class="text-on-surface/60 bg-on-surface/12 border-outline/38"></span>
<span class="md-ink md-type-body-md">M3's own are fine</span>
</div>
@@ -5,3 +5,7 @@
.bg-brand {
background: var(--brand);
}
.dashboard-hero {
padding: var(--md-sys-measurement-space200);
}
@@ -0,0 +1,8 @@
<div x-data="{ view: 'grid' }" :class="{ 'is-open': open, collapsed: !open, 'md-ink': view === 'grid' }" wire:loading.class="dashboard-busy"
x-transition:enter="dashboard-enter" x-transition:enter-start="dashboard-enter-start">
<ul @class(['dashboard-hero', 'share-list', 'md-ink-quiet' => $status === 'hidden', 'md-ink' => isActive('block')])
{{ $attributes->merge(['class' => 'dashboard-lead']) }}>
<li class="md-truncate
share-list">Row</li>
</ul>
</div>
@@ -0,0 +1,10 @@
{{-- Real application lines: a quoted string that is an array index ($block['base'],
$header['class'], $tab['iconClass']) reads a value, not a class list. The keys around it and
both branches of a ternary are classes, and are read. --}}
<div @class(['product-shot-base' => $block['base']])></div>
<x-sort-header :sort-by="$sortBy" :column="$header['key']" :class="$header['class'] ?? null" />
<x-icon :name="$tab['icon']" size="20" :class="$tab['iconClass']" />
<x-row :class="$band['easy'] ? 'md-type-title-sm' : 'md-type-body-md'" />
<div @class(['routes-show-run-chosen' => $referenceRunId === $run['id']])></div>
<x-badge :class="\Illuminate\Support\Arr::toCssClasses(['shot-helper-one', 'shot-helper-two' => $on])" />
<span :class="$on ? 'shot-tern-a' : ($off ? 'shot-tern-b' : 'shot-tern-c')"></span>
@@ -0,0 +1 @@
<p class="md-type-title-md md-ink-variant showcase-w-narrow">Every one of these is a package rule.</p>
@@ -1,4 +0,0 @@
{{-- Running text is not a class list: none of Tailwind's bare-word utilities should be read out
of prose just because they share a spelling with one. --}}
<p>This creates a grid of cards, in a flex layout that stays hidden below medium and stacks into a
block once the pane narrows. Nothing here truncates or grows, and the sample is not a table.</p>
@@ -1,5 +0,0 @@
<div class="rounded-none rounded-xs rounded-sm rounded-md rounded-lg">
<span class="rounded-xl rounded-2xl rounded-3xl rounded-4xl rounded-full"></span>
<span class="rounded-t-lg rounded-se-2xl"></span>
<span class="md-type-body-md" style="border-radius: var(--md-sys-shape-corner-lg)">M3's own are fine</span>
</div>
@@ -1,4 +0,0 @@
<div class="shadow-2xs shadow-xs shadow-sm shadow-md">
<span class="shadow-lg shadow-xl shadow-2xl"></span>
<span class="md-type-body-md" style="box-shadow: var(--md-sys-elevation-1)">M3's own are fine</span>
</div>
@@ -1,4 +0,0 @@
<div class="ease-linear ease-in ease-out ease-in-out">
<span class="duration-75 duration-300 duration-1000"></span>
<span class="md-type-body-md">M3's own are fine</span>
</div>
@@ -1,4 +0,0 @@
// A plain CSS keyword read out of an API, not a Tailwind class: matches DesignGuard's easing and
// duration families only in a class list, never a bare JS string like this one.
const easing = matchMedia('(prefers-reduced-motion: reduce)').matches ? 'linear' : 'ease-out';
const duration = matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 300;
@@ -1,7 +0,0 @@
<div class="text-xs text-sm text-base text-lg text-xl text-2xl text-9xl">
<span class="font-thin font-extralight font-light font-normal font-medium"></span>
<span class="font-semibold font-bold font-extrabold font-black"></span>
<span class="leading-none leading-tight leading-snug leading-normal leading-relaxed leading-loose leading-6"></span>
<span class="tracking-tighter tracking-tight tracking-normal tracking-wide tracking-wider tracking-widest"></span>
<span class="md-type-body-md md-type-title-lg md-type-emphasized-label-lg">M3's own are fine</span>
</div>
@@ -1,4 +1,4 @@
@import 'tailwindcss';
@import '@fontsource/roboto';
@import url('https://fonts.googleapis.com/css2?family=Roboto');
@import './app-theme.css' layer(app);
@import '../../../../resources/css/foundation.css';
@@ -1,4 +0,0 @@
<div class="top-[10px] grid-cols-[1fr_2fr]">
<span class="flex!"></span>
</div>
<div class="[--material-bottom-bar:5rem] [font-variation-settings:'ROND'_100]"></div>
@@ -1,8 +0,0 @@
<div x-data="{ view: 'grid' }" :class="{ 'is-open': open, hidden: !open, 'md-ink': view === 'grid' }" wire:loading.class="opacity-50"
x-transition:enter="transition ease-out" x-transition:enter-start="scale-95">
<ul @class(["gap-2", 'share-list', 'md-ink-quiet' => $status === 'hidden', 'md-ink' => isActive('block')])
{{ $attributes->merge(['class' => 'mt-2']) }}>
<li class="md-truncate
flex">Row</li>
</ul>
</div>
@@ -1 +0,0 @@
<span class="duration-(--md-sys-motion-spatial-fast-duration)"></span>
@@ -1,3 +0,0 @@
<div class="hidden block">
<span class="inline-block invisible"></span>
</div>
@@ -1 +0,0 @@
<span class="text-on-primary-container bg-background text-current md-ink-variant"></span>
@@ -1,3 +0,0 @@
<div class="flex grid inline-flex flex-col">
<span class="grid-cols-3 col-span-2 items-center justify-between gap-4"></span>
</div>
@@ -1,2 +0,0 @@
<div class="text-on-surface-variant border-outline-variant"></div>
<span class="bg-surface-container bg-primary"></span>
@@ -1,3 +0,0 @@
<div class="w-full h-screen min-w-0 max-w-md">
<span class="size-4"></span>
</div>
@@ -1,3 +0,0 @@
<div class="p-4 px-2 mt-2 space-y-4">
<span class="m-4 space-x-2"></span>
</div>
@@ -1,3 +0,0 @@
<p class="text-center truncate">
<span class="sr-only whitespace-nowrap line-clamp-2 tabular-nums"></span>
</p>
@@ -1,8 +0,0 @@
<div class="bg-off-plan text-sport-run from-brand to-brand-end text-brand-ink">
<span class="border-route-reference bg-route-reference/8 border-l-zone-4 ring-offset-brand/[0.4]"></span>
<p class="text-balance text-pretty text-wrap text-nowrap text-ellipsis text-clip"></p>
<table class="border-collapse border-separate border-solid border-dashed border-dotted border-none"></table>
<span class="shadow-none outline-none outline-dashed ring-inset decoration-wavy from-0% via-none"></span>
<span class="bg-clip-text bg-fixed bg-cover bg-center bg-no-repeat bg-top-left text-inherit text-shadow-sm fill-none accent-auto"></span>
<span class="bg-mauve-500 border-bs-2"></span>
</div>
@@ -1,4 +0,0 @@
<div class="relative inset-0 -top-2 z-10 overflow-hidden shrink-0 col-start-2 justify-self-end container">
<span class="border border-t-2 rounded shadow outline-none ring-2 transition opacity-50 -rotate-45 cursor-pointer select-none"></span>
<span class="aspect-video object-cover font-mono font-sans antialiased underline uppercase text-end list-disc table bg-clip-text"></span>
</div>
@@ -1,3 +0,0 @@
<div class="hover:underline dark:opacity-50">
<span class="group-hover:flex focus:md:gap-2"></span>
</div>
@@ -3,7 +3,7 @@
<x-icon name="o-home" />
<x-button icon="calendar_month" icon-right="not_a_symbol" label="Fine" />
<span>text-red-600</span>
<p class="text-tertiary focus-ring">Tailwind's and ours</p>
<p class="text-tertiary focus-ring">Classes no stylesheet declares</p>
<x-icon :name="$dynamic" />
<x-icon name="home" @class(['size-4']) />
</div>