Frontend Design System
This project treats shadcn/Base UI components as replaceable low-level primitives. Product behavior lives in app-owned wrappers so preset refreshes do not break settings persistence, density, or translated UI.
Ownership
Section titled “Ownership”| Layer | Path | Owner | Rules |
|---|---|---|---|
| Generated primitives | src/components/ui/ | shadcn/Base UI plus documented local overrides | Keep low-logic. Do not add provider, model, RAG, or React Hook Form behavior. |
| Form behavior | src/components/forms/ | App | Own value parsing, React Hook Form integration, number/slider conversion, and behavior tests. |
| Layout rhythm | src/components/layout/ | App | Own page width, stacks, grids, toolbars, and repeated spacing. |
| Settings composition | src/components/settings/ | App | Own settings rows, fields, sections, action rows, diagnostics, and dangerous actions. |
| Feedback states | src/components/feedback/ | App | Own empty, loading, error, progress, metric, health, and status UI. |
| Data display | src/components/data-display/ | App | Own repeated provider, model, source, file, metadata, and info-list patterns. |
| Feature UI | src/features/* | Feature | Own domain copy, state, validation config, and feature-specific workflows only. |
Form Rules
Section titled “Form Rules”- Prefer
ControlledNumberInput,ControlledSlider,ControlledTextInput,ControlledTextarea,ControlledSelect, andControlledSwitchover wiringsrc/components/ui/*directly into React Hook Form. - Use
SettingsFieldorSettingsSliderFieldwhen the control needs label, description, value badge, or validation text. - Keep parsing and conversion out of feature components. Feature components should pass values and handlers, not know Base UI event shapes.
- Any app-owned form primitive that transforms values needs tests.
Layout Rules
Section titled “Layout Rules”- Use
PageStackfor top-level tab/page rhythm. - Use
SectionStackfor grouped settings surfaces. - Use
FieldStackfor stacked fields inside cards. - Use
ControlStackfor label/control micro-layouts. - Use
TwoColumnGrid,FormGrid, andDenseFormGridinstead of repeating raw grid strings. - Use
Toolbar,InlineActions, andSettingsActionRowfor action groups that must wrap on narrow widths.
Token Rules
Section titled “Token Rules”- Use semantic tokens:
bg-background,bg-card,text-muted-foreground,border-border,bg-status-success/10, andSTATUS_STYLES. - Use
dark:*only for primitive quirks or prose inversion where tokens cannot express the state. - Keep raw
hsl(...),oklch(...), and hex values inside token files unless the value belongs to syntax highlighting or external content. - Use control density helpers when a component needs explicit height:
control-h-sm,control-h-md,control-h-lg, plus matchingcontrol-min-h-*utilities.
i18n Rules
Section titled “i18n Rules”- Shared component props for
title,label,description,emptyText, and actions should acceptReact.ReactNode. - Do not concatenate translated sentence fragments.
- Long translated labels must wrap or truncate intentionally; never assume English-length text.
- Generated primitives in
src/components/ui/should stay text-agnostic.
Preset Update Checklist
Section titled “Preset Update Checklist”- Apply the shadcn preset or primitive update on a branch.
- Review diffs under
src/components/ui/andsrc/globals.cssfirst. - Confirm no feature behavior moved into generated primitives.
- Run
pnpm lint:check,pnpm typecheck, andpnpm test:run. - Run targeted tests for number inputs, sliders, selects, switches, and any touched primitive.
- Manually check model settings, embedding settings, provider settings, chat input, and narrow options width in both light and dark mode.
Visual QA Checklist
Section titled “Visual QA Checklist”- Options page: settings sidebar, mobile tabs, model settings, provider settings, embedding settings.
- Sidepanel: chat header, composer, message rendering, search dialog, empty states.
- Themes: light, dark, and system mode.
- Long text: use a language with longer labels and verify buttons, rows, cards, and badges do not overlap.
- Density: controls should remain compact without relying on raw per-file heights.