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.
324 lines
11 KiB
Markdown
324 lines
11 KiB
Markdown
# 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
|