|
| 1 | +# check-btn-restyle |
| 2 | + |
| 3 | +Forbid scoped `.svelte` `<style>` blocks from restyling the canonical `<Button>` classes. |
| 4 | + |
| 5 | +## Why |
| 6 | + |
| 7 | +`<Button>` (`apps/desktop/src/lib/ui/Button.svelte`) is the single source of truth for the app's button visuals: variant |
| 8 | +background/text colors, hover states, focus ring, disabled opacity. Every primary button shares one accent-driven color |
| 9 | +pair, so a contrast fix made in `Button.svelte` reaches every consumer. |
| 10 | + |
| 11 | +When a feature component drops a `.btn-primary { color: ... }` into its scoped `<style>`, it silently re-introduces |
| 12 | +whatever problem we just paid to solve in `Button.svelte`. The feature owner isn't usually thinking about contrast under |
| 13 | +macOS-Purple-accent dark mode, so the regression goes unnoticed until a user reports unreadable text — exactly the |
| 14 | +failure mode the accent matrix in `check-a11y-contrast` exists to prevent. |
| 15 | + |
| 16 | +## What it flags |
| 17 | + |
| 18 | +Any rule inside a `<style>` block (in any `.svelte` file other than `Button.svelte` itself) whose: |
| 19 | + |
| 20 | +- selector references one of `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-danger`, AND |
| 21 | +- declarations include `color`, `background`, or `background-color`. |
| 22 | + |
| 23 | +Layout-only overrides (flex, width, padding, margin, font-size, etc.) are allowed and not flagged. Re-targeting |
| 24 | +`<Button>` via `:global(button)` for layout is also fine, since the `<button>` selector alone is not on the banned list. |
| 25 | + |
| 26 | +Similarly-named classes like `.btn-mini`, `.btn-regular` (Button's size classes), or `.toggle-button` are NOT flagged — |
| 27 | +only the four canonical classes above. |
| 28 | + |
| 29 | +## Allowlist |
| 30 | + |
| 31 | +Add `/* allowed-btn-restyle: <rationale> */` immediately before the rule (only whitespace between the comment's closing |
| 32 | +`*/` and the selector). Empty rationales are rejected — write a real reason: |
| 33 | + |
| 34 | +```svelte |
| 35 | +<style> |
| 36 | + /* allowed-btn-restyle: drag handle inside the conflict resolver needs an inverted color pair for visibility on the |
| 37 | + warning-tinted background; verified at 5.2:1 under all accent variants */ |
| 38 | + .btn-primary { |
| 39 | + color: var(--color-warning-text); |
| 40 | + background: var(--color-warning-bg); |
| 41 | + } |
| 42 | +</style> |
| 43 | +``` |
| 44 | + |
| 45 | +Allowlisted rules show up in `--verbose` output at the end of a clean run so the rationales stay visible (and reviewable |
| 46 | +later). |
| 47 | + |
| 48 | +## Run |
| 49 | + |
| 50 | +```bash |
| 51 | +# Direct |
| 52 | +go run ./scripts/check-btn-restyle |
| 53 | + |
| 54 | +# Via check runner |
| 55 | +./scripts/check.sh --check btn-restyle |
| 56 | + |
| 57 | +# Verbose (list allowlisted rules) |
| 58 | +go run ./scripts/check-btn-restyle -- --verbose |
| 59 | +``` |
| 60 | + |
| 61 | +Exit code 0 on clean, 1 on any violation. |
| 62 | + |
| 63 | +## Scope and limitations |
| 64 | + |
| 65 | +- Only top-level rules inside a `<style>` block are scanned. Rules nested inside `@media` / `@supports` blocks are |
| 66 | + currently NOT scanned. This is acceptable for now: dark-mode overrides of Button styling have not appeared in the |
| 67 | + codebase, and adding `@media` traversal is straightforward later if it becomes needed (see the test |
| 68 | + `TestScanDescendsIntoMediaBlocks` for the pinned behavior). |
| 69 | +- The check trusts that Svelte's class-scoping means `.btn-primary` in a feature file only matches via global selectors |
| 70 | + or Button's own classes flowing through. In practice both end up clobbering the shared component. |
| 71 | +- The check skips `Button.svelte` itself by filename. If you ever rename the canonical button file, update `scanFile`'s |
| 72 | + caller in `main.go`. |
| 73 | + |
| 74 | +## See also |
| 75 | + |
| 76 | +- `scripts/check-a11y-contrast` — the static contrast checker that catches the contrast regressions this check exists to |
| 77 | + prevent in the first place. Run both. |
0 commit comments