Template
Gate hardening:
- strict + noUncheckedIndexedAccess in tsconfig.app.json; fix resulting errors
- check-tokens.mjs: scan inline style={{ }} in ts/tsx, ban named colors and
rgb()/hsl()/oklch()/lab(), read multi-line values, fail on undefined var(--x)
- vitest (24 tests: overlay, icon-registry, useTimeout, token guard) wired into
npm run check; GitHub Actions CI workflow
Single-source refactors:
- new Overlay primitive owns focus trap/Escape/scroll-lock/focus-restore and
conditional aria-labelledby/aria-describedby; Dialog + Sheet are thin skins
(overlayBehavior.ts exports trapTabKey/useOverlayLifecycle for SearchPalette)
- icon-registry.ts is the one icon map: strict IconName, aliases typed, dev
throws on unknown names instead of silently rendering <Layers/>; IconMatrix
consumes the shared registry (41 curated)
- useTimeout hook replaces fire-and-forget setTimeout in DeploymentModal,
SettingsPanel, IconMatrix, CodeSample (timers die on close/unmount)
Bug fixes:
- SearchPalette: role=dialog/aria-modal, focus trap + restore, combobox/listbox
semantics, labelled input, selection clamped to results
- theme: pre-paint script in index.html (localStorage -> prefers-color-scheme ->
dark), useTheme no longer writes DOM during state init
- StatCard: direction (arrow) decoupled from trend (sentiment); latency drop
renders green "↓ -4.2ms" instead of "↑ -4.2ms"
- sidebar collapse button wired (aria-pressed + collapsed rail CSS); pattern
routing keys on patternId from library.ts, not English labels
- remove 25 dead legacy tokens (--panel*, --line*, --text-soft, --critical,
--pink, --blue, ...); migrate --bg/--text/--positive/--warning usages
- title "Northstar Design System", real README, AGENTS.md structure/theme/
overlay/registry/test docs updated; dev error sink (#dev-errors +
ErrorBoundary) so agents can read runtime errors via inspect
8.2 KiB
8.2 KiB
Northstar Design System — agent handbook
React 19 + Vite 8 + TypeScript (strict) design-system showcase. Dark-first, token-driven UI with a light theme.
Commands
| Task | Command |
|---|---|
| Dev server | npm run dev (vite.config.ts pins 0.0.0.0:5173) |
| Full gate: lint + token contract + tests + typecheck + build | npm run check — run before declaring any change done |
| Lint only | npm run lint (oxlint) |
| Tests | npm run test (vitest, jsdom; suites in tests/) |
TypeScript is strict (strict + noUncheckedIndexedAccess in tsconfig.app.json) — implicit any fails the build, not just the reviewer.
Debugging without a console
Browser tooling here has no console access. Runtime errors therefore render into a #dev-errors element (see src/dev/errorSink.ts + src/dev/ErrorBoundary.tsx): after loading the page, wait_for / inspect [data-dev-error] to read error messages, and wait_for vite-error-overlay for build/HMR errors. If #root is empty but no [data-dev-error] exists, check the dev-server stdout.
Sandboxed worker dev flow
vite.config.tsallows*.preview.k6n.net; expose port 5173 viarepo_instance_exposefor a preview URL.- Removing
allowedHostsbreaks preview ingress with a 403 ("host not allowed"). Don't.
Design tokens — the contract
- Single source of truth:
src/tokens.css. Every font size, color, spacing, radius, and shadow color comes from a token. The palette is defined in modern CSS Color 4lab(...)color space:- Dark theme (
:root:not([data-theme='light']),[data-theme='dark'],.dark): Pitch black base (lab(0% 0 0)), deep cards (lab(.903296% 0 0)), radiant electric mint/cyan primary (lab(89.9321% -47.9163 -15.7303)), vivid destructive (lab(54.5811% 71.684 50.9279)), chart colors 1–5, and subtle dot grid (--dot-color). - Light theme (
:root,[data-theme='light'],.light): Pure white daylight base (lab(100% 0 0)), crisp charcoal primary (lab(7.78201% -.0000149012 0)), and high-contrast surfaces. - There are no legacy aliases (
--panel*,--line*,--text-soft, …) anymore — canonical names only.
- Dark theme (
- The guard
scripts/check-tokens.mjs(innpm run check) bans hardcoded colors — hex, named colors,rgb()/hsl()/oklch()/lab(), even across multiple lines — plus px font sizes andvar(--x)references that no CSS file defines. It scans CSS files and inlinestyle={{ … }}in.ts/.tsx. Allowed:var(--token),color-mix(in srgb, var(--token) N%, transparent),light-dark(var(--a), var(--b)), and neutral black/white rgba shines. - Typography scale (
--text-3xs…--text-2xl,--text-hero) is rem-based on a 9→28px grid. Longform body--text-md, buttons/nav--text-xs, meta/eyebrows--text-2xs/--text-3xs. - Global density knob:
--root-font-size(default112%readability preset, baseline 16px =100%). One line scales every token-driven size app-wide; spacing tokens follow. - Theming: components read only
var(--*)and never know the theme. A pre-paint script inindex.htmlapplieslocalStorage['ns-theme']orprefers-color-schemebefore first paint (no flash);useThemeinTopbar.tsxreads what it set and keepsdata-themeand the.dark/.lightclasses in sync on toggle.
Smart Utils & AI Workflow
- Class helper:
src/utils/cn.ts— zero-dependency classnames merging utility for conditional component styles. - Timer helper:
src/utils/useTimeout.ts—schedule(fn, ms)/clear(); auto-cancels pending timers on unmount. Never fire-and-forget a rawsetTimeoutin a component. - Smart CSS:
src/utils.css— ready-to-use helpers (.dot-canvas,.surface-card,.surface-sidebar,.badge-*,.hover-lift,.kbd-tag,.skeleton-pulse,.progress-*,.ui-table-*,.dialog-*,.sheet-*,.alert-*,.ui-tabs-*,.switch-*). - Overlays — one primitive:
src/components/ui/Overlay.tsxowns focus trap, Escape-to-close, scroll lock, focus restore, and conditionalaria-labelledby/aria-describedby(wired only when a title/description is actually rendered).DialogandSheetare thin skins over it;SearchPalettereusestrapTabKey+useOverlayLifecyclefromsrc/components/ui/overlayBehavior.ts. Build new modals on Overlay, not from scratch. - Composable Primitives:
src/components/ui/(Overlay,Dialog,Sheet,Tabs,Alert,EmptyState,Table,Progress,Switch,Badge,Card,StatCard,Skeleton,Tooltip,Kbd,Avatar,Spinner;Iconlives atsrc/components/Icon.tsxand is re-exported from the barrel). - StatCard:
trendis sentiment (badge color),directionis the arrow and defaults to the sign ofchange— a "good" latency drop istrend="up"withchange="-4.2ms"rendering a green↓ -4.2ms. - Reusable Application Patterns:
src/components/examples/(MetricsDashboard,SettingsPanel,ServiceTable,DeploymentModal). - Icon Library: the registry in
src/components/icon-registry.tsmaps everyIconNameto a lucide component (strictly typed — typos failtsc, unknown names at runtime throw in dev so the error sink surfaces them). 41 curated icons power the showcase;lucide-reactships 1,400+ for direct import.
Structure
src/
tokens.css ← lab() design tokens + dark & light themes (edit this first)
utils.css ← token-compliant smart CSS utility classes
index.css ← fonts, tokens & utils import, base resets
App.css ← application component styles (token-driven, plain CSS)
App.tsx ← page composition & showcase layout
main.tsx ← mounts App inside the ErrorBoundary, installs the dev error sink
dev/
ErrorBoundary.tsx, errorSink.ts ← render runtime errors into #dev-errors
utils/
cn.ts ← conditional classnames utility
useTimeout.ts ← auto-cleaning setTimeout hook
components/
ui/ ← composable UI primitives (Overlay, Dialog, Sheet, Tabs, Alert,
EmptyState, Table, Progress, Switch, Badge, Card, StatCard,
Skeleton, Tooltip, Kbd, Avatar, Spinner) + overlayBehavior.ts
icon-registry.ts← the one icon name → lucide component map (single source)
examples/ ← reusable pattern components (MetricsDashboard, SettingsPanel,
ServiceTable, DeploymentModal)
AIWorkflowSection.tsx ← interactive pattern switcher & showcase
SearchPalette.tsx ← ⌘K command palette (Overlay behavior + combobox a11y)
TypographySection.tsx ← type-scale showcase
IconMatrix.tsx ← interactive Lucide icon explorer (renders iconRegistry)
Icon.tsx ← <Icon name="…" /> component (types from icon-registry.ts)
Topbar.tsx ← owns the theme toggle (data-theme + .dark/.light)
Hero.tsx, CodeSample.tsx, RightRail.tsx, ButtonMatrix.tsx,
Sidebar.tsx, Section.tsx, FormField.tsx, TokenGrid.tsx,
MarkdownRenderer.tsx, CodeEditor.tsx, Button.tsx
data/
library.ts ← demo content, nav groups (patternId routing) & color tokens
tests/ ← vitest suites (jsdom): overlay, icon-registry, useTimeout,
check-tokens guard
scripts/check-tokens.mjs ← token contract guard (part of npm run check)
Parallel subagent workflow
Multiple agents can work the same checkout concurrently if tasks are file-disjoint:
- Each agent gets an explicit allow-list of files; shared files (barrels like
components/ui/index.ts,utils.css,App.tsx) are edited by the orchestrator only, after agents finish. - Agents verify with
npx oxlint <files>,npx tsc --noEmit -p tsconfig.app.json, andnode scripts/check-tokens.mjs— nevernpm run check/vite buildin parallel (incremental build info anddist/would race). - Read-only investigation tasks use the read-only spawn mode; they can run freely alongside writers.
- New primitives: agents create self-contained files (component + own
.cssimport if keyframes/classes are needed); the orchestrator registers exports and showcase usage.
Iteration workflow for visual changes
- Change the token (or component markup) — never a literal.
npm run checkuntil green.- Verify live: dev server + preview URL; screenshot at desktop width; toggle light/dark via the topbar button.