Skip to content

Commit 51f3193

Browse files
committed
A11y: btn-restyle check forbids .btn-* overrides
New Go-based check (`scripts/check-btn-restyle/`) scans `.svelte` files for scoped CSS that sets `color`, `background`, or `background-color` on `.btn`, `.btn-primary`, `.btn-secondary`, or `.btn-danger`. Layout-only overrides via `:global(button)` are unaffected. Similarly-named classes (`.btn-mini`, `.btn-regular`, `.toggle-button`) aren't flagged. Opt-out via `/* allowed-btn-restyle: <rationale> */` on the line above. Empty rationales are rejected. Allowlisted entries surface under `--verbose` so reasons stay visible. Wired into the runner as `desktop-svelte-btn-restyle` (nickname `btn-restyle`), `IsFast: true`, depends on stylelint.
1 parent 0e885f5 commit 51f3193

9 files changed

Lines changed: 768 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Three cadences. Pick the one that matches where you are in the work, not the one
115115
exclusive with `--include-slow` / `--only-slow`. Covers:
116116
- All formatters (`oxfmt`, `rustfmt`, `gofmt`) and most non-compiling static linters (`cfg-gate`, `log-error-macro`,
117117
`error-string-match`, `ipc-enum-camelcase`, `cargo-machete`, `knip`, `import-cycles`, `type-drift`, `stylelint`,
118-
`css-unused`, `a11y-contrast`, `a11y-coverage`, `e2e-linux-typecheck`).
118+
`css-unused`, `a11y-contrast`, `btn-restyle`, `a11y-coverage`, `e2e-linux-typecheck`).
119119
- Go: `go-vet`, `staticcheck`, `ineffassign`, `misspell`, `gocyclo`, `go-tests`.
120120
- API server: `typecheck`, `tests`.
121121
- Website: `html-validate` (self-skips when `dist/` is absent).
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.
2.91 MB
Binary file not shown.

scripts/check-btn-restyle/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module cmdr/scripts/check-btn-restyle
2+
3+
go 1.25

0 commit comments

Comments
 (0)