# PettyUI AI-First Architecture Design **Date:** 2026-03-29 **Status:** Approved **Supersedes:** All prior specs and plans in this directory ## Vision PettyUI is the first component library where **the AI is the intended user, not an afterthought**. The primary consumer is LLMs generating code. Human developers benefit as a side effect of good AI ergonomics. Distribution follows the proven Radix → shadcn two-layer model: headless core + copy-paste styled registry. ## Package Structure ``` packages/ core/ → Headless primitives with Zod-first props + meta mcp/ → MCP server: discovery, validation, code generation registry/ → Styled copy-paste components (shadcn layer for Solid) ``` No separate `schemas/` package. The Zod schema lives WITH the component because it IS the component's type system. ## Component Inventory Organized by **AI task** — what the agent is trying to build — not UI taxonomy. ### BUILD A FORM (AI's #1 task) | Component | Status | Notes | |-----------|--------|-------| | TextField | Done | | | Checkbox | Done | | | Switch | Done | | | RadioGroup | Done | | | Select | Done | | | Combobox | Done | | | Slider | Done | | | NumberField | Done | | | ToggleGroup | Done | | | **Form** | New | Validation integration (Zod v4), field grouping, error display, aria-describedby linking | | **DatePicker** | New | Calendar + input, range selection, locale-aware | | **Calendar** | New | Standalone month/year grid, keyboard nav | ### BUILD NAVIGATION | Component | Status | Notes | |-----------|--------|-------| | Tabs | Done | | | Breadcrumbs | Done | | | Link | Done | | | DropdownMenu | Done | | | **CommandPalette** | New | cmdk pattern — search, keyboard nav, grouped actions | | **NavigationMenu** | New | Horizontal nav with dropdown submenus, hover intent | ### BUILD AN OVERLAY | Component | Status | Notes | |-----------|--------|-------| | Dialog | Done | | | AlertDialog | Done | | | Drawer | Done | | | Popover | Done | | | Tooltip | Done | | | HoverCard | Done | | | Toast | Done | | ### BUILD A DASHBOARD / DATA VIEW | Component | Status | Notes | |-----------|--------|-------| | Progress | Done | | | Badge | Done | | | Skeleton | Done | | | Alert | Done | | | **DataTable** | New | Sorting, filtering, pagination, column resize, row selection | | **VirtualList** | New | Virtualized rendering for large datasets, variable row heights | | **Avatar** | New | Image + fallback initials, status indicator | ### BUILD LAYOUT & STRUCTURE | Component | Status | Notes | |-----------|--------|-------| | Accordion | Done | | | Collapsible | Done | | | **Card** | New | Header/Content/Footer compound, token contract | | **Wizard/Stepper** | New | Multi-step flows, step validation, linear/non-linear | ### BUILD INTERACTIONS | Component | Status | Notes | |-----------|--------|-------| | Button | Done | | | Toggle | Done | | ### CUT (remove from exports, keep internally if needed) | Component | Reason | |-----------|--------| | ContextMenu | Niche desktop pattern | | Image | `` covers it | | Meter | Nobody uses it vs Progress | | Separator | Keep as menu sub-component only, drop standalone export | | Pagination | Keep as DataTable internal, drop standalone export | | ColorPicker suite | Design-tool-only, <5% of projects | | Menubar | Desktop OS pattern | | TimeField | Extremely specialized | | Rating | Trivial, single-purpose | | FileField | Browser native + dropzone libs | | SegmentedControl | ToggleGroup covers this | ### Totals | Category | Done | Cut | New | Final Total | |----------|------|-----|-----|-------------| | Components | 28 | -3 | +10 | 35 | | Standalone exports | 28 | -5 | +10 | 33 | | Primitives | 5 | 0 | +1 (createVirtualizer) | 6 | | Utilities | 6 | 0 | 0 | 6 | ## Schema Architecture: Zod-First, Single Source of Truth Every component's props are defined AS Zod schemas. TypeScript types are derived FROM the schemas. No duplication, no drift. ### Per-Component Pattern ```typescript // components/dialog/dialog.props.ts import { z } from "zod/v4"; export const DialogRootProps = z.object({ open: z.boolean().optional() .describe("Controlled open state"), defaultOpen: z.boolean().optional() .describe("Initial open state (uncontrolled)"), onOpenChange: z.function().args(z.boolean()).returns(z.void()).optional() .describe("Called when open state changes"), modal: z.boolean().default(true) .describe("Whether to trap focus and add backdrop"), }); // TypeScript types DERIVED from schema export type DialogRootProps = z.infer; // Metadata — just enough for MCP discovery export const DialogMeta = { name: "Dialog", description: "Modal overlay requiring user acknowledgment", parts: ["Root", "Trigger", "Portal", "Overlay", "Content", "Title", "Description", "Close"] as const, requiredParts: ["Root", "Content", "Title"] as const, } as const; ``` ### What This Enables | AI asks... | Schema provides... | |---|---| | "What components can build a form?" | `.describe()` strings + MCP semantic search | | "What props does Select accept?" | Full typed contract with descriptions | | "Is this Dialog usage valid?" | Runtime validation against schema | | "Which parts are required for a11y?" | `requiredParts` in meta | ## MCP Server Architecture The MCP server is the AI's interface to PettyUI. It reads Zod schemas and meta directly from `core/`. ### Tools ``` pettyui.discover → "I need to collect user input" → returns matching components with schemas pettyui.inspect → "Tell me about Dialog" → returns full props schema + parts + required pettyui.validate → "Is this Dialog usage correct?" → validates JSX/props against schema pettyui.add → "Add Dialog to my project" → copies styled component from registry pettyui.compose → "Build a settings form" → returns composed multi-component JSX ``` ### pettyui.discover ``` Input: { intent: "I need a searchable dropdown" } Output: [ { name: "Combobox", match: 0.95, description: "..." }, { name: "CommandPalette", match: 0.72, description: "..." }, { name: "Select", match: 0.6, description: "..." } ] ``` Uses `.describe()` strings for semantic matching. No rigid taxonomy — descriptions ARE the search index. ### pettyui.inspect ``` Input: { component: "Dialog" } Output: { props: { /* Zod schema as JSON Schema */ }, parts: ["Root", "Trigger", "Portal", "Overlay", "Content", "Title", "Description", "Close"], requiredParts: ["Root", "Content", "Title"], example: "/* minimal valid usage */", source: "/* full component source if requested */" } ``` ### pettyui.validate ``` Input: { component: "Dialog", jsx: "hello" } Output: { valid: false, errors: ["Missing required part: Title. Dialog.Content must contain Dialog.Title for accessibility"], fix: "...hello" } ``` ### pettyui.add ``` Input: { component: "Dialog", style: "default" } Output: { files: [{ path: "src/components/ui/dialog.tsx", content: "/* styled component */" }], dependencies: ["pettyui/dialog"] } ``` ### pettyui.compose ``` Input: { intent: "login form with email and password", components: ["Form", "TextField", "Button"] } Output: { jsx: "/* complete composed component */", imports: ["pettyui/form", "pettyui/text-field", "pettyui/button"] } ``` ### MCP Server Does NOT Do - No styling opinions — registry's job - No state management — components handle their own - No routing — out of scope - No build tooling — tsdown/vite ## Registry Layer (shadcn Model for Solid) ### What Gets Copied `pettyui add dialog` creates `src/components/ui/dialog.tsx`: ```typescript import { Dialog as DialogPrimitive } from "pettyui/dialog"; import { cn } from "@/lib/utils"; const DialogContent = (props) => ( {props.children} ); export { Dialog, DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogClose }; ``` ### Registry Principles 1. **Tailwind + CSS variables** — styles co-located, tokens via `--` variables 2. **Imports headless from core** — registry depends on `pettyui/*`, not copy-pasted behavior 3. **Pre-composed for 90% case** — DialogContent includes Portal + Overlay automatically 4. **Fully editable** — your file after copy 5. **One file per component** — AI reads one file, gets everything ### Theme Contract ```css :root { --background: 0 0% 100%; --foreground: 0 0% 3.9%; --primary: 0 0% 9%; --primary-foreground: 0 0% 98%; --muted: 0 0% 96.1%; --border: 0 0% 89.8%; --ring: 0 0% 3.9%; --radius: 0.5rem; } ``` ### CLI ```bash pettyui init # Sets up tokens, utils, tsconfig paths pettyui add dialog # Copies styled dialog component pettyui add form # Copies styled form with Zod integration pettyui diff dialog # Shows what you've changed vs upstream ``` ## Build Order ``` Phase 1: Foundation Phase 2: Advanced Phase 3: AI Layer ───────────────── ────────────────── ───────────────── Zod-first props refactor DataTable MCP server (migrate 28 components) CommandPalette pettyui.discover Meta objects on all DatePicker + Calendar pettyui.inspect components Wizard/Stepper pettyui.validate Card + Avatar (simple) Form system pettyui.add NavigationMenu VirtualList + primitive pettyui.compose Cut ContextMenu/Image/ Meter/Separator/Pagination Registry scaffolding Registry: styled versions CLI wrapper (init, tokens, utils) of all components pettyui init/add/diff ``` Phase 1 is mostly refactoring what exists. Phase 2 is the hard new work. Phase 3 is where PettyUI becomes unique. ## Key Design Principles 1. **AI is the primary user** — every API decision optimizes for LLM code generation 2. **Zod-first** — schemas ARE the type system, not a parallel description 3. **Sub-path exports** — `pettyui/dialog` not `pettyui` barrel. Prevents hallucination 4. **Compound components** — `Dialog.Root` + `Dialog.Content` over prop explosion 5. **Sensible defaults** — components work with zero props, accept controlled props when needed 6. **Union types over booleans** — `variant: 'primary' | 'secondary'` not `isPrimary?: boolean` 7. **Consistent naming** — same patterns everywhere, reduces AI search space 8. **CSS variables for theming** — AI handles CSS vars naturally vs JS theme providers 9. **`.describe()` on every prop** — this IS the documentation 10. **Runtime validation feedback** — AI generates, validates, fixes. Feedback loop