Leave a Markdown mail component's classes to the mail theme in the design guard
A mail component is drawn by the mail theme, not by the application's CSS entry, and the theme's classes share Tailwind's names: ReStride's `x-mail::sessions` wraps its table in `<div class="table">`, the class the package's own mail theme styles, and the guard reported it as Tailwind's `display: table`. A view under a path `mail.markdown.paths` names (Laravel's `resources/views/vendor/mail` by default) now skips family (i) and the breakpoint, scale, palette and colour-value checks; its icon names, the directives in its component tags, forbidColours() and forbid() are still read. A mail theme stylesheet under that path, which has to write literal values, is left out of check (iii) and of the exempt classes, so its `.table` no longer hides `table` in the application's other views either. The guard's docblock, the README and the skill's "Testing the design" say so; a fixture test reads the same mail view and theme with and without the path configured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7a95449c82
commit
0641071a06
@@ -47,6 +47,15 @@ use Symfony\Component\Finder\Finder;
|
||||
* at runtime (`'text-'.$tone`) or hidden in a comment stays invisible — the same reason
|
||||
* to write class names out whole.
|
||||
*
|
||||
* A Markdown mail component — a view under a path `mail.markdown.paths` names, Laravel's
|
||||
* `resources/views/vendor/mail` by default — is drawn by the mail theme, not by the application's
|
||||
* CSS entry, and the theme's own classes (`table`, `button`, `panel`, `break-all`) share a
|
||||
* Tailwind utility's name. So family (i) and the breakpoint, scale, palette and colour-value
|
||||
* checks skip those views, while their icon names, the directives in their component tags and
|
||||
* the application's own bans are still read; and a stylesheet under that path, a mail theme that
|
||||
* has to write literal values because mail clients read no custom property, is neither check
|
||||
* (iii)'s nor a source of exempt classes.
|
||||
*
|
||||
* expect(DesignGuard::scan([resource_path('views'), resource_path('js'), resource_path('css'), app_path()])
|
||||
* ->missingStylesheets(resource_path('css/app.css'))
|
||||
* ->forbidColours(['tertiary'])
|
||||
@@ -385,6 +394,8 @@ class DesignGuard
|
||||
}
|
||||
}
|
||||
|
||||
$mailPaths = $this->mailComponentPaths();
|
||||
|
||||
foreach ($this->files() as $file) {
|
||||
if (str_ends_with($file->getFilename(), '.css')) {
|
||||
continue; // A directly-scanned CSS file is check (iii)'s alone; these checks read Blade, PHP and JS.
|
||||
@@ -393,12 +404,13 @@ class DesignGuard
|
||||
$contents = (string) file_get_contents($file->getPathname());
|
||||
$where = $this->relative($file->getPathname());
|
||||
$isBlade = str_ends_with($file->getFilename(), '.blade.php');
|
||||
$readsClasses = ! $this->isUnder($file->getPathname(), $mailPaths);
|
||||
|
||||
if ($isBlade) {
|
||||
$contents = $this->withoutBladeComments($contents);
|
||||
}
|
||||
|
||||
if (str_ends_with($file->getFilename(), '.php')) {
|
||||
if ($readsClasses && str_ends_with($file->getFilename(), '.php')) {
|
||||
foreach ($this->literalClasses($contents) as [$line, $class]) {
|
||||
if (($hint = $this->tailwindFamilyHint($class)) !== null) {
|
||||
$violations[] = "{$where}:{$line} {$hint}";
|
||||
@@ -433,13 +445,13 @@ class DesignGuard
|
||||
foreach (explode("\n", $contents) as $index => $text) {
|
||||
$line = $index + 1;
|
||||
|
||||
if (preg_match_all($this->colourPattern(), $text, $matches)) {
|
||||
if ($readsClasses && preg_match_all($this->colourPattern(), $text, $matches)) {
|
||||
foreach ($matches[0] as $class) {
|
||||
$violations[] = "{$where}:{$line} Tailwind palette colour `{$class}` compiles to nothing — M3 paints with roles: an `md-ink-*` class, or `var(--md-sys-color-*)` in your own CSS";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ([...$this->offTheTokens($text), ...$this->forbiddenRoles($text)] as $what) {
|
||||
foreach ([...($readsClasses ? $this->offTheTokens($text) : []), ...$this->forbiddenRoles($text)] as $what) {
|
||||
$violations[] = "{$where}:{$line} {$what}";
|
||||
}
|
||||
|
||||
@@ -1546,8 +1558,9 @@ class DesignGuard
|
||||
|
||||
/**
|
||||
* Every `.css` file this guard reads for check (iii): the ones `scan()`'s paths hold, outside
|
||||
* the package, the generated `material-scheme.css` excluded. The CSS entry's imports are not
|
||||
* followed here — a vendor stylesheet it pulls in is not the application's to put on tokens.
|
||||
* the package, the generated `material-scheme.css` and a mail theme under a mail component path
|
||||
* excluded. The CSS entry's imports are not followed here — a vendor stylesheet it pulls in is
|
||||
* not the application's to put on tokens.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
@@ -1571,12 +1584,44 @@ class DesignGuard
|
||||
}
|
||||
}
|
||||
|
||||
$mailPaths = $this->mailComponentPaths();
|
||||
|
||||
return array_values(array_unique(array_filter(
|
||||
$files,
|
||||
fn (string $file): bool => $file !== '' && ! static::isPackageFile($file) && ! $this->isGeneratedScheme($file),
|
||||
fn (string $file): bool => $file !== '' && ! static::isPackageFile($file) && ! $this->isGeneratedScheme($file) && ! $this->isUnder($file, $mailPaths),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Markdown mail component paths Laravel renders mail from (`mail.markdown.paths`), as real
|
||||
* paths; one that does not exist is left out. See the class header.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function mailComponentPaths(): array
|
||||
{
|
||||
return array_values(array_filter(array_map(
|
||||
fn (mixed $path): string => is_string($path) ? (string) realpath($path) : '',
|
||||
(array) config('mail.markdown.paths', []),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $directories Real paths.
|
||||
*/
|
||||
protected function isUnder(string $file, array $directories): bool
|
||||
{
|
||||
$file = realpath($file) ?: $file;
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
if (str_starts_with($file, rtrim($directory, '/').'/')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every class an application's own stylesheets select on — the scanned ones and whatever the
|
||||
* CSS entry imports outside the package — check (i)'s exemption list, so a class it defines is
|
||||
|
||||
Reference in New Issue
Block a user