Skip to content

Commit 945c353

Browse files
OneStepAt4timeCopilotHephaestus
authored
feat(dashboard): Command Center redesign (mission-control DNA) (#4814)
* docs(dashboard): add Command Center design system contract DESIGN.md is the brand contract every redesign slice reads. DNA: mission-control (deep-space navy, amber telemetry, cyan health, mono data, alert hierarchy) + Linear-grade craft (Inter 510 signature weight, negative letter-spacing, keyboard-first density). Aegis is a command center for an agent fleet, not a SaaS dashboard — the redesign makes that truth visible. Token-exact (hex + CSS var mapping) so aegis-driven Claude Code sessions can execute slices in parallel against one source of truth. Defines 10 ordered redesign slices (tokens → status → mono sweep → KPI/table/palette/shell/detail → page pass → cuts → polish). Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): apply command-center design tokens + Inter Slice 1 of the Command Center redesign (dashboard/DESIGN.md). Token foundation only — no component .tsx changes. - @theme palette -> mission-control DNA: deep-space navy surfaces (#0b1120 / #111827 / #1a2535 / #1e3a5f), amber primary accent (#ffb800), cyan health (#00d4ff), alert/warning/success realigned. - Text -> cool-white #e8f0fe / #8ba3c7 / #4a6080 (WCAG AA on navy). - CTA pair -> amber bg + navy text. - Retire purple: --color-accent-purple aliased to --color-accent (deprecated) so existing references resolve. - Status dot tokens remapped to the §4 status system. - Fonts: DM Sans -> Inter (cv01/ss03/cv11 feature settings); mono stays JetBrains Mono. Accessibility fonts (OpenDyslexic, Atkinson) kept. Existing CSS variable names preserved (values changed only) so all current Tailwind utilities and component styles resolve without code edits. build:dashboard passes; verified live (body bg #0b1120, Inter). Executed via an aegis-driven Claude Code session (acceptEdits) reading DESIGN.md as the contract. Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): centralize status -> color (Command Center §4) Slice 2. Introduces a single source of truth for status colors so every status dot/badge/pill draws from one map instead of scattered hardcoded token tables. - utils/statusStyles.ts: canonical StatusKey -> {dotColor,label,className} map per DESIGN.md §4 (cyan idle, amber working, warning awaiting/permission/stalled, success completed, danger error/killed/crashed, muted unknown). Includes getStatusStyle(). - StatusDot.tsx, StallBadge.tsx: drop inline STATUS_COLORS / STALL_COLOR_CLASSES; source color from getStatusStyle. Layout/copy/ pulse/flash untouched. - 41-test contract suite covers each status + unknown fallback; tsc clean; build:dashboard passes. Remaining status-color consumers can adopt getStatusStyle in later slices. Executed via an aegis-driven Claude Code session (acceptEdits) reading DESIGN.md. Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): mono-for-values typography sweep (Command Center §2) Slice 3. Applies the "values are mono" rule from DESIGN.md §2: every numeric reading (tokens, ms, latency, cost, session IDs, timestamps, counts, metric percentages) renders in JetBrains Mono; labels and prose stay Inter. Mechanical, high-impact toward the command-center feel. Touched value renderers in cost/overview/session components and the Analytics/Cost/Metrics pages. Layout and logic unchanged; only the value element gets font-mono. tsc clean; build:dashboard passes. Executed via an aegis-driven Claude Code session (acceptEdits) reading DESIGN.md. Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): command-center KPI/telemetry cards (§4) Slice 4. Restyles overview MetricCard(s) + analytics KPIBanner to the Command Center KPI pattern: #111827 surface, 1px #1e3a5f border, 16px pad, uppercase Inter 590 11px muted label, big JetBrains Mono value, cyan delta/sparkline. Data/props/behavior unchanged; className/style swaps to design tokens only. tsc clean; build:dashboard passes. Executed via an aegis-driven Claude Code session (acceptEdits) reading DESIGN.md. Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): command-center data tables (§4) Slice 5. SessionTable, VirtualizedSessionList, SessionMobileCard → Command Center table pattern: 1px row dividers (--color-border-subtle), uppercase muted header row on #0b1120, status cell via getStatusStyle (dot+label), numeric cells font-mono, hover --color-surface-hover, active row = 2px left amber border + surface-hover bg. Dense rows. Column set, sorting, virtualization, props unchanged. tsc clean; build passes. Executed via an aegis-driven Claude Code session (acceptEdits). Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): command-center app shell (§4) Slice 6. Sidebar + Header → console aesthetic: - Sidebar: solid #060912 (--color-void-deep) instead of frosted glass; borders -> --color-border; active nav item = 2px left amber (--color-accent) bar + --color-surface-hover bg + amber text (was cyan); hover rows use --color-surface-hover consistently. - Header: solid #0B1120 bg (--color-void) instead of frosted; border -> --color-border. ⌘K kbd stays mono; PREVIEW badge stays amber. Command palette + brand wordmark unchanged this slice. Layout/routing/ collapse behavior identical. tsc clean; build passes. Executed by hand where the aegis-driven session bailed mid-investigation; slices 1-5 remain aegis-driven. Generated by Hephaestus (Aegis dev agent) * refactor(dashboard): command-center session detail (§4) Slice 7. SendContinueButton, SessionHeader, session-detail MessageFooter -> console aesthetic: amber CTA send button, mono hints, token borders/ surfaces. Transcript/approval-chrome touched where these components own them. Behavior/props identical; className/style swaps to tokens. tsc clean; build passes. Executed via an aegis-driven Claude Code session. Generated by Hephaestus (Aegis dev agent) * test(dashboard): fix 9 failing tests for Command Center redesign Fixes PR #4814 CI failures: - cls-regression.test.tsx: Update ROW_HEIGHT 52→40, GROUP_ROW_HEIGHT 44→36, transition assertion to match new transition-colors shorthand - a11y-css.test.ts: Update focus ring assertion from box-shadow to outline (matches DESIGN.md §1 amber focus ring) - MetricCards.mobile-responsive.test.tsx: Update selectors from .card-glass to new rounded-[10px] container structure - SessionCostTable.test.tsx: Update cost assertions to handle split text nodes (header total + row value both render $1.25) All 9 previously-failing tests now pass. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Hephaestus <hephaestus@aegis.dev>
1 parent 04177f1 commit 945c353

33 files changed

Lines changed: 700 additions & 265 deletions

dashboard/DESIGN.md

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Aegis — Command Center Design System
2+
3+
> The control plane of Claude Code. This is the brand contract every agent and
4+
> contributor reads before touching the dashboard. One source of truth.
5+
6+
**DNA:** NASA/SpaceX mission control (deep-space navy, amber telemetry, cyan
7+
health, monospace data, alert hierarchy, "readable at 3 m in low light by
8+
someone who hasn't slept in 18 hours") + Linear-grade craft (Inter 510
9+
signature weight, aggressive negative letter-spacing at display, ultra-thin
10+
semi-transparent borders, keyboard-first density).
11+
12+
**North star:** Aegis is a **command center for an agent fleet** — not a SaaS
13+
dashboard. Every screen should feel like a console you trust to fly 100 agents
14+
unsupervised: dense, calm, unambiguous, fast. Amber-on-navy is the signature.
15+
Distinctive > fashionable.
16+
17+
---
18+
19+
## 1. Color
20+
21+
All tokens are CSS variables in `dashboard/src/index.css` `@theme`. Hex is
22+
authoritative; Tailwind utilities derive from them.
23+
24+
### Surface palette (the canvas)
25+
26+
| Token | Hex | Role |
27+
|-------|-----|------|
28+
| `--color-void` | `#0B1120` | Page canvas — deep-space navy |
29+
| `--color-void-deep` | `#060912` | App shell / off-screen depth |
30+
| `--color-surface` / `--color-void-dark` | `#111827` | Panels, cards, elevated areas |
31+
| `--color-surface-hover` / `--color-void-light` | `#1A2535` | Interactive surface hover |
32+
| `--color-surface-active` | `#1E3A5F` | Selected / active panel |
33+
| `--color-border` / `--color-void-lighter` | `#1E3A5F` | Panel dividers, grid lines |
34+
| `--color-border-subtle` | `#162035` | Inner dividers, minor separation |
35+
36+
Rule: surfaces step up in luminance, never in hue. No blue-tinted "SaaS
37+
slate" — backgrounds are warm-neutral navy.
38+
39+
### Data palette (telemetry values)
40+
41+
| Token | Hex | Role |
42+
|-------|-----|------|
43+
| `--color-accent` | `#FFB800` | **Primary telemetry / brand amber** — key metrics, CTAs, brand mark |
44+
| `--color-accent-cyan` | `#00D4FF` | Healthy / active / links — agent "alive" signal |
45+
| `--color-danger` | `#FF4757` | Critical alert, error, killed |
46+
| `--color-warning` | `#FF9F43` | Degraded / stalled / awaiting approval |
47+
| `--color-success` | `#26DE81` | Nominal / completed |
48+
| `--color-info` | `#3B82F6` | Informational / neutral event |
49+
50+
Amber is the signature. It appears on CTAs, the primary metric in any readout,
51+
the brand mark, and the focus ring. Cyan is the "system nominal" color. The two
52+
together (amber value + cyan status dot) is the Aegis motif — use it.
53+
54+
All data colors on `#111827` must pass WCAG AA (≥ 4.5:1).
55+
56+
### Text
57+
58+
| Token | Hex | Role |
59+
|-------|-----|------|
60+
| `--color-text-primary` | `#E8F0FE` | Primary readable text (cool white, not pure) |
61+
| `--color-text-muted` | `#8BA3C7` | Labels, secondary copy |
62+
| `--color-placeholder` | `#4A6080` | Timestamps, metadata, placeholders |
63+
64+
### CTA
65+
66+
| Token | Value | Role |
67+
|-------|-------|------|
68+
| `--color-cta-bg` | `--color-accent` (`#FFB800`) | Primary action — "launch / approve" |
69+
| `--color-cta-text` | `--color-void` (`#0B1120`) | High-contrast navy on amber |
70+
| `--color-cta-bg-hover` | `#FFC933` | Amber hover |
71+
72+
Secondary actions are ghost: `rgba(255,255,255,0.04)` bg, `--color-border`
73+
1px, `--color-text-primary` text.
74+
75+
### Forbidden
76+
- Purple/violet accent (`#8b5cf6`) — retire it. Aegis is amber + cyan.
77+
- Gradients on surfaces. Depth comes from luminance steps + 1px borders, not
78+
gradients. (A single subtle amber→transparent glow on the active CTA is the
79+
only allowed gradient.)
80+
- Pure black (`#000`) or pure white (`#fff`) as surfaces.
81+
82+
---
83+
84+
## 2. Typography
85+
86+
**Inter** (replaces DM Sans) as UI/type, **JetBrains Mono** for all data.
87+
88+
```
89+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
90+
--font-mono: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
91+
```
92+
93+
Enable Inter OpenType features globally: `font-feature-settings: "cv01", "ss03", "cv11"`.
94+
Signature weight is **510** (between regular and medium) — the Aegis UI weight.
95+
96+
### Scale (display uses negative letter-spacing; data uses mono)
97+
98+
| Role | Family / weight | Size / line-height | Letter-spacing |
99+
|------|-----------------|--------------------|----------------|
100+
| Display (hero, page title) | Inter 590 | 40–56px / 1.05 | -1.5px |
101+
| H1 page | Inter 590 | 28px / 1.15 | -0.8px |
102+
| H2 section | Inter 590 | 20px / 1.2 | -0.4px |
103+
| Body | Inter 510 | 14px / 1.5 | 0 |
104+
| Label / UI | Inter 510 | 13px / 1.4 | 0 |
105+
| Caption / meta | Inter 450 | 12px / 1.4 | +0.1px (uppercase labels only) |
106+
| **Telemetry value** | JetBrains Mono 500 | 13–28px | 0 |
107+
| **Status / timestamp / ID** | JetBrains Mono 400 | 12–13px | 0 |
108+
109+
**Rule:** every number a user reads as "a reading" — metric, token count,
110+
duration, latency, cost, session ID, timestamp — is **JetBrains Mono**. Text
111+
prose is Inter. If it's a value, it's mono. This single rule does more for the
112+
"command center" feel than any color change.
113+
114+
Uppercase labels (section headers, table column heads, KPI captions) use
115+
`text-transform: uppercase; letter-spacing: +0.08em; font-size: 11px;
116+
font-weight: 590; color: --color-text-muted`.
117+
118+
---
119+
120+
## 3. Spacing, radius, borders, depth
121+
122+
- **Spacing scale:** 4 / 8 / 12 / 16 / 24 / 32 / 48 px. Cards pad 16–24.
123+
- **Radius:** 6px controls, 10px cards, 14px panels/modals. **Not** pill-round
124+
(except status dots and the command-palette input). 6–14px reads
125+
"engineered", not "consumer".
126+
- **Borders:** 1px `--color-border` (`#1E3A5F`) default;
127+
`--color-border-subtle` (`#162035`) for inner divisions. Mission-control
128+
grids are visible structure — don't hide borders, celebrate them at 1px.
129+
- **Shadow / depth:** prefer 1px border + one luminance step over blur shadows.
130+
Elevated surfaces (modals, popovers): `0 0 0 1px #1E3A5F, 0 8px 24px rgba(0,0,0,0.5)`.
131+
- **Grid lines:** tables and telemetry grids show 1px `--color-border-subtle`
132+
row dividers. No zebra striping — dividers carry it.
133+
134+
---
135+
136+
## 4. Signature components
137+
138+
### Status dot + telemetry pair (the motif)
139+
Every agent/session/metric pairs a **cyan/amber/red dot** with a **mono value**:
140+
`● idle 2.3k tok` — dot encodes state, mono value is the reading, Inter label
141+
names it. This pair repeats everywhere (session row, KPI, header, badge).
142+
143+
### Status color map (single source — reuse everywhere)
144+
| State | Dot/text | Hex |
145+
|-------|----------|-----|
146+
| idle / ready | cyan | `#00D4FF` |
147+
| working / running | amber (pulsing) | `#FFB800` |
148+
| waiting_for_input / awaiting_approval | warning | `#FF9F43` |
149+
| permission_prompt | warning | `#FF9F43` |
150+
| completed / nominal | success | `#26DE81` |
151+
| error / crashed / killed | danger | `#FF4757` |
152+
| stalled | warning (slow pulse) | `#FF9F43` |
153+
| unknown | text-muted | `#4A6080` |
154+
155+
### KPI / telemetry card
156+
`#111827` surface, 1px `#1E3A5F` border, 16px pad. Top: uppercase Inter 590
157+
11px muted label. Center: huge JetBrains Mono value (amber if primary, else
158+
text-primary). Bottom: delta + sparkline (cyan). No icon unless it earns it.
159+
160+
### Data table (session list)
161+
1px row dividers `#162035`, header row uppercase 11px muted on `#0B1120`.
162+
Status = dot+label. IDs/timestamps/tokens = mono. Hover row = `#1A2535`.
163+
Active row = left 2px amber border + `#1A2535`. Dense: 36–40px row height.
164+
165+
### Command palette (⌘K)
166+
Full-width-minus-margins overlay, `#0F172A`, 1px `#1E3A5F`, 14px radius.
167+
Input: Inter, 18px, placeholder muted. Results: mono for session IDs / hotkeys,
168+
Inter for labels. Selected row: `--color-surface-active` + 2px amber left bar.
169+
170+
### Primary CTA
171+
Amber bg, navy text, Inter 590 13px, 6px radius, 1px amber-hover border.
172+
Reserved for the single most important action on the screen (Launch session,
173+
Approve). Never two amber CTAs in one viewport.
174+
175+
### Approval / permission toast
176+
Border-left 3px `--color-warning`. `#111827` surface. Mono session ID +
177+
Inter tool name. Actions: Approve (amber CTA) / Reject (ghost danger).
178+
179+
---
180+
181+
## 5. Motion
182+
183+
- Status transitions are calm: dot color crossfade 150ms ease.
184+
- "working" agent dot pulses amber (opacity 1→0.4→1, 1.6s) — the only ambient
185+
motion on a static screen. It signals "the system is alive".
186+
- No bounce, no spring, no parallax on data. Motion conveys state, never
187+
decoration.
188+
- Loading = a 1px amber top-progress bar (indeterminate), not a spinner, for
189+
full-page loads. Skeletons for content.
190+
191+
---
192+
193+
## 6. Voice & copy
194+
195+
Operator-console voice. Terse, declarative, no marketing words.
196+
- "3 agents running · 2 awaiting approval" not "You have agents working!"
197+
- Statuses lowercase: `idle`, `working`, `awaiting approval`.
198+
- Numbers always paired with units: `2.3k tok`, `412 ms`, `$0.42`.
199+
- Empty states name the next action: "No sessions — press ⌘N to launch."
200+
201+
---
202+
203+
## 7. Redesign slices (ordered — each is one aegis-driven PR)
204+
205+
Each slice is bounded enough for a single Claude Code session (via aegis,
206+
`permissionMode: acceptEdits`) to execute against this DESIGN.md. Verify gate
207+
+ visual before merging.
208+
209+
1. **Tokens + fonts.** Rewrite `src/index.css` `@theme` to the palette/type
210+
above. Add `@fontsource/inter/latin.css`, retire DM Sans + purple. Map
211+
every renamed token so existing utilities resolve. (Foundation — everything
212+
else depends on this.)
213+
2. **Status system.** Centralize the status→color map (§4) into one module
214+
(`utils/statusStyles` or similar); replace the scattered `--color-dot-*`
215+
ad-hoc usage across components with it. Touches: SessionTable, StallBadge,
216+
SessionMobileCard, status pills everywhere.
217+
3. **Mono-for-values sweep.** Find every numeric reading (tokens, ms, cost, IDs,
218+
timestamps) rendered in Inter and switch to `--font-mono`. Mechanical,
219+
high-impact.
220+
4. **KPI / telemetry cards.** Restyle overview KPI banner + metrics cards to
221+
the §4 KPI pattern (uppercase label, big mono value, sparkline).
222+
5. **Data tables.** SessionTable + VirtualizedSessionList to the §4 table
223+
pattern (dividers, active-row amber bar, dense rows).
224+
6. **Command palette + app shell.** Sidebar, header, ⌘K palette to Command
225+
Center (navy surfaces, amber active, mono hotkeys). Brand mark restyle.
226+
7. **Session detail.** Transcript/terminal chrome, approval toast, the
227+
"Send continue" composer → console aesthetic.
228+
8. **Page pass.** Remaining pages (Pipelines, Audit, Metrics, Cost, Analytics,
229+
Templates, Routines, Settings, AuthKeys, Inbox, Activity) — apply tokens,
230+
cut visual noise, enforce component patterns.
231+
9. **Cuts.** Remove dead/redundant UI, collapse low-value sections, tighten
232+
information hierarchy. (The "tagli" the brief asks for.)
233+
10. **Polish + a11y.** Focus rings (amber), contrast audit (AA on all
234+
data-on-surface pairs), keyboard nav, reduced-motion respect, loading
235+
skeletons.
236+
237+
**Per-slice definition of done:** `npm run build:dashboard` passes; the
238+
slice's touched components render correctly in the running dashboard; a
239+
before/after screenshot is captured; no new `any`/`as`; gate green on CI.

dashboard/package-lock.json

Lines changed: 10 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"dependencies": {
1818
"@fontsource/atkinson-hyperlegible": "^5.2.8",
1919
"@fontsource/dm-sans": "^5.2.8",
20+
"@fontsource/inter": "^5.2.8",
2021
"@fontsource/jetbrains-mono": "^5.2.8",
2122
"@tanstack/react-virtual": "^3.14.3",
2223
"canvas-confetti": "^1.9.4",

dashboard/src/__tests__/MetricCards.mobile-responsive.test.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,32 @@ describe('MetricCards mobile responsive', () => {
5757
vi.restoreAllMocks();
5858
});
5959

60-
it('Delivery Rate card uses flex-col on mobile and sm:flex-row on larger screens', async () => {
60+
it('Delivery Rate section uses flex-col on mobile and sm:flex-row on larger screens', async () => {
6161
render(<MemoryRouter><MetricCards /></MemoryRouter>);
6262

6363
await act(async () => { await vi.runAllTicks(); });
6464

65-
// Find the card containing "Delivery Rate"
65+
// Find the heading and traverse up to the container section
6666
const heading = screen.getByText('Delivery Rate');
67-
const card = heading.closest('.card-glass');
68-
expect(card).not.toBeNull();
67+
// The container is the parent div with rounded-[10px] border etc.
68+
const container = heading.closest('div[class*="rounded-[10px]"]');
69+
expect(container).not.toBeNull();
6970

7071
// The flex container holding RingGauge + text details
71-
const flexContainer = card!.querySelector('.flex.flex-col');
72+
const flexContainer = container!.querySelector('.flex.flex-col');
7273
expect(flexContainer).not.toBeNull();
7374
expect(flexContainer!.classList.contains('sm:flex-row')).toBe(true);
7475
});
7576

76-
it('Delivery Rate card uses responsive padding (p-4 sm:p-5)', async () => {
77+
it('Delivery Rate section uses responsive padding (p-4)', async () => {
7778
render(<MemoryRouter><MetricCards /></MemoryRouter>);
7879

7980
await act(async () => { await vi.runAllTicks(); });
8081

8182
const heading = screen.getByText('Delivery Rate');
82-
const card = heading.closest('.card-glass');
83-
expect(card).not.toBeNull();
84-
expect(card!.classList.contains('p-4')).toBe(true);
85-
expect(card!.classList.contains('sm:p-5')).toBe(true);
83+
const container = heading.closest('div[class*="rounded-[10px]"]');
84+
expect(container).not.toBeNull();
85+
expect(container!.classList.contains('p-4')).toBe(true);
8686
});
8787

8888
it('Delivery Rate text content is centered on mobile, left-aligned on sm+', async () => {
@@ -91,23 +91,23 @@ describe('MetricCards mobile responsive', () => {
9191
await act(async () => { await vi.runAllTicks(); });
9292

9393
const heading = screen.getByText('Delivery Rate');
94-
const card = heading.closest('.card-glass');
94+
const container = heading.closest('div[class*="rounded-[10px]"]');
9595
// The text details div
96-
const textDiv = card!.querySelector('.flex-1.space-y-3');
96+
const textDiv = container!.querySelector('.flex-1.space-y-3');
9797
expect(textDiv).not.toBeNull();
9898
expect(textDiv!.classList.contains('text-center')).toBe(true);
9999
expect(textDiv!.classList.contains('sm:text-left')).toBe(true);
100100
});
101101

102-
it('RingGauge renders inside the delivery rate card', async () => {
102+
it('RingGauge renders inside the delivery rate section', async () => {
103103
render(<MemoryRouter><MetricCards /></MemoryRouter>);
104104

105105
await act(async () => { await vi.runAllTicks(); });
106106

107107
const heading = screen.getByText('Delivery Rate');
108-
const card = heading.closest('.card-glass');
108+
const container = heading.closest('div[class*="rounded-[10px]"]');
109109
// RingGauge renders an SVG element
110-
const svg = card!.querySelector('svg');
110+
const svg = container!.querySelector('svg');
111111
expect(svg).not.toBeNull();
112112
});
113113
});

dashboard/src/__tests__/a11y-css.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ describe('index.css — focus ring tokens', () => {
2525

2626
it('applies focus ring via :focus-visible', () => {
2727
expect(css).toContain(':focus-visible');
28-
expect(css).toContain('box-shadow: var(--focus-ring-offset), var(--focus-ring)');
28+
// Command Center redesign uses outline-based focus ring (DESIGN.md §1)
29+
expect(css).toContain('outline: 2px solid var(--color-accent)');
2930
});
3031

3132
it('removes outline for :focus:not(:focus-visible)', () => {

0 commit comments

Comments
 (0)