Brand Identity & UX Design System¶
Design Direction¶
Chosen direction: Warm Ops -- a warm, approachable aesthetic with balanced density and spring-physics interactions, combined with semantic state-driven color encoding where every color communicates system status rather than serving as decoration.
Why this direction: Warmth makes an AI operations tool feel approachable without losing professionalism. Tying color exclusively to state (green = rising, amber = attention, red = critical) gives operators instant comprehension of system health. The brand accent is a warm soft blue -- deliberately neutral so that semantic state colors dominate the visual hierarchy. Orange/amber means "attention needed," not "this is SynthOrg."
What was rejected and why: A cool blue-cyan palette (data center aesthetic) was too generic -- indistinguishable from Grafana/Datadog screenshots. A neutral gray palette (no hue) lacked enough identity to be recognizable. A high-energy violet/purple palette was visually fatiguing for sustained 8-hour use. These directions scored well on individual criteria but failed the combination of identity distinctiveness + sustained usability.
Design influences: Linear (clean layout, balanced density), Vercel (status-first design), Dust.tt (warm approachability), Grafana (data density as a user preference).
Color System¶
Semantic Color Tokens¶
Colors are state-driven, not decorative. Every colored element answers: "what is the system telling me?"
| Token | Purpose | Example hex | When to use |
|---|---|---|---|
accent |
Brand, interactive elements, links, focus rings, active nav | #38bdf8 (warm soft blue -- tunable) |
Default state, clickable things, brand identity |
accent-dim |
Muted brand, secondary interactive, onboarding | Derived from accent | Hover states, secondary info, less prominent interactive |
success |
Rising, improving, healthy, completed | #10b981 (emerald) |
Metrics trending up, tasks completed, agents active |
warning |
Declining, degrading, attention needed | #f59e0b (amber) |
Metrics trending down, budget nearing limit, stale tasks |
danger |
Critical, error, immediate action | #ef4444 (red) |
Agent errors, budget exceeded, failed tasks |
text-primary |
Main content text | #e2e8f0 |
Headings, values, primary content |
text-secondary |
Supporting text | #94a3b8 |
Labels, descriptions, secondary info |
text-muted |
Least prominent text | #8b95a5 |
Timestamps, metadata, disabled items |
bg-base |
Page background | #0a0a12 |
Deepest background layer |
bg-surface |
Sidebar, elevated surfaces | #0f0f1a |
Sidebar, panels, raised areas |
bg-card |
Card backgrounds | #13131f |
All card containers |
bg-card-hover |
Card hover state | #181828 |
Card background on mouse-over |
border |
Default borders | #1e1e2e |
Card borders, dividers |
border-bright |
Interactive/hover borders | #2a2a3e |
Focus rings, hover states |
Dynamic Color Assignment¶
Metric cards, sparklines, and trend indicators use colors dynamically based on direction of change:
| Data state | Color token | Rationale |
|---|---|---|
| Improving / rising | success |
Green = things getting better |
| Stable / nominal | accent or text-muted |
Neutral -- no action needed |
| Declining / degrading | warning |
Amber = attention warranted |
| Critical / threshold | danger |
Red = act now |
This ensures operators instantly understand system state from colors alone, without reading values. The same metric card shows green when tasks are completing faster and amber when they are slowing down.
How to Add a New Color Theme¶
Each theme is a single configuration object (~50 lines). All colors are CSS custom properties consumed via var(--theme-*) tokens. To add a new theme:
- Create a new theme config (e.g.
themes/midnight.ts) with all color tokens - Register it in the theme index
- Done -- all components automatically pick up the new palette
No component code changes required. The 5 exploration themes (Ice Station, Warm Ops, Stealth, Signal, Neon) demonstrate this pattern -- each theme is ~50 lines of color token definitions with zero component changes.
Typography¶
Chosen Pairing¶
| Role | Font | Usage |
|---|---|---|
| Monospace | Geist Mono (via @fontsource, self-hosted) | Data values, code, metrics, timestamps, agent names |
| Sans-serif | Geist Sans (via @fontsource, self-hosted) | Labels, descriptions, UI text, headings |
Rationale: Geist was designed by Vercel specifically for developer dashboards. Excellent readability at small sizes, clean number rendering in mono, professional but not clinical in sans.
Typography is a theme axis: other pairings (JetBrains Mono + Inter, IBM Plex Mono + IBM Plex Sans) are available and can be selected independently of colors.
Self-Hosted Fonts¶
All fonts are bundled via @fontsource packages. No external CDN (Google Fonts) dependencies. This ensures:
- No GDPR/privacy issues from third-party font loading
- Consistent rendering regardless of network
- Faster first contentful paint (no font fetch waterfall)
Density¶
Density is an independent user preference, not tied to theme colors.
| Level | Padding | Section gap | Grid gap | Metric size | Body size | Use case |
|---|---|---|---|---|---|---|
| Dense | p-3 (12px) |
gap-3 (12px) |
gap-3 (12px) |
text-2xl |
text-xs |
Power users, large monitors, data-heavy workflows |
| Balanced (default) | p-4 (16px) |
gap-4 (16px) |
gap-4 (16px) |
text-3xl |
text-sm |
General use, comfortable reading distance |
| Medium | p-[14px] |
gap-4 (16px) |
gap-4 (16px) |
text-2xl |
text-xs |
Slightly tighter than balanced |
| Sparse | p-5 (20px) |
gap-6 (24px) |
gap-6 (24px) |
text-3xl |
text-sm |
Presentation mode, low information density tasks |
Token usage: Components must use density-aware token classes (
p-card,space-y-section-gap,gap-section-gap,gap-grid-gap) instead of the raw Tailwind utilities shown above. The raw values are what each token resolves to at each density level.
How to Add a New Density Level¶
Create a new ThemeDensity object with padding, gap, and font size values. Register it in the density index. Components read density from the theme context -- no component changes needed.
Animation¶
Animation is an independent user preference, controlling motion intensity.
| Profile | Card entrance | Hover | Page transition | Status pulse | Use case |
|---|---|---|---|---|---|
| Minimal | 200ms fade | None | Fade only | Subtle | Reduced motion preference, distraction-free |
| Spring | Spring physics | Lift + shadow | Slide | Yes | Playful, responsive feel |
| Instant | No animation | None | None | No | Maximum performance, zero latency feel |
| Status-driven | Fade | None | Fade | Only on state change | Animation earns attention -- only moving things changed |
| Aggressive | Slide + fade + scale | Lift + glow | Scale | Yes + shimmer | High energy, demo/presentation mode |
Recommended default: Status-driven. Animation should communicate state change, not decoration. Static elements stay still; only things that changed move.
Sidebar¶
Sidebar mode is an independent user preference.
| Mode | Behavior | Width | Best for |
|---|---|---|---|
| Rail | Always visible, icon + label | 220px | Standard desktop use |
| Collapsible (default) | Expanded by default, can collapse to icon rail. Remembers user preference. | 220px / 56px | Most users -- full nav when needed, compact when focused |
| Hidden | Hamburger toggle, content gets full width | 240px (overlay) | Maximum content area, presentation |
| Persistent | Always expanded with notification badges | 220px | High-interactivity workflows, many nav items |
| Compact | Always visible, icons prominent, text secondary | 56px | Small screens, secondary monitors |
Persistence¶
Sidebar collapse state is persisted in user preferences. If a user collapses the sidebar, it stays collapsed across sessions until they expand it again.
Theme Architecture¶
Independent Axes¶
The theme system has 5 orthogonal axes that users can configure independently:
This gives users full control without combinatorial explosion in theme definitions. A user can run "warm blue colors + dense layout + IBM Plex fonts + minimal animation + compact sidebar" without any custom theme code.
Implementation Pattern¶
ThemeProvider (React context)
|-- sets CSS custom properties on wrapper div (--theme-accent, --theme-bg-base, etc.)
|-- Tailwind @theme block maps --theme-* to Tailwind utility classes
|-- Components use Tailwind classes (text-accent, bg-bg-card, border-border)
|-- Density/animation read from theme context object
|-- Sidebar mode selects which sidebar component to render
Critical Implementation Note: Tailwind v4 CSS Layers¶
When using Tailwind v4 with @import "tailwindcss", all custom CSS resets MUST be inside @layer base. Tailwind v4 uses CSS cascade layers, and unlayered styles (like * { margin: 0; padding: 0; }) have higher priority than layered utilities, silently overriding all spacing, padding, margin, and gap utilities.
/* WRONG -- breaks all Tailwind spacing utilities */
* { margin: 0; padding: 0; }
/* CORRECT -- respects Tailwind cascade layers */
@layer base {
* { margin: 0; padding: 0; }
}
This was discovered during the design exploration (#765) and caused layout breakage that was difficult to diagnose because the utilities appeared in the generated CSS but had no visual effect.
Dark Mode¶
Dark mode only (confirmed in #762 research). No light mode planned. All color tokens assume dark backgrounds. WCAG AA contrast ratios are validated against dark card/surface backgrounds.
Accessibility¶
- WCAG AA contrast minimum on all text (4.5:1 on backgrounds, 3:1 for large text)
prefers-reduced-motionsupported:AnimatedPresenceusesreducedPageVariants(opacity-only fade), skeleton shimmer disabled via CSS media query, Framer Motion'suseReducedMotion()hook used for runtime detection- Keyboard navigation for all interactive elements
aria-hidden="true"on decorative icons- Escape key closes overlays/drawers
- Storybook a11y enforcement:
parameters.a11y.test: 'error'set globally in.storybook/preview.tsx-- all stories fail on WCAG violations, catching regressions at component development time
Storybook Tooling (v10)¶
The component development environment uses Storybook 10 with native type-safe configuration:
- Config:
defineMain(from@storybook/react-vite/node) anddefinePreview(from@storybook/react-vite) for full TypeScript inference - Addons:
@storybook/addon-docs(autodocs) and@storybook/addon-a11y(WCAG testing). Essentials (backgrounds, controls, viewport, actions) and interactions are built into core - Backgrounds: Selected via
initialGlobals.backgrounds.value = 'dark', which references our--so-bg-basetoken (#0a0a12) throughbackgrounds.options.dark.value, ensuring stories render against the actual brand dark background - Decorator: Global dark-mode wrapper (
div.dark.bg-background.p-4.text-foreground) applies our design tokens to all stories - API mocking: MSW (Mock Service Worker) via
msw-storybook-addon. GlobalmswLoaderinpreview.tsxintercepts API calls. Stories declare handlers viaparameters.msw.handlersusing pre-built handler arrays fromweb/src/mocks/handlers/. All responses use theApiResponse<T>envelope viaapiSuccess()helper
Component Inventory¶
The following shared components live in web/src/components/ui/ and form the building blocks for all dashboard pages. Always compose pages from these -- never recreate equivalent functionality inline.
Core Components¶
| Component | File | Props | Purpose |
|---|---|---|---|
StatusBadge |
status-badge.tsx |
status, label?, pulse? |
Status indicator dot. label is a boolean that toggles display of the built-in status text (not a custom string). Maps AgentRuntimeStatus to semantic colors via getStatusColor(). |
MetricCard |
metric-card.tsx |
label, value, change?, sparklineData?, progress?, subText? |
Numeric KPI display with optional sparkline, change badge (+/-%), and progress bar. |
Sparkline |
sparkline.tsx |
data, color?, width?, height?, animated? |
Pure SVG sparkline with gradient fill and animated draw. color defaults to var(--so-accent). Standalone or inside MetricCard. |
SectionCard |
section-card.tsx |
title, icon?, action?, children |
Titled card wrapper with Lucide icon, action slot, and content area. Use for every content section. |
AgentCard |
agent-card.tsx |
name, role, department, status, currentTask?, timestamp? |
Consistent agent display. Composes Avatar + StatusBadge internally. Must look identical everywhere. |
DeptHealthBar |
dept-health-bar.tsx |
name, health?, agentCount |
Animated horizontal fill bar with utilization percentage (null-safe -- shows N/A when utilization unavailable). Color auto-mapped via getHealthColor(). |
ProgressGauge |
progress-gauge.tsx |
value, max?, label?, variant?, size? |
Circular or linear gauge for budget/utilization. variant defaults to 'circular', max defaults to 100. |
StatPill |
stat-pill.tsx |
label, value |
Compact inline label + value pair for metadata rows. |
Avatar |
avatar.tsx |
name, size?, borderColor? |
Circular initials avatar with optional colored border. Sizes: sm (24px), md (32px), lg (40px). |
Button |
button.tsx |
shadcn standard | Standard button component (shadcn/ui). |
TaskStatusIndicator |
task-status-indicator.tsx |
status: TaskStatus, label?: boolean, pulse?: boolean, className?: string |
Task status dot with optional label and pulse animation. |
PriorityBadge |
task-status-indicator.tsx |
priority: Priority, className?: string |
Task priority colored pill badge. |
ProviderHealthBadge |
provider-health-badge.tsx |
status: ProviderHealthStatus, label?: boolean, pulse?: boolean, className?: string |
Provider health status dot (up/degraded/down/unknown) with optional label. |
Interaction Components¶
| Component | File | Props | Purpose |
|---|---|---|---|
Toast / ToastContainer |
toast.tsx |
toast (ToastItem), onDismiss, maxVisible? |
Notification toasts (success/error/warning/info) with auto-dismiss queue, Framer Motion animations. Mount ToastContainer once in AppLayout. |
Skeleton variants |
skeleton.tsx |
shimmer?, lines?, rows?, columns? |
Loading placeholders: Skeleton (base), SkeletonText, SkeletonCard, SkeletonMetric, SkeletonTable. Shimmer respects prefers-reduced-motion. |
EmptyState |
empty-state.tsx |
icon?, title, description?, action? |
No-data / no-results placeholder with optional action button. |
ErrorBoundary |
error-boundary.tsx |
fallback?, onReset?, level? |
React error boundary with retry. Levels: page (full-height), section (card), component (inline). |
ConfirmDialog |
confirm-dialog.tsx |
open, onOpenChange, title, onConfirm, variant?, loading? |
Confirmation modal built on Radix AlertDialog. Variants: default, destructive. |
CommandPalette |
command-palette.tsx |
className? |
Global Cmd+K search built with cmdk. Focus-trapped, fuzzy search, scope toggle, recent items. |
InlineEdit |
inline-edit.tsx |
value, onSave, validate?, type?, disabled? |
Click-to-edit with Enter/Escape, inline validation, optimistic save via useFlash. |
AnimatedPresence |
animated-presence.tsx |
routeKey, className? |
Page transition wrapper. Uses Framer Motion AnimatePresence with reduced-motion fallback. |
StaggerGroup / StaggerItem |
stagger-group.tsx |
staggerDelay?, animate?, layoutId?, layout? |
Card entrance stagger container with configurable delay and layout animation support. |
Drawer |
drawer.tsx |
open, onClose, title?, ariaLabel?, side?, contentClassName?, children, className? |
Slide-in panel (left or right via side, default right) with overlay, spring animation, focus trap, and Escape-to-close. At least one of title or ariaLabel must be provided for accessible naming. Header omitted when title is absent. |
InputField |
input-field.tsx |
label, error?, hint?, multiline?, rows?, placeholder?, required?, disabled?, type?, value, onChange |
Labeled text input with inline error/hint display and optional textarea mode. Extends native input/textarea props. |
SelectField |
select-field.tsx |
label, options, value, onChange, error?, hint?, placeholder?, required?, disabled?, className? |
Labeled select dropdown with error/hint display and placeholder support. |
SliderField |
slider-field.tsx |
label, value, onChange, min, max, step?, formatValue?, disabled?, className? |
Labeled range slider with custom value formatter and aria-live value display. |
ToggleField |
toggle-field.tsx |
label, checked, onChange, description?, disabled? |
Labeled toggle switch (role="switch") with optional description text. |
TagInput |
tag-input.tsx |
label, value, onChange, placeholder?, disabled?, className? |
Chip-style multi-value input with add/remove, keyboard support (Enter to add, Backspace to remove), paste splitting. |
TokenUsageBar |
token-usage-bar.tsx |
data, segments?, max?, animated?, className? |
Segmented horizontal meter bar for token usage (multi-segment with auto-colors, role="meter", animated transitions). |
CodeMirrorEditor |
code-mirror-editor.tsx |
value, onChange, language, readOnly?, aria-label?, className? |
CodeMirror 6 editor with JSON/YAML modes, design-token dark theme, line numbers, bracket matching, and readOnly support. |
SegmentedControl |
segmented-control.tsx |
label, options, value, onChange, disabled?, size?, className? |
Accessible radiogroup with keyboard navigation (arrow keys + wrapping), size variants (sm/md), generic <T extends string> typing. |
ThemeToggle |
theme-toggle.tsx |
className? |
Radix Popover with 5-axis theme controls (color, density, typography, animation, sidebar). Rendered in StatusBar for global access. |
LiveRegion |
live-region.tsx |
children, politeness?, debounceMs?, className? |
Debounced ARIA live region wrapper for real-time WS updates without overwhelming screen readers. Default: 500ms polite, 0ms assertive. |
MobileUnsupportedOverlay |
mobile-unsupported.tsx |
(none -- self-managing) | Full-screen overlay at <768px viewports directing users to desktop or CLI. Self-manages visibility via useBreakpoint. |
LazyCodeMirrorEditor |
lazy-code-mirror-editor.tsx |
Same as CodeMirrorEditor |
Suspense-wrapped lazy-loaded CodeMirrorEditor. Drop-in replacement that defers ~200KB+ CodeMirror bundle. |
MetadataGrid |
metadata-grid.tsx |
items, columns?, className? |
Key-value metadata grid for detail pages with configurable 2/3/4 columns and density-aware spacing. |
ProjectStatusBadge |
project-status-badge.tsx |
status, showLabel?, className? |
Project status dot with optional label and semantic colors (planning/active/on_hold/completed/cancelled). |
ContentTypeBadge |
content-type-badge.tsx |
contentType, className? |
MIME content type pill badge with semantic colors (JSON, PDF, Image, Text, Markdown, CSV, Binary). |
Utility Functions¶
| Function | File | Purpose |
|---|---|---|
cn() |
lib/utils.ts |
Tailwind class merging (clsx + twMerge). Use in every component. |
getStatusColor() |
lib/utils.ts |
Maps AgentRuntimeStatus to SemanticColor \| "text-secondary" token name (offline maps to "text-secondary"). |
getHealthColor() |
lib/utils.ts |
Maps 0-100 percentage to SemanticColor (>=75 success, >=50 accent, >=25 warning, <25 danger). |
getTaskStatusColor() |
utils/tasks.ts |
Maps TaskStatus to SemanticColor. |
getTaskStatusLabel() |
utils/tasks.ts |
Maps TaskStatus to display label. |
getPriorityColor() |
utils/tasks.ts |
Maps Priority to SemanticColor. |
getPriorityLabel() |
utils/tasks.ts |
Maps Priority to display label. |
getTaskTypeLabel() |
utils/tasks.ts |
Maps TaskType to display label. |
getProviderHealthColor() |
utils/providers.ts |
Maps ProviderHealthStatus to SemanticColor \| "muted". |
formatLatency() |
utils/providers.ts |
Formats milliseconds to human-readable string (e.g. "123ms", "1.5s"). |
formatErrorRate() |
utils/providers.ts |
Formats error rate percentage with <0.1% handling. |
formatTokenCount() |
utils/providers.ts |
Formats token count with K/M suffixes. |
formatCost() |
utils/providers.ts |
Formats cost value using project currency (defaults to EUR). |
toRuntimeStatus() |
utils/agents.ts |
Maps API-layer AgentStatus (HR lifecycle) to AgentRuntimeStatus for UI components. |
getRiskLevelColor() |
utils/approvals.ts |
Maps ApprovalRiskLevel to SemanticColor \| "accent-dim". |
getRiskLevelLabel() |
utils/approvals.ts |
Maps ApprovalRiskLevel to display label. |
getRiskLevelIcon() |
utils/approvals.ts |
Maps ApprovalRiskLevel to LucideIcon. |
getApprovalStatusColor() |
utils/approvals.ts |
Maps ApprovalStatus to SemanticColor \| "text-secondary". |
getApprovalStatusLabel() |
utils/approvals.ts |
Maps ApprovalStatus to display label. |
getUrgencyColor() |
utils/approvals.ts |
Maps UrgencyLevel to SemanticColor \| "text-secondary". |
formatUrgency() |
utils/approvals.ts |
Formats seconds_remaining into human-readable countdown string. |
groupByRiskLevel() |
utils/approvals.ts |
Groups approvals into Map<ApprovalRiskLevel, ApprovalResponse[]> sorted critical-to-low. |
filterApprovals() |
utils/approvals.ts |
Client-side filtering by status, risk level, action type, and search text. |
RISK_LEVEL_ORDER |
utils/approvals.ts |
Numeric ordering map for risk levels (critical=0 through low=3). |
DOT_COLOR_CLASSES |
utils/approvals.ts |
Maps SemanticColor \| "accent-dim" to Tailwind background classes. |
URGENCY_BADGE_CLASSES |
utils/approvals.ts |
Maps SemanticColor \| "text-secondary" to Tailwind badge classes. |
formatFileSize() |
utils/format.ts |
Formats byte count to human-readable size string (e.g. "1.2 MB", "340 KB"). |
Design System Hooks¶
| Hook | File | Purpose |
|---|---|---|
useFlash() |
hooks/useFlash.ts |
Real-time update flash effect. Returns { flashing, flashClassName, triggerFlash, flashStyle }. Uses STATUS_FLASH timing constants. |
useStatusTransition() |
hooks/useStatusTransition.ts |
Animate between agent status colors. Returns { displayColor, motionProps } for spreading on motion.div. |
useCommandPalette() |
hooks/useCommandPalette.ts |
Global command palette state. registerCommands() adds page-local commands (cleanup on unmount). open() / close() / toggle(). |
useAnimationPreset() |
hooks/useAnimationPreset.ts |
Returns animation config (spring, tween, staggerDelay, enableLayout) based on the user's theme animation preference. Components use this instead of directly referencing lib/motion.ts constants. |
useCountAnimation() |
hooks/useCountAnimation.ts |
Animated numeric value transitions for metric displays. Uses rAF with ease-out cubic and prefers-reduced-motion support. |
useAutoScroll() |
hooks/useAutoScroll.ts |
Auto-scroll container to bottom on new content, pausing when user scrolls away. Returns { isAutoScrolling, scrollToBottom }. |
useRovingTabIndex() |
hooks/useRovingTabIndex.ts |
WAI-ARIA roving tabindex for arrow-key navigation in lists and grids. Supports vertical, horizontal, and grid orientations with loop/clamp. |
useBreakpoint() |
hooks/useBreakpoint.ts |
Reactive viewport breakpoint detection via matchMedia. Returns { breakpoint, isDesktop, isTablet, isMobile }. |
useJustUpdated() |
hooks/useJustUpdated.ts |
Tracks recently-updated entity IDs with TTL and relative time strings for flash/highlight effects. |
Types¶
| Type | File | Values |
|---|---|---|
AgentRuntimeStatus |
lib/utils.ts |
"active", "idle", "error", "offline" |
SemanticColor |
lib/utils.ts |
"success", "accent", "warning", "danger" |
TaskStatus |
api/types |
"created", "assigned", "in_progress", "in_review", "completed", "blocked", "failed", "interrupted", "cancelled" |
Priority |
api/types |
"critical", "high", "medium", "low" |
ProviderHealthStatus |
api/types |
"up", "degraded", "down", "unknown" |
ApprovalStatus |
api/types |
"pending", "approved", "rejected", "expired" |
ApprovalRiskLevel |
api/types |
"low", "medium", "high", "critical" |
UrgencyLevel |
api/types |
"critical", "high", "normal", "no_expiry" |
ApprovalPageFilters |
utils/approvals |
Filter shape: status?, riskLevel?, actionType?, search? |
ProjectStatus |
api/types |
"planning", "active", "on_hold", "completed", "cancelled" |
ArtifactType |
api/types |
"code", "tests", "documentation" |
When to Create a New Shared Component¶
Create a new component in web/src/components/ui/ when:
- The same UI pattern appears (or will appear) on 2+ pages
- It represents a semantic concept (not just a styled div)
- It has configurable behavior via props (variants, states, sizes)
Every new shared component must have:
- A
.stories.tsxfile with all states (default, hover, loading, error, empty) - A TypeScript props interface
- Design token usage exclusively (no hardcoded colors/fonts/spacing)
cn()for conditional class merging
Enforcement¶
A PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files. See CLAUDE.md "Web Dashboard Design System" section for the full rule set.
Reference Materials¶
| Resource | Location |
|---|---|
| Design exploration mockups (5 variations) | feat/765-design-exploration branch, mockups-v2/ (exploration artifacts, not production code) |
| Original winning prototype (C+D direction) | research/762-ux-mockups branch, mockups/direction-cd/ |
| UX research document | research/762-ux-mockups branch, docs/design/ux-research.md |
| Page structure and information architecture | Page Structure & IA |
| UX design guidelines (implementation specs) | UX Guidelines |
| Parent issue (full UX overhaul) | #762 |
| Design exploration issue | #765 |
| Page structure issue | #766 |
| Design tokens implementation | #775 |
| UX guidelines document | #767 |