From 87004a90357008b1eb5ad2206e4be6094b4a83f3 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Tue, 15 Sep 2026 08:51:32 +0200 Subject: [PATCH] Document the design guard as it now stands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan step 41 review: README and the development skill describe the API that exists after the review, briefly — what scan() reads, the family table with the M3 spacing step and 1.x utilities, missingStylesheets() reading imports only, forbidColours() on the 2.0.0 vocabulary — and UPGRADE.md lists the removed checks and methods and the retargeted forbidColours() (the user, 2026-09-15). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- README.md | 6 +-- UPGRADE.md | 14 ++++++- .../livewire-material-development/SKILL.md | 41 ++++++++++--------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index c5503cfd..0e00d4b3 100644 --- a/README.md +++ b/README.md @@ -143,16 +143,16 @@ While the application runs locally (or with `MATERIAL_SHOWCASE=true`), `/materia use NoNameWeb\LivewireMaterial\Testing\DesignGuard; it('uses only what compiles', function () { - expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()]) + expect(DesignGuard::scan([resource_path('views'), resource_path('js'), resource_path('css'), app_path()]) ->missingStylesheets(resource_path('css/app.css')) ->forbidColours(['tertiary']) ->violations())->toBe([]); }); ``` -The guard fails on Tailwind palette colours, unknown Material Symbol names and Blade directives written inside component tags. `forbidColours([...])` names roles the application leaves out, and fails wherever one is still written: its `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. It also fails on every Tailwind utility or variant still sitting in a view, PHP or JS file — a breakpoint prefix, a radius, shadow, type size/weight/leading/tracking, easing or duration, a flex/grid/spacing/sizing/display utility, a text-layout utility, an M3 role utility (`text-on-surface-variant`, `bg-primary`), a pseudo-class variant, or an arbitrary `[…]` value — none of which compile any more, since the application carries no Tailwind: every line names its 2.0.0 replacement, a layout component and prop (`flex gap-4` → ``), an `md-*` class (`truncate` → `md-truncate`), or a token for the application's own CSS (`rounded-lg` → `var(--md-sys-shape-corner-lg)`). A class the application's own stylesheets declare is exempt, and so is every `md-*` class. +The guard reads every path it is given and fails, with `path:line` and the 2.0.0 replacement, on any Tailwind utility or variant still in a view, PHP or JS file — none compiles, since the application carries no Tailwind — and on 1.x's own utilities (`type-body-md`, `text-meta`, `rounded-corner-lg`, `focus-ring`): a layout component and prop (`flex gap-4` → ``), an `md-*` class (`truncate` → `md-truncate`), or a token for the application's own CSS (`rounded-lg` → `var(--md-sys-shape-corner-lg)`). A class the application's own stylesheets declare is exempt. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, easing or duration, and on a media query off M3's 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. It also fails on unknown Material Symbol names and Blade directives written inside component tags. -`missingStylesheets($cssEntry)` checks the CSS entry's `@import` graph (followed through every package file's own imports) against the package tags a view actually renders — unprefixed, under the configured prefix, or `` — and `->links()`; each missing one names the exact `@import` line to add, and a tag the application shadows with its own component of the same name is reported instead. It also turns on a check of the application's own CSS (the entry and what it imports outside the package, plus any `.css` file among the scanned paths), which reports a literal colour, radius, shadow, font size, weight, line height, letter spacing, easing, duration or off-scale media query with its token or breakpoint — a value inside `var(--md-sys-…)` or `calc()` is always fine, and the generated `material-scheme.css` is skipped. +`missingStylesheets($cssEntry)` checks the entry's relative `@import` graph, followed through every package file's own imports, against the package tags the views render (unprefixed, under the configured prefix, or ``) and `->links()`, and names each missing `@import` line once; a tag the application shadows with its own component is reported instead. It reads imports only, so leave `resource_path('css')` out of `scan()` while the stylesheets still hold literals. `forbidColours([...])` names roles the application leaves out, and fails wherever one (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds any pattern of your own, line by line. ## AI agents diff --git a/UPGRADE.md b/UPGRADE.md index 73b04c3e..abe6159e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -156,8 +156,18 @@ the contrast toggle (``) and the Standard motion ### 8. Tests and guards -Add `DesignGuard`'s new checks to the application's design test (`forbidOpacityInk()` and -`forbidAbsolutes()` are opt-in). Browser tests that assert widths switch at 640/1024/1280 now +`DesignGuard` changes with the stack: + +- **Removed:** the maryUI tag and daisyUI class checks, `forbidAbsolutes()` and `forbidOpacityInk()`. + Every Tailwind utility now compiles to nothing and is reported with its replacement, so + `bg-white` and `text-on-surface/60` still are. +- **Retargeted:** `forbidColours([...])` keeps its signature and reports a left-out role where 2.0.0 + writes one: `var(--md-sys-color-…)` in CSS or an inline `style`, an `md-ink-*` class, a + component's `color`/`tone` prop. +- **New:** `missingStylesheets($cssEntry)` names each `@import` the views need; a `.css` file passed + to `scan()` is checked for literal values and off-scale media queries. + +Browser tests that assert widths switch at 640/1024/1280 now switch at 600/840/1200; tests that read `role="alert"` on an alert, `aria-pressed` on the time picker's period buttons or `data-theme-option` need the new hooks above. diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 3ede402b..a7358d84 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -1110,41 +1110,42 @@ Pagination: `$paginator->links()` (Laravel and Livewire, full and simple/cursor) use NoNameWeb\LivewireMaterial\Testing\DesignGuard; it('uses only what compiles', function () { - expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()]) + expect(DesignGuard::scan([resource_path('views'), resource_path('js'), resource_path('css'), app_path()]) ->missingStylesheets(resource_path('css/app.css')) ->forbidColours(['tertiary']) // roles this application's rules leave out ->violations())->toBe([]); }); ``` -It fails on Tailwind palette colours, unknown symbol names and Blade directives written inside a component tag (where they do not compile), with `path:line` for each. `forbidColours([...])` fails wherever a left-out role (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. - -It also fails on every Tailwind utility or variant, which compile to nothing in a Tailwind-free application, each with its 2.0.0 replacement — a layout component and prop, an `md-*` class, or a token for the application's own CSS. A class the application's own stylesheets declare is exempt, and so is every `md-*` class: +It reads every path it is given and fails with `path:line` on unknown symbol names, Blade directives written inside a component tag (where they do not compile), and every Tailwind utility or variant — none compiles in a Tailwind-free application — or 1.x utility, each with its 2.0.0 replacement. A class the application's own stylesheets declare is exempt, and so is every `md-*` class: | Written | Use | | --- | --- | -| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` (and `max-`), `hover:`, `dark:` … | a layout component's `hide-below`/`hide-from`/`stack-below` prop, `:has()`/`aria-*`/`data-md-*`, or a plain `@media`/state rule | -| `flex`, `grid`, `gap-4`, `grid-cols-*`, `items-center` … | ``, `` or `` and their `gap`/`align`/`justify`/`columns` props | -| `p-4`, `mt-2`, `space-y-4` | a layout component's `gap`/`padding` prop, or `var(--md-sys-measurement-space*)` | -| `w-full`, `h-screen`, `min-w-0` | a literal length in your own CSS — M3 keeps no size scale | -| `hidden` | a layout component's `hide-below`/`hide-from` prop, or `[hidden]` | -| `block`, `inline-block`, `invisible` | a plain `display` rule in your own CSS | +| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` (and `max-`) | `@media (width >= 600px)` …, or a layout component's `hide-below`/`hide-from`/`stack-below` prop | +| `hover:`, `dark:`, `group-hover:`, `rtl:` … | `md-state-layer`/`md-focus-ring`, `[data-theme='dark']`, `:has()`, `:is([dir='rtl'], [dir='rtl'] *)` in your own CSS | +| `flex`, `flex-col`, `flex-wrap`, `items-center`, `justify-between` | ``, ``, ``, `align="center"`, `justify="between"` | +| `grid`, `grid-cols-3` | `` (or a per-breakpoint map), `` for cards | +| `gap-4`, `space-y-4`, `p-4`, `mt-2` | the M3 step at 4px per unit: `gap="space200"` (16px), ``, ``, `var(--md-sys-measurement-space100)` | +| `w-full`, `max-w-5xl`, `size-6` | ``, ``, or a length in your own CSS — M3 keeps no size scale | +| `hidden` | a layout component's `hide-below`/`hide-from` prop, the `hidden` attribute, or `x-show` | +| `block`, `relative`, `inset-0`, `z-10`, `overflow-hidden`, `shrink-0`, `border`, `opacity-50`, `cursor-pointer` … | the rule in your own CSS | | `text-center`, `truncate`, `sr-only`, `whitespace-nowrap`, `line-clamp-2`, `tabular-nums` | `md-text-center`, `md-truncate`, `md-visually-hidden`, `md-nowrap`, `md-line-clamp-2`, `md-tabular` | +| `type-body-md`, `focus-ring`, `state-layer`, `link` (1.x) | `md-type-body-md`, `md-focus-ring`, `md-state-layer`, `md-link` | | `text-on-surface-variant`, `text-primary`, `text-meta` … | `md-ink-variant`, `md-ink-primary` … | -| `border-outline-variant` | `` or `` | +| `border-outline-variant`, `border-divider` | `` or `` | | `bg-surface-container` | `` | -| `bg-primary`, `border-error` … | `var(--md-sys-color-*)` | +| `bg-primary`, `border-error`, `text-red-500` … | `var(--md-sys-color-*)` | | `bg-white`, `text-black` | a surface or `on-` role — M3 has no white or black | | `text-on-surface/60`, `bg-on-surface/8` | `md-ink-variant`/`md-ink-quiet`, or `md-state-layer` for an overlay | -| `rounded-lg`, `rounded-t-2xl`, `rounded-full` | `var(--md-sys-shape-corner-lg)`, or `` | -| `shadow-sm`, `shadow-md` … `shadow-2xl` | `var(--md-sys-elevation-1)` … `var(--md-sys-elevation-5)` | -| `text-sm`, `leading-6`, `tracking-wide` | one of the `md-type-*` classes, which set size, line height and tracking together | -| `font-medium`, `font-bold` | one of the `md-type-emphasized-*` classes | -| `ease-in-out`, `ease-linear` | `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)`, paired with its `-duration` | -| `duration-300` | `var(--md-sys-motion-…-duration)`, paired with its easing | -| `bg-[#1d7afc]`, `text-[rgb(…)]`, `w-[320px]` | an M3 role, or the literal value in your own CSS | +| `rounded-lg`, `rounded-corner-lg` (1.x) | `var(--md-sys-shape-corner-lg)`, or `` | +| `shadow-md`, `shadow-elevation-2` (1.x) | `var(--md-sys-elevation-2)` | +| `text-sm`, `leading-6`, `tracking-wide`, `font-medium` | one of the `md-type-*`/`md-type-emphasized-*` classes | +| `ease-in-out`, `ease-spatial-fast` (1.x), `duration-300` | `var(--md-sys-motion-spatial-fast)` with its `-duration`, in your own `transition` | +| `bg-[#1d7afc]`, `w-[320px]`, `[--x:1]` | an M3 role, or the value in your own CSS | -`missingStylesheets($cssEntry)` follows the entry's `@import` graph through every package file's own imports (`split-button.css` counts `button.css` and `menu.css` too) and checks it against the package tags a scanned view renders (plain, prefixed or ``) and `->links()`; a missing one names the exact `@import` line to add, and a tag the application shadows with its own component of the same name is reported instead — the application's component wins in Blade. It also turns on a check of the application's own CSS (the entry and what it imports outside the package, plus any `.css` file among the scanned paths, `material-scheme.css` excluded): a literal colour, radius, shadow, font size, weight, line height, letter spacing, easing, duration or a media query off M3's 600/840/1200/1600px scale, each with its token or breakpoint — a value inside `var(--md-sys-…)` or `calc()` is always fine. +In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, font size, weight, line height, letter spacing, easing or duration, and on a media query width off 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. + +`missingStylesheets($cssEntry)` follows the entry's relative `@import`s through every package file's own imports (`split-button.css` counts `button.css` and `menu.css` too; `tailwindcss` or a font URL is skipped) and checks them against the package tags a scanned view renders (plain, prefixed or ``) and `->links()`; each missing stylesheet names its `@import` line once, and a tag the application shadows with its own component is reported instead — the application's component wins in Blade. It reads imports only: leave `resource_path('css')` out of `scan()` until the stylesheets are on tokens. `forbidColours([...])` fails wherever a left-out role (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds a pattern of your own. ## Conventions