# 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 | `<img loading="lazy">` 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<typeof DialogRootProps>;

// 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: "<Dialog.Root><Dialog.Content>hello</Dialog.Content></Dialog.Root>" }
Output: {
  valid: false,
  errors: ["Missing required part: Title. Dialog.Content must contain Dialog.Title for accessibility"],
  fix: "<Dialog.Root><Dialog.Content><Dialog.Title>...</Dialog.Title>hello</Dialog.Content></Dialog.Root>"
}
```

### 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) => (
  <DialogPrimitive.Portal>
    <DialogPrimitive.Overlay class={cn("fixed inset-0 bg-black/50 ...")} />
    <DialogPrimitive.Content
      class={cn("fixed left-1/2 top-1/2 ... bg-background rounded-lg shadow-lg", props.class)}
    >
      {props.children}
    </DialogPrimitive.Content>
  </DialogPrimitive.Portal>
);

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
