Skip to content

Agentic UI: Close the CSS design-system gaps with tokens, shared recipes, surface stepping, scoped dark mode and a token lint - #5390

Merged
youknowriad merged 19 commits into
trunkfrom
add-css-design-tokens
Oct 25, 2026
Merged

youknowriad merged 19 commits into
trunkfrom
add-css-design-tokens

Conversation

@youknowriad

@youknowriad youknowriad commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Related issues

  • Related to the Oct 25 comparison of trunk's design system with the rebuild's. Trunk's token set came out more complete, with z-index layers, focus, scoped dark mode and reduced transparency. Its CSS discipline came out behind: no spacing or line-height tokens, one-off values throughout, repeated recipes and three dark-mode selector forms. This PR closes those gaps in one change.

How AI was used in this PR

Claude Code made the changes. It used dev-time postcss codemods (not committed) to swap literals for tokens and repeated declarations for shared recipes without touching the formatting. A computed-style harness then checked every rendered element against trunk across 39 scenes, in Chromium and WebKit, light and dark, plus motion passes. A subagent matched each off-scale radius to the same element in the rebuild. I reviewed the token names, the snapped radii, the recorded exceptions and the lint rule.

Proposed Changes

The agentic UI's stylesheets now draw on a complete token set and share their recurring recipes. A lint rule keeps them that way. The UI looks the same apart from a handful of radii and weights that now match the rebuild (listed below). The CSS shrinks by about 90 lines.

