Compare commits
No commits in common. "bf576905a7cdf6a9308cf5f003840bb4df6ed81a" and "e8e811f711acffdc7fb5fc8ef5e639802d946bbc" have entirely different histories.
bf576905a7
...
e8e811f711
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
# .github/workflows/ci.yml
|
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Biome check
|
|
||||||
run: pnpm biome check packages/
|
|
||||||
|
|
||||||
- name: ESLint (Solid rules)
|
|
||||||
run: pnpm eslint packages --ext .ts,.tsx
|
|
||||||
|
|
||||||
- name: Type check
|
|
||||||
run: pnpm -r typecheck
|
|
||||||
|
|
||||||
- name: Tests
|
|
||||||
run: pnpm -r test
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm -r build
|
|
||||||
36
biome.json
36
biome.json
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
|
|
||||||
"organizeImports": { "enabled": true },
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"rules": {
|
|
||||||
"recommended": true,
|
|
||||||
"suspicious": {
|
|
||||||
"noExplicitAny": "error"
|
|
||||||
},
|
|
||||||
"style": {
|
|
||||||
"useConst": "error",
|
|
||||||
"useTemplate": "error"
|
|
||||||
},
|
|
||||||
"a11y": {
|
|
||||||
"useSemanticElements": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"indentStyle": "space",
|
|
||||||
"indentWidth": 2,
|
|
||||||
"lineWidth": 100
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"quoteStyle": "double",
|
|
||||||
"semicolons": "always",
|
|
||||||
"trailingCommas": "all"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"ignore": ["node_modules", "dist", ".tsdown", "coverage"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,323 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import solid from "eslint-plugin-solid";
|
|
||||||
import tsParser from "@typescript-eslint/parser";
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
files: ["packages/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: tsParser,
|
|
||||||
},
|
|
||||||
plugins: { solid },
|
|
||||||
rules: {
|
|
||||||
"solid/reactivity": "error",
|
|
||||||
"solid/no-destructure": "error",
|
|
||||||
"solid/prefer-for": "warn",
|
|
||||||
"solid/no-react-deps": "error",
|
|
||||||
"solid/no-react-specific-props": "error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: ["node_modules/**", "dist/**", "coverage/**"],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -4,16 +4,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -r build",
|
"build": "pnpm -r build",
|
||||||
"test": "pnpm -r test",
|
"test": "pnpm -r test",
|
||||||
"lint": "biome check . && eslint packages --ext .ts,.tsx",
|
|
||||||
"format": "biome format --write .",
|
|
||||||
"typecheck": "pnpm -r typecheck"
|
"typecheck": "pnpm -r typecheck"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.0",
|
"typescript": "^6.0.2"
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
|
||||||
"eslint": "^9.0.0",
|
|
||||||
"eslint-plugin-solid": "^0.14.0",
|
|
||||||
"typescript": "^6.0.2",
|
|
||||||
"zod": "^4.3.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user