Repository navigation
Replace focus rings with a painted token shift (closes #2718) - #2726
Conversation
A focused control now adopts its own hover appearance instead of gaining a ring. Focus and hover mean the same thing to the person using the app — "this is the control you are about to act on" — so they look the same, and a treatment built only from background-color cannot move the control or its siblings. The hard constraint is that focus must never change geometry. Three families are banned for three different reasons: border-width/padding genuinely reflow and shove every sibling after the control; outline with a negative offset paints inside the edge and reads as a sudden inner border; ring-offset-* paints a gap plus a ring and visually enlarges the footprint. Only the first is literally a reflow, which is why all three are banned together rather than leaving a reader to work out which kind of "doesn't move" each one offers. Two defects surfaced that the issue had not catalogued, because its survey covered Tailwind utilities and these are hand-written CSS: - app-shell carried `outline: 2px solid; outline-offset: 2px` on :focus-within, which drew a ring around the ENTIRE application window whenever anything inside it held focus. This is the idiom removed app-wide in #300; it survived because :focus-within was in nobody's grep. - chat-input and model-manager (x2) paired a border-color change with `box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2)` — the same theme-blind alpha compositing the checkbox had. The border-color change is kept: it repaints an existing border without touching its width, so it moves nothing. A painted-only rule creates a trap a geometry ban cannot catch: a fill that moves nothing but is indistinguishable from the page satisfies every rule and still shows the user nothing. This bit twice during implementation. The neutral treatment was first written as bg-muted, matching how neutral hovers are done elsewhere — 1.10:1 against the page in light mode. Then `secondary` was given focus-visible:bg-secondary/80 to mirror its hover, which composites to 1.08:1. Measured against both themes, --accent is the only non-brand token clearing the 3:1 non-text floor (5.19:1 light, 3.75:1 dark), so chrome focus uses it. Both traps now have assertions rather than relying on someone re-measuring. Checkbox, switch and radio get no focus treatment at all. Each shows its state WITH its fill, so borrowing that fill for focus would make a focused-unchecked box read as checked; the only surface left is a border, which is the geometry idiom this change exists to prevent. A tab panel is likewise a scroll container rather than a control — nothing in it for Enter to activate. The editor surface is untouched. Tab is bound to indent/outdent there rather than focus traversal, and the four sites carrying "Removed :focus-visible border" keep that decision. focus-treatment.test.ts enforces all of it, sweeping every source file for focus utilities and hand-written :focus blocks. It is a text scan rather than a render because Happy-DOM applies no Tailwind stylesheet, so a computed style reports an empty background for a correct class and a forbidden one alike. Mutation-tested against four regressions: a reintroduced ring, a border-width change in CSS, a focus-visible->focus revert, and the invisible alpha fill. One known gap is stated rather than hidden: --accent-foreground on --accent is 3.40:1 in light mode, below the 4.5:1 text floor. It is a pre-existing property of the accent pair, already shipped on seven hover:bg-accent sites, which the focus rule inherits rather than introduces. The test pins the figure so it cannot silently worsen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review TypeInitial review — PR #2726, single commit Verification performed independently of the PR body: WCAG ratios recomputed from Headline: the core design work is sound and the contrast reasoning is correct — every figure in the PR body reproduces exactly. Two things block: a WCAG 2.4.7 regression where Requirements Check
Independent contrast verification — every figure the PR asserts reproduces to the stated precision:
Credit where due: the "invisible fill" trap is real, the diagnosis is right, and Code Review Findings🔴 CriticalF4 — 📁 This is the finding I'd most want the author to push back on, so let me be precise about why it isn't the same as "the spec said so." The spec said don't give them a border treatment. What shipped is stronger: each of these keeps The concrete failure is live in the tree today: 📁 <div class="grid gap-3">
{#each optionRows as option (option.id)}
<div class="flex items-start gap-3">
<Checkbox id={option.id} bind:checked={modalState[option.key]} ... />A vertical stack of checkboxes and nothing else interactive between them. A keyboard user tabbing into this modal cannot tell which checkbox Space will toggle. That is WCAG 2.4.7 (Focus Visible, Level AA) failing outright, and 2.1.1 in practice — the control is reachable but not operable with confidence. I'd also challenge the premise in the source comment.
The rule the PR wrote permits Suggested fix — one of, in order of preference:
Principle: removing a fallback and its replacement in the same change is not a neutral refactor. The spec authorized declining to add a treatment; it did not authorize suppressing the one the platform provides for free. 🟡 ImprovementsF1 — 📁 I ran the regex against 36 cases. It correctly catches everything the PR mutation-tested, and correctly spares
Every one of these would ship a geometry-changing focus state past a green suite. Suggested tightening: /(?:focus|focus-visible|focus-within)(?::[a-z-]+)*:(?:ring(?:-offset)?(?:-|\b)|border(?:-[xytrbl])?(?:-(?:\d|\[))?\b|p[xytrbl]?-(?:\d|\[)|m[xytrbl]?-(?:\d|\[)|outline(?!-none)|shadow(?:-|\b)|scale-|translate-|tracking-|font-(?:thin|extralight|light|normal|medium|semibold|bold|extrabold|black))/Principle: a guard test's value is bounded by what it doesn't let through, not by what it caught during authoring. Mutation-testing against four regressions you chose yourself confirms the assertions fire; it doesn't establish coverage. These seven are what an unfamiliar contributor would plausibly write next. F2 — the hand-written-CSS sweep misses several real block shapes; the 📁 I ran the state machine against 15 CSS shapes. Confirmed misses:
The single-line-rule and multiple-declarations-per-line misses are the most likely in practice — Suggested fix: rather than patching the line-oriented scanner case by case, flatten each file's CSS before scanning — strip comments, normalize whitespace, then split declarations on Principle: line-oriented parsing of a brace language is a category error; each fix buys one shape and leaves the next. Normalizing the input first makes the scanner's correctness independent of formatting. F3 — AC #4's "every focus treatment clears 3:1 against its surface" is asserted for 📁 The test measures
That is well under the 3:1 the AC claims for "every focus treatment," and it is the same class of defect as the To be fair about what this is and isn't: 3:1 in WCAG 1.4.11 governs a component against adjacent colors, and a filled button against the page still clears it easily. There's a defensible reading where a hover-token shift on an already-high-contrast filled button is acceptable. But the PR asserts the stronger property and doesn't measure it, and the whole thesis of the "invisible fill" section is don't trust a shift you haven't measured. Two options:
Either way the gap between what the AC says and what the test checks should close. Right now a reader of AC #4 plus a green suite concludes something untrue. Principle: an assertion that covers one of three cases and is named as if it covers all three is worse than no assertion — it converts an open question into a false answer. F5 — 📁 The stated reason is sound (this suite's own prose names the forbidden utilities). But the exclusion is by directory name at any depth, so any Also worth noting: the sweep is Nit: F6 — the 📁
Mitigating: Suggestion: 🟢 Nitpicks / ObservationsN1 — 📁 N2 — 📁 N3 — 📁 N4 — issue numbers in comments: I checked N5 — no lint suppression, no N6 — tw-merge: I evaluated N7 — comment density. The source comments here are unusually long — Recommendation🔴 REQUEST CHANGESThis is close, and most of it is genuinely good work — the contrast analysis is correct in every particular I could check, the Blocking — one item:
Strongly recommended before merge (the test is the artifact that has to outlive the change):
Non-blocking: F5, F6, N1–N7. |
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: REQUEST CHANGES (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Review of #2726 returned REQUEST CHANGES on one blocking finding plus three recommended. All four addressed. F4 (blocking) — checkbox, switch and radio keep `outline-none`, so they suppress the user agent's own focus ring AND have no replacement. That is strictly worse than before this branch: they previously had a bad indicator (1.84:1), now they have none, and `import-options-modal` is a stack of checkboxes with nothing else interactive in it. Resolved as a deliberate, documented WCAG 2.4.7 exception rather than a fix, per the product decision. The reviewer's suggested `focus-visible:border-accent` does not survive measurement — accent against primary is 1.41:1, which is the invisible-fill trap this branch exists to avoid. Measured alternatives, all recorded in DESIGN.md and in the Checkbox source: bg-accent unchecked 5.19:1 light / 3.75:1 dark, CHECKED 1.41:1 border-accent 1.41:1 in both states border-width geometry, banned by the rule itself No painted treatment serves both states, so a fill-based indicator would be visible on empty boxes and invisible on ticked ones. Restoring the UA ring would close the gap and is named in DESIGN.md as the option to reach for if the decision is revisited; it was declined because a native ring on three controls and a painted shift on the rest is two idioms, which is what this rule replaced. F1 — FORBIDDEN_UTILITY had ten false negatives. Verified against 32 cases. Now caught: bare `focus-visible:border` (= border-width:1px, the most natural way to write the exact regression this file prevents), arbitrary values (`border-[3px]`, `p-[4px]`), bare `outline`/`ring`/`shadow`, and the aliases that do not name their property — `shadow-*` IS box-shadow and `tracking-*` IS letter-spacing, both already banned in the CSS sweep, so omitting them here made the two halves of one rule disagree. Still correctly spares `border-primary` and `border-accent`, which are border COLORS and move nothing. F2 — the line-based CSS state machine missed four block shapes, including the single-line rule (`.x:focus { border-width: 2px; }`) that a formatter produces from a short rule and that is the collapsed form of the three defects this branch just fixed. Replaced with a whole-source extractor that strips comments, walks brace depth and flattens each rule body, so layout stops mattering instead of needing a special case per shape. Verified against 23 cases. Also closed a real gap the reviewer flagged and I nearly dismissed: CSS logical properties (`border-block-width`, `padding-inline-start`). They are used at 8 sites in this codebase, so the hole was live rather than theoretical. F3 — AC #4 claims every focus treatment clears 3:1, which is true for the neutral fill and NOT true for the filled variants: primary-hover against primary is 1.38:1 light / 1.30:1 dark. Recorded rather than asserted, with the reason the case differs — a neutral control has to be told apart from an unbounded page, whereas a filled button only has to be told apart from itself a moment earlier, by the same shift it already makes on hover. The figures are pinned so a token edit that flattens the shift fails loudly. F5 — narrowed the `tests/` exclusion to `src/tests` by full path (a bare name check would have dropped any `tests/` nested under `lib/` from every sweep), and read each file once instead of four times. Mutation-tested the two rewritten scanners against the exact holes reported: a single-line CSS rule with border-width, and a bare focus-visible:border utility. Both now fail the suite; both passed it before this commit. Not adopted: F6 (`link`'s focus underline) — the variant has zero call sites, so the change would be unverifiable in situ. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Address Review —
|
| Candidate | Unchecked | Checked |
|---|---|---|
bg-accent |
5.19:1 light / 3.75:1 dark ✓ | 1.41:1 ✗ |
border-accent |
1.41:1 ✗ | 1.41:1 ✗ |
| border width | — | geometry, banned by the rule |
No painted treatment serves both states: the fill already carries "checked," so any fill-based indicator is visible on empty boxes and invisible on ticked ones — inconsistent, which is worse than absent.
Taken to the product owner with the three options (restore UA ring / accent-fill-when-unchecked / accept and document). Decision: accept and document. So this is now a deliberate WCAG 2.4.7 exception, recorded in three places rather than left implicit:
DESIGN.md— a table of what was measured, why each option fails, and restoring the UA ring named as the option to reach for if revisited (it draws outside the element, so it changes no geometry; declined because a native ring on three controls plus a painted shift on the rest is two idioms, which is what this rule replaced)checkbox.svelte— the measured reasoning, with an explicit "read DESIGN.md before changing this line"switch.svelte/radio-group-item.svelte— pointer to both
Your framing — "removing a fallback and its replacement in the same change is not a neutral refactor" — is the right principle, and it's why this went back to the user rather than being settled in the PR.
✅ F1 — ten false negatives closed, verified against 32 cases
All confirmed real. Now caught: bare focus-visible:border (= border-width: 1px, and the most natural way to write the very regression this file prevents), arbitrary values (border-[3px], p-[4px]), bare outline/ring/shadow, tracking-*, font-extrabold.
The sharpest one was the inconsistency you named: shadow-* is box-shadow and tracking-* is letter-spacing, both already banned in the CSS sweep — so omitting them from the utility sweep made the two halves of one rule disagree with each other.
Still correctly spares border-primary / border-accent (border colors, which move nothing).
✅ F2 — scanner rewritten, verified against 23 cases
Took the recommendation to normalize rather than special-case. The line-based state machine is replaced with a whole-source extractor: strip comments, walk brace depth, flatten each rule body. Layout stops mattering instead of needing a case per shape.
All four reported misses now caught, including the single-line rule — which, as you noted, is what a formatter produces from a short rule and is the collapsed form of the three defects this PR just fixed.
Also closed a gap I nearly dismissed: CSS logical properties (border-block-width, padding-inline-start). I checked before waving it off — they're used at 8 sites in this codebase, so the hole was live, not theoretical. Good catch.
✅ F3 — claim corrected rather than quietly dropped
You're right that the PR asserted a stronger property than it measured. --primary-hover vs --primary is 1.38:1 light / 1.30:1 dark; --destructive-hover vs --destructive is 1.24:1 / 1.21:1.
Now recorded with the reason the case genuinely differs from the traps: a neutral control focusing to muted had to be distinguished from the page — an unbounded surface with nothing marking where the control ends. A filled button is already a saturated shape against that page, so its shift only has to be distinguishable from the same button a moment earlier, which is the identical shift it already makes on hover. The figures are pinned (1.15–1.6) so a token edit flattening the shift fails loudly.
✅ F5 — both parts fixed
tests/ exclusion narrowed to src/tests by full path — the bare name check would have silently dropped any tests/ nested under lib/ from every sweep, which is a worse bug than the one it prevented. Files now read once into a Map instead of four times.
⏭️ F6 — skipped (link focus underline)
variant="link" has zero call sites in the app today, which you noted. Changing it to decoration-2 would be an unverifiable change to dead code; if a link button ever ships, the focus treatment gets designed against a real use.
Mutation testing
Both rewritten scanners were re-mutated against the exact holes reported. Each passed the suite before this commit and fails it now:
| Mutation | Result |
|---|---|
.form-select:focus { border-color: …; border-width: 2px; } (single line) |
✗ caught |
focus-visible:border on select-trigger (bare utility) |
✗ caught |
Verification
- Design suite: 173 passing (was 171)
- Frontend: 252 files / 5166 tests passing
quality:fixclean — 0 errors/warnings across 2543 files, design-token baseline unchanged at 134DESIGN.mdupdated innodespace-docs(5176d76)
Summary
- ✅ Addressed: 5 — F4 🔴, F1 🟡, F2 🟡, F3 🟡, F5 🟡
- ⏭️ Skipped: 1 — F6 🟢 (
variant="link"has zero call sites) - 📝 Commits: 1 —
e836dd7a, plus5176d76innodespace-docs - 🧪 Tests: passing — 252 files / 5166 tests; design suite 173 (was 171)
Re-Review Decision
Decision: RE-REVIEW NEEDED
Rationale: Three reasons, in order of weight:
- The blocking finding was resolved by a product decision plus documentation rather than the suggested code change. That resolution deserves a second look — particularly whether the DESIGN.md exception states the cost plainly enough to be honest about a knowingly-shipped AA gap.
- Both guard-test scanners were substantially rewritten — a new brace-walking CSS extractor replacing the line-based state machine, and a restructured utility regex. A guard test that is wrong in a new way is worse than one wrong in a known way, and this file is the artifact meant to outlive the change.
- The contrast arithmetic behind rejecting
border-accent(1.41:1) is new and load-bearing, since it is why the suggested fix was not taken. It is worth independent verification.
Process note
This summary should have been posted immediately after e836dd7a and was not — the fixes were implemented by hand without invoking /address-review, so its final steps (post the summary, own the re-review call) were skipped. Caught on a direct question from the repo owner, then re-run properly. The code changes above were unaffected; what was missing was this comment, which is what a re-review reads to check whether prior feedback was addressed.
Review TypeRe-review — PR #2726, new commit Verification performed independently rather than read off the address-review comment: every contrast figure recomputed from Headline: the blocking finding F4 is genuinely resolved-by-decision, and the documentation is honest rather than defensive — it states the cost in the first sentence rather than after the justification. F1 is fully fixed with no false positives I could construct. F2 is fixed for all four reported shapes and the rewrite is the right call, but I found two residual holes the rewrite introduced or left, one of which is a silent false negative. Neither blocks. Previous Review Summary
Addressed: 5. Correctly declined: 1 (F6). New issues: 2 (both 🟡). Requirements Check
Independent VerificationContrast arithmetic — every figure reproduces exactly
The rejection of my F1 —
|
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review (re-review): APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Re-review returned APPROVE with two non-blocking findings, both in the CSS
scanner rewritten in the previous commit. Both fixed, since the whole point of
that file is catching what nobody thought to look for.
F7 — a declaration FOLLOWING a nested block inside a `:focus` rule was silently
missed. Stripping only `{...}` left the nested block's SELECTOR text behind,
and because declarations are split on `;` that orphan glued onto the front of
the next one, defeating the `^\s*` anchor:
.a:focus-visible { .x { color: red } padding: 4px }
^ passed, chunk read ".x padding: 4px"
A bug I introduced with the rewrite, and an order-dependent one — the same
declaration placed BEFORE the nested block was caught, which is exactly the
shape that survives casual inspection. The strip now takes each nested block
together with its selector (`[^;{}]*\{[^{}]*\}`). Reproduced against 3 variants
first, then verified the fix against all 28 CSS cases with no regressions.
F8 — `@apply ring-2` inside a `:focus` block fell between the two sweeps: the
utility sweep wants a `focus-visible:` prefix that is not there (the focus state
is in the selector), and the property sweep wants `property: value`, which
`@apply` is not. Closed by extracting the geometry list into GEOMETRY_UTILITY
and building both the prefixed and the bare form from it, so the two spellings
of one rule cannot drift apart. No `@apply` exists in the codebase today; this
closes the seam rather than waiting for the first one to land in a focus block.
N8 — DESIGN.md's rejected-candidates table showed `border-accent` as 1.41:1 in
both columns without saying against what. Unchecked it is 1.41:1 against the
border it replaces and 5.19:1 against the page; the first is the number that
matters, because a focus treatment has to be told apart from what was there a
moment ago. Added the comparison surface as a column, since the surface is what
decides each row.
Mutation-tested all three scanners again:
padding after a nested block -> caught (passed before this commit)
@apply ring-2 in a :focus block -> caught (passed before this commit)
@apply bg-accent (painted) -> correctly NOT caught, no false positive
Verification: design suite 173 passing, frontend 252 files / 5166 tests,
quality:fix clean, 32 utility and 28 CSS regex cases green after the refactor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Address Review (re-review) —
|
| Mutation | Before 2e491f7c |
After |
|---|---|---|
padding: 4px after a nested block in :focus |
✅ passed (hole) | ✗ caught |
@apply ring-2 ring-offset-2 in a :focus block |
✅ passed (hole) | ✗ caught |
@apply bg-accent (painted control) |
passes | passes — no false positive |
On the F4 arithmetic
Noted that you re-derived the numbers and found your own original border-accent suggestion was measured against --input (the radio/switch rest border) rather than the checkbox's border-primary. That is the kind of correction that makes a review worth running twice — thank you for checking it rather than taking the rejection at face value.
Summary
- ✅ Addressed: 3 — F7 🟡, F8 🟡, N8 🟢
- ⏭️ Skipped: 0
- 📝 Commits: 1 —
2e491f7c, plus7b4e871innodespace-docs - 🧪 Tests: passing — design 173, frontend 252 files / 5166 tests, quality:fix clean, 32 utility + 28 CSS regex cases green after the
GEOMETRY_UTILITYrefactor
Re-Review Decision
Decision: NO RE-REVIEW NEEDED
Rationale: The two code changes are narrow and each is pinned by a mutation test that demonstrably failed before the fix and passes after. F7 is a one-token change to a regex whose behaviour is verified against 28 cases. F8 adds a check built from an existing verified list rather than a new one, and the refactor that enabled it was re-verified against the same 32 utility cases as before. N8 is documentation only. No behaviour visible to a user changed in this commit — nothing shipped here alters what a control looks like.
The substantive work (the focus idiom, the 2.4.7 exception, the scanner rewrite) was reviewed in the two prior rounds and is unchanged.
Closes #2718.
A focused control now adopts its own hover appearance instead of gaining a ring. Focus and hover mean the same thing to the person using the app — "this is the control you are about to act on" — so they look the same, and a treatment built only from
background-colorcannot move the control or its siblings.The hard constraint
A focus treatment may change painted properties only. Three families are banned, for three different reasons, and only the first is literally a reflow:
border-width/paddinggenuinely reflow.border: 0→border: 2pxadds 4px to the box and shoves every sibling after it. This is what makes a form twitch line by line as you tab through it.outlinewith a negative offset does not reflow, but paints inside the element's edge, so the control reads as having suddenly grown a thick inner border.ring-offset-*does not reflow either, but paints a gap plus a ring around the control, visually enlarging its footprint — indistinguishable from movement.All three are banned together so nobody has to re-derive which kind of "doesn't move" a given property offers.
border-colorstays allowed: it repaints an existing border without touching its width.What changed
Button×6 variantsoutline-none, no indicator at allselect-trigger, 2 schema triggersaccentfilltabs-triggerring-2 ring-offset-2accentfilltabs-contentcheckboxring-ring/50at 1.84:1switch,radio-group-item:focusdialog-contentclose:focus+ring-2accent,focus-visiblerelationship-viewer-modal×5ring-1accentapp-shelloutline: 2pxon:focus-withinchat-input,model-manager×2box-shadowringborder-coloronlyinput×2ring-offset-backgroundTwo defects the issue had not catalogued
Its survey covered Tailwind utilities; these are hand-written CSS, so nothing in the original table reached them.
app-shellcarriedoutline: 2px solid; outline-offset: 2pxon:focus-within— which drew a ring around the entire application window whenever anything inside it held focus. This is the idiom removed app-wide in Split-Pane Layout System #300; it survived because:focus-withinwas in nobody's grep.chat-inputandmodel-manager(×2) paired aborder-colorchange withbox-shadow: 0 0 0 2px hsl(var(--ring) / 0.2)— the same theme-blind alpha compositing the checkbox had.The trap a geometry ban cannot catch
A fill that moves nothing but is indistinguishable from the page satisfies every rule above and still shows the user nothing. This bit twice during implementation:
bg-muted, matching how neutral hovers are done elsewhere.--mutedis 1.10:1 against--backgroundin light mode.secondarywas givenfocus-visible:bg-secondary/80to mirror its hover, which composites to 1.08:1.Measured across both themes,
--accentis the only non-brand token clearing the 3:1 non-text floor (5.19:1 light, 3.75:1 dark), so the chrome surface uses it. Hover can afford a subtle shift because the pointer is already on the control; focus cannot, because it is the only thing telling a keyboard user where they are. Both traps now have assertions rather than depending on someone remembering to measure.Controls that get nothing
Checkbox, switch and radio each show their state with their fill, so borrowing that fill for focus would make a focused-unchecked box read as checked. The only surface left is a border — the geometry idiom this change exists to prevent. A tab panel is likewise a scroll container rather than a control; there is nothing in it for
Enterto activate.The editor surface is untouched.
Tabis bound to indent/outdent there rather than focus traversal, and the four sites carrying/* Removed :focus-visible border */keep that decision.Test
focus-treatment.test.ts(10 tests) sweeps every source file for focus utilities and hand-written:focusblocks. It is a text scan rather than a render because Happy-DOM applies no Tailwind stylesheet — a computed style reports an empty background for a correct class and a forbidden one alike, and would pass either way.Mutation-tested against four regressions, each caught by the intended assertion:
ring-2 ring-offset-2restored on switchborder-width: 2pxin a CSS:focusrulefocus-visible:reverted tofocus::focussweepsecondary/80invisible alpha fill restoredAlso verified through PostCSS that all 11 focus classes compile and resolve to their intended tokens — the
--*-hoverones bare and opaque, as the config intends.Known gap, stated rather than hidden
--accent-foregroundon--accentis 3.40:1 in light mode, below the 4.5:1 text floor. It is a pre-existing property of the accent pair, already shipped on sevenhover:bg-accentsites, which this change inherits rather than introduces. The test pins the figure so it cannot silently worsen, and #2724 tracks the fix (light--accent-foregroundis near-black on a dark fill — almost certainly copied from--secondary-foreground).Verification
test:all,cargo build --bin nodespaced,test:e2equality:fixclean: eslint, design tokens (134 known, unchanged), svelte-check 0 errors across 2543 files, cargo fmt + clippyDESIGN.mdupdated innodespace-docs(bfe6b7f)🤖 Generated with Claude Code