UI Restyle Contract Tests#
test/ui-restyle-contract.test.ts enforces CSS/JSX pairing invariants for the Venice restyle — a set of design system changes that introduced 13 core component classes across the UI modules and src/ui/styles.css. Because there is no build step that pairs JSX class names to CSS selectors, a rename on either side silently drops the design rather than producing an error . These tests close that gap via regex scans over the raw file contents at test time .
How it works#
The test file reads the UI source as a raw string using the shared readUiSource() helper from test/ui-source.ts and asserts on source substrings rather than importing or rendering the module. This sidesteps the module-level createRoot side-effect in main.tsx (see Session List UI Constraints). The helper concatenates all UI modules under src/ui/ so that the tests survive moving code between files.
Invariants enforced#
1. JSX ↔ CSS class pairing
Each class in RESTYLED_CLASSES must appear both in a JSX className attribute and as a top-level CSS selector. The 13 classes are:
stat-grid, stat-card, stat-icon, page-heading, inline-heading, nav-group-label, date-range-buttons, primary-button, secondary-button, topbar-search, theme-toggle, badge, brand
Regex used:
- JSX:
className=[{"][^"]*\b${className}\b - CSS:
^\\.${className}[\\s,{:](multiline)
2. Stat grid layout — no orphaned cards
The analytics stat grid always has exactly 6 <StatCard> components. The test bans auto-fit/auto-fill in .analytics-stat-grid (which previously left the 6th card alone on a short row) and asserts that every explicit column count declared in repeat() evenly divides 6. The test locates the grid in src/ui/views/analytics.tsx using sourceFrom() and counts <StatCard> instances within the grid markup.
3. Grid gap over adjacency selectors
Stat cells must use a grid gap for dividers, not .stat-card + .stat-card (which breaks when the grid wraps). The test asserts .stat-grid has gap: 1px and background: var(--line) for tinted hairlines, and that .stat-card uses background: var(--canvas) — no border-radius or fill.
4. Table headers — sentence case
th styles must have text-transform: none and letter-spacing: 0 (the uppercase treatment predated the Venice restyle). Monospace font is also prohibited on table headers.
5. Persistent sticky headers stay opaque
.topbar and .thread-header must declare position: sticky and background: var(--canvas) and must not use backdrop-filter. The share-review-sheet header/footer sticky sections must use background: var(--surface) and also ban backdrop-filter.
6. backdrop-filter allowlist — transient overlays only
Backdrop blur creates a compositor render surface resampled on every intersecting frame. Applying it to per-row chrome (e.g., .badge) caused checkerboarding during fast flings. The test scans all CSS rule blocks containing backdrop-filter and asserts every match is one of these five selectors :
| Allowed selector | Element |
|---|---|
.command-palette-backdrop | Command palette backdrop |
.command-palette | Command palette itself |
.share-review-backdrop | Share/review sheet backdrop |
.overflow-menu-popover | Overflow menu popover |
.sidebar-backdrop.is-open | Open sidebar backdrop |
Any backdrop-filter outside this allowlist fails the test.
7. Display serif font weight
The display serif (--font-serif) may only be used at weight 400. The test maps --font-weight-* tokens, finds all CSS blocks that set font-family: var(--font-serif), and asserts every font-weight resolves to 400. It also verifies the shorthand token --text-serif-xl-regular carries weight 400.
Maintenance notes#
- A failing test usually means a class was renamed on one side without updating the other, or a new
backdrop-filterwas added outside the allowlist — not necessarily a behavioral regression. The fix is either to align the class name in both files or to extend the allowlist if the new element genuinely qualifies as a transient overlay. - This file follows the same
readUiSource()testing pattern astest/ui-loading-state.test.ts,test/ui-sync-copy.test.ts, andtest/ui-interaction-contracts.test.ts.