Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@
class="relative border-b border-(--border-default) py-(--spacing-sm) px-(--spacing-md)"
>
<!--
min-h matches the absolute close button (IconButton small = 1.75rem): with no
description the lone title line top-aligns within a row at least as tall as the
close, instead of centering in the header's symmetric padding.
min-h matches the absolute close button (IconButton small is `size-7` = 1.75rem):
with no description the lone title line top-aligns within a row at least as tall as
the close, instead of centering in the header's symmetric padding.

It is the bare `min-h-7`, mirroring the button's own `size-7`, because it must equal
that box — there is no token for it, and the one this previously named did not have
the right value anyway. It used to name `--size-4` through an inverted bracket/paren
shorthand, which emits no CSS, so the row had no minimum height at all — and that
token is 16px, not the 1.75rem this is matching. Two defects hiding each other; the
`dead-token-shorthand` token-check now catches the first class.
-->
<div
class="flex min-h-[--(size-4)] flex-col gap-(--spacing-xxs) pr-(--spacing-xxl) [&>button]:absolute [&>button]:right-(--spacing-md) [&>button]:top-(--spacing-sm)"
class="flex min-h-7 flex-col gap-(--spacing-xxs) pr-(--spacing-xxl) [&>button]:absolute [&>button]:right-(--spacing-md) [&>button]:top-(--spacing-sm)"
>
<slot />
</div>
Expand Down
27 changes: 27 additions & 0 deletions packages/webkit/src/eslint-plugin/token-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ export const TOKEN_CHECKS = [
message:
'Zero with the wrong unit inside a math function. `calc()`/`min()`/`max()`/`clamp()` require a unit on the zero — write `0rem`, not `0px` / `0em` (.claude/rules/styling.md).'
},
{
id: 'dead-token-shorthand',
// A MALFORMED token shorthand never reaches the element, silently. Two spellings,
// both found in the wild, failing at two different stages:
// `bg-(--token )` a stray space TERMINATES the Tailwind candidate, so it sees
// the unterminated `bg-(--token` and emits NOTHING at all.
// `min-h-[--(token)]` bracket/paren inverted. This one DOES emit a rule — with the
// declaration `min-height: --(token)`, an invalid value the
// browser discards at parse time. Same outcome, later stage.
// Neither is a lint, type, or build error anywhere else, and the class reads as correct
// forever. chip's `filled` kind was transparent in BOTH themes from the day it shipped
// because of the first, and its own visual baselines were generated from the broken
// render — so they encoded the bug as correct and nothing ever failed.
//
// The scan is over raw file text, so a COMMENT quoting a malformed class trips this
// too. Describe such a class ("an inverted bracket/paren shorthand") rather than
// spelling it out.
//
// Read as: `-(`, whose contents mention a custom property, contain whitespace ANYWHERE
// (before the `--`, inside it, or trailing), and then close. Requiring the `--` via
// lookahead is what keeps this off ordinary subtraction — JS `x -(y + z)` has no `--`
// — and `var(--a, var(--b))` never matches because the character before that paren is
// `r`, not `-`, so a nested var with a fallback is untouched.
regex: /-\((?=[^)]*--)[^)]*\s[^)]*\)|\[--\(/g,
message:
'Malformed token shorthand — the style NEVER applies (Tailwind emits nothing for a space inside the parens; an inverted `[--(token)]` emits an invalid value the browser discards). Write `prop-(--token)` / `prop-(type:--token)` with no whitespace inside the parens, and `[--token:value]` to DECLARE a custom property (.claude/rules/styling.md).'
},
{
id: 'class-in-defineprops',
regex: /defineProps\s*[<(][^>)]*['"]?class['"]?\s*:/s,
Expand Down
42 changes: 42 additions & 0 deletions packages/webkit/test/eslint-plugin/token-checks.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,48 @@ test('the widened guardrails do not fire on the canonical whole-value paren toke
}
})

test('dead-token-shorthand fires on a paren shorthand that emits no CSS', () => {
for (const content of [
// The one that shipped: a stray space terminates the candidate.
'class="data-[kind=filled]:bg-(--bg-surface-raised )"',
'class="bg-( --bg-surface)"',
'class="text-(length:--text-body-md )"',
// A token broken across lines is the same defect.
'class="bg-(--bg-\n surface)"',
// Bracket and paren forms inverted.
'class="min-h-[--(size-4)]"'
]) {
assert.ok(
ids(content).includes('dead-token-shorthand'),
`expected dead-token-shorthand for: ${content}`
)
}
})

test('dead-token-shorthand stays silent on every VALID token spelling', () => {
for (const content of [
'class="bg-(--bg-surface) text-(--text-default) rounded-(--shape-card)"',
'class="text-(length:--text-body-md) font-(family-name:--font-sora)"',
'class="data-[disabled]:bg-(--bg-disabled)"',
// Declaring a custom property keeps its brackets — the sanctioned form.
'class="[--table-row-bg:var(--bg-surface)]"',
'class="data-[state=selected]:[--table-row-bg:var(--bg-selected)]"',
// Expression values legitimately carry spaces INSIDE brackets, not parens.
'class="w-[calc(100%_-_var(--x))]"',
'style="width: calc(var(--a) - (var(--b) * 2))"',
// A var with a fallback: `-(` never precedes the `--`, so this is not a match.
'style="background: var(--x, var(--y))"',
// Ordinary subtraction in script, with and without a space.
'const gap = width -(padding + border)',
'const gap = width - (padding + border)'
]) {
assert.ok(
!ids(content).includes('dead-token-shorthand'),
`expected dead-token-shorthand silent for: ${content}`
)
}
})

test('tokenChecksApply scopes enforcement to component sources', () => {
assert.ok(tokenChecksApply('packages/webkit/src/components/actions/button/button.vue'))
assert.ok(tokenChecksApply('packages/webkit/src/components/data/table/injection-key.ts'))
Expand Down
Loading