Tokens (lib/theme/tokens.css), used in place of ~800 literals:

  • Spacing: --space-*, a 4px grid numbered like Tailwind's scale (--space-1_5 is 6px). It covers margins, padding, gaps and insets, including negatives and values inside calc().
  • Line heights: a --text-*--line-height for each text size (line-height: 20px was hand-written 41 times), plus --leading-* for the unitless ratios.
  • Text: new sizes --text-2xs (11px), --text-md (15px) and --text-2xl (20px). --text-2xs sits below the existing --text-xxs (12px). The names follow the brief but read alike, so a rename may be worth it.
  • Radii: --radius-2xl, --radius-3xl and a pill, --radius-full. The pill replaces 999px, 9999px and 50%.
  • Elevation and layers: --shadow-raised, plus --z-sticky, --z-overlay, --z-band and --z-sidebar for the layers inside the workspace.
  • Motion and focus: literals that equal a motion or focus token now use it.
  • Breakpoints: 520, 600, 639 and 640px become one breakpoint, ( width < 640px ) (the rebuild's sm).

Shared recipes: declarations repeated across files are now defined once in lib/theme/recipes.module.css and pulled in with CSS Modules composes. That file sits in the ui layer, so a component's own rules still override them. The recipes are the ellipsis trio (33 places), the band's header hairline, the 1px inset image outline and the notification dot with its icon mask. The duplicated spin/sitePreviewSpin and pulse/skeletonPulse keyframes become one studio-spin and one studio-pulse.

Icons size themselves at 16px. That comes from LucideProvider and a 16px default on the custom icons. The global svg { 16px } rule goes, along with its data-keep-size escape hatch and the rules that only restated 16px.

Nested surfaces step automatically, as in the rebuild. A surface sets --surface to its fill, and --muted inside it resolves a shade up through @property and @container style() queries. That replaces five hand-patched --muted overrides (composer, command sheet, composer dialog, settings cards, question card). WebKit supports it, and the harness checks it there.

Dark mode follows the nearest scene. Scheme-dependent colors use light-dark(), and code tokens use Shiki's defaultColor: 'light-dark()'. The two non-color cases use the [data-color-scheme='dark'] .x form. No :root[...] or html[...] scheme selectors are left, so a scoped data-color-scheme scene renders its own colors.

Lint and formatting:

  • CSS now goes through ESLint, using the official @eslint/css language. The existing Prettier setup formats it, which ends the var(--x) / var( --x ) mix, and npx eslint --fix covers stylesheets too.
  • A new studio/use-theme-tokens rule flags literal colors, px font sizes, radii, elevation shadows and z-index layers outside lib/theme.
  • The rule allowlists the onboarding illustrations and Wapuu World, which are artwork, and the DESIGN.md view, which renders a site's own design.
  • The remaining literals each carry an eslint-disable-next-line comment with the reason.

Changes you can see, all toward the rebuild:

Element Ours Rebuild Now
Auth, settings and site-settings error boxes 6px, 6px, 4px Alert rounded-lg --radius-lg
Annotation image thumbnail 12px rounded-xl --radius-xl
Composer in field mode, folder picker 9px Input rounded-lg --radius-lg
"Drop files" pill 13px rounded-xl --radius-xl
Sharing empty-state card and its button 12px, 6px rounded-xl, rounded-lg --radius-xl, --radius-lg
Dragged site row 6px drag preview --radius-md --radius-md
Add site summary image 6px PreviewFrame rounded-sm --radius-sm
  • font-weight: 500 becomes --font-weight-medium (450) on the Blueprint upload link, the create-site form labels, the Add site summary title and the settings dialog title.
  • On HiDPI screens, nine focus rings hard-coded at 2px now use --focus-width (1.5px), like the primitives.
  • Five 180ms animations now use --motion-duration-surface, so with reduced motion they run at 120ms.
  • In the browser, at window widths of 520–639px, the Add site summary and the auth actions stack. Before, that happened below 520px and 600px. The Desktop window can't be narrower than 800px.

Recorded and left as they are:

  • Spacing: 3, 7, 7.5, 22 and 30px nudges, the dialog's 70px inset, and the input's 36px icon padding.
  • Line heights that don't pair with their size: 18/24px on --text-xs, 16/17/20/24px on --text-xxs. Also the 1.2–1.4 ratios.
  • Motion: 160ms and 200ms transitions, and animation periods.
  • Weights: 550 and bold.
  • Radii without a token counterpart in the rebuild: the 4px checkbox (rounded-[4px] there too), the 1px drop line, 2px link focus rings, the 4px site icon and transcript previews, the annotation chip, the settings search highlight and the address-bar focus ring.
  • Four compound selectors (.item > .label, .source span and similar) keep the ellipsis trio, because composes needs a single-class selector.

Testing Instructions

  • npm run cli:build:ui && node apps/cli/dist/cli/main.mjs ui --no-open. Browse the sidebar, chats, the composer, settings, Add site, the palette and the guide in light and dark, in Chromium and Safari. Everything should look as on trunk.
  • Check the dark composer, the command sheet and settings cards. Hover fills inside them should still show a shade above the surface.
  • Computed-style diff against trunk (b32accbf3). The harness reads every rendered element's computed values in 39 scenes: the sidebar, chats, markdown, the composer, settings tabs, Add site steps, the palette, menus, notifications, the design system view, guide steps and narrow windows. The table counts what's left after the intended changes above and after known noise. The noise shows up trunk against trunk too: the Usage tab's loading race, Add site's random site name, WebKit's lack of scroll anchoring, and spinners rotating during motion passes.

Each build ran twice. A difference counts only if it's the same in both trunk runs and both branch runs. Add site, Share and the narrow sidebar each had one timing-dependent difference (Blueprint thumbnails or preview list still loading, sidebar mid-slide), so I re-ran those scenes on their own, and they matched trunk exactly.

Pass Scenes Elements Values compared Differences beyond the intended ones
Chromium light 39 55,159 4.44M 0
Chromium dark 39 51,622 4.15M 0
WebKit light 39 54,160 4.36M 0
WebKit dark 39 53,889 4.34M 0
Motion allowed, light / dark 39 each 58,273 / 59,309 0.70M each 0
Reduced motion 39 59,309 0.71M 0

The intended differences in these scenes are the dragged-row and field-composer radii, the 450 weight on "upload a file" and the settings title, and the avatar ring inheriting a 9999px instead of 50% radius (the same 20px circle). The other radius changes are on elements the scenes don't render (error boxes, previews, the drop pill).

  • npm run lint, npm run typecheck and npm test -- apps/ui tools/eslint-plugin-studio pass (869 tests).

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

🤖 Generated with Claude Code

youknowriad and others added 9 commits October 25, 2026 11:42
…er tokens, and use them instead of literals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… global svg rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lors to their scene

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re-css-recipes

# Conflicts:
#	apps/ui/README.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
youknowriad and others added 8 commits October 25, 2026 12:25
…-css-design-tokens

# Conflicts:
#	apps/ui/src/features/chats/session-view/conversation/style.module.css
#	apps/ui/src/features/composer/style.module.css
#	apps/ui/src/features/sidebar/site-list/style.module.css
#	apps/ui/src/features/sites/site-preview/style.module.css
…re-css-recipes

# Conflicts:
#	apps/ui/src/features/chats/session-view/conversation/style.module.css
#	apps/ui/src/features/composer/style.module.css
#	apps/ui/src/features/sites/site-preview/style.module.css
…pe-css-surfaces-and-schemes

# Conflicts:
#	apps/ui/src/features/chats/session-view/conversation/style.module.css
… for colors, type, radii, shadows and layers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	apps/ui/src/features/composer/attachment-chips/style.module.css
#	apps/ui/src/features/composer/style.module.css
#	apps/ui/src/features/notifications/notice-history/style.module.css
#	apps/ui/src/features/sidebar/sidebar-header/style.module.css
#	apps/ui/src/features/sidebar/site-list/style.module.css
# Conflicts:
#	apps/ui/src/features/chats/session-view/conversation/agent-question/style.module.css
#	apps/ui/src/lib/theme/tokens.css
#	apps/ui/src/lib/ui/dialog/style.module.css
#	apps/ui/src/lib/ui/input-group/style.module.css
#	apps/ui/src/lib/ui/popover/style.module.css
#	apps/ui/src/lib/ui/switch/style.module.css
…ions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpmobilebot

wpmobilebot commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 8119f42 vs trunk

app-size

Metric trunk 8119f42 Diff Change
App Size (Mac) 954.21 MB 954.23 MB +0.02 MB ⚪ 0.0%

site-editor

Metric trunk 8119f42 Diff Change
load 1190 ms 1261 ms +71 ms 🔴 6.0%

site-startup

Metric trunk 8119f42 Diff Change
siteCreation 6569 ms 6563 ms 6 ms ⚪ 0.0%
siteStartup 2873 ms 2889 ms +16 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

youknowriad and others added 2 commits October 25, 2026 13:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@youknowriad youknowriad changed the title Agentic UI: Add spacing, line-height, type, radius, elevation and layer tokens Agentic UI: Close the CSS design-system gaps with tokens, shared recipes, surface stepping, scoped dark mode and a token lint Oct 25, 2026
@youknowriad
youknowriad merged commit f6cdd97 into trunk Oct 25, 2026
13 checks passed
@youknowriad
youknowriad deleted the add-css-design-tokens branch October 25, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants