New spec supersedes all prior design docs. Phase 1 covers Zod-first props migration for 31 components, Meta objects, Card/Avatar/NavigationMenu, cut ContextMenu/Image/Meter, and registry scaffolding.
11 KiB
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
// 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:
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
- Tailwind + CSS variables — styles co-located, tokens via
--variables - Imports headless from core — registry depends on
pettyui/*, not copy-pasted behavior - Pre-composed for 90% case — DialogContent includes Portal + Overlay automatically
- Fully editable — your file after copy
- One file per component — AI reads one file, gets everything
Theme Contract
: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
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
- AI is the primary user — every API decision optimizes for LLM code generation
- Zod-first — schemas ARE the type system, not a parallel description
- Sub-path exports —
pettyui/dialognotpettyuibarrel. Prevents hallucination - Compound components —
Dialog.Root+Dialog.Contentover prop explosion - Sensible defaults — components work with zero props, accept controlled props when needed
- Union types over booleans —
variant: 'primary' | 'secondary'notisPrimary?: boolean - Consistent naming — same patterns everywhere, reduces AI search space
- CSS variables for theming — AI handles CSS vars naturally vs JS theme providers
.describe()on every prop — this IS the documentation- Runtime validation feedback — AI generates, validates, fixes. Feedback loop