PettyUI/docs/superpowers/specs/2026-03-30-pettyui-v2-web-components-design.md
2026-03-30 12:08:51 +07:00

379 lines
14 KiB
Markdown

# PettyUI v2: Web Components Architecture
## Overview
PettyUI v2 is a complete rewrite from SolidJS to vanilla Web Components. Zero framework dependencies. Zero external runtime. Built on modern web platform APIs (Popover, `<dialog>`, Invoker Commands, Navigation API, View Transitions). AI-native with Zod schemas and MCP tools.
**Goal:** The smallest, fastest headless UI component library that exists, because it builds on the browser instead of replacing it. Capable of powering full SPA experiences with zero client-side framework.
**Non-goals:** Styled components. Shadow DOM. Framework-specific bindings. Build step requirements.
## Architecture
### Core Principles
1. **Browser-first** — use native APIs before writing JS. Popover API for overlays, `<dialog>` for modals, Invoker Commands for triggers, Navigation API for routing, View Transitions for animations, `<details>` for disclosure.
2. **Progressive enhancement** — components that can work before JS loads DO work before JS loads. JS upgrades add ARIA, keyboard navigation, and programmatic APIs.
3. **Zero dependencies** — no Lit, no Floating UI, no framework. The only runtime is a ~30-line signals utility (~500 bytes) bundled inline.
4. **AI-native** — Zod schemas describe every component's API. MCP tools (discover, inspect, compose, validate, add) let AI agents generate correct HTML without reading docs.
5. **Full SPA capability** — Navigation API + View Transitions replace client-side routers. PettyUI apps feel like SPAs with zero framework overhead.
### Tech Stack
- **Language:** TypeScript, compiled to ES modules
- **Reactivity:** Homegrown signals (~30 lines, ~500 bytes), aligned with TC39 Signals proposal
- **Components:** Custom Elements v1 (no Shadow DOM)
- **Positioning:** Simple CSS (relative/absolute). Consumer adds Floating UI if they need viewport-edge flipping — that's a styling concern, not a library concern.
- **Overlays:** Popover API + `<dialog>` element
- **Triggers:** Invoker Commands API (`commandfor`/`command` attributes)
- **Routing:** Navigation API (browser-native SPA router, Baseline Jan 2026)
- **Transitions:** View Transitions API (smooth page/component animations, Baseline 2025)
- **Build:** tsdown (same as v1)
- **Testing:** Vitest + @web/test-runner (real browser tests for Custom Elements)
- **Validation:** Zod v4 schemas (carried over from v1)
### Signals Utility
The entire reactivity system, bundled with the library (not an external dep):
```ts
let current: (() => void) | null = null;
interface Signal<T> {
get(): T;
set(value: T): void;
}
function signal<T>(value: T): Signal<T> {
const subs = new Set<() => void>();
return {
get() { if (current) subs.add(current); return value; },
set(v: T) { value = v; for (const fn of subs) fn(); },
};
}
function effect(fn: () => void): void {
const prev = current;
current = fn;
fn();
current = prev;
}
```
Components use `signal()` for internal state and `effect()` to sync state to DOM attributes/properties.
## Component Anatomy
### Three Tiers
**Tier 1 — HTML-native (works without JS):**
- Dialog, Popover, Tooltip, Collapsible/Accordion (via `<details>`), Alert, Separator, Link
- JS upgrade adds: ARIA linking, keyboard nav, custom events, programmatic API
**Tier 2 — Popover-enhanced (partially works without JS):**
- Select, DropdownMenu, ContextMenu, HoverCard, DatePicker, CommandPalette
- Popover API handles open/close. JS adds: keyboard nav, typeahead, ARIA, selection state
**Tier 3 — JS-required (inert until upgrade):**
- Tabs, Slider, Calendar, DataTable, VirtualList, Combobox, Wizard, Form, NumberField, Toast, Pagination
- These components need JS for core functionality. They render as plain HTML until the custom element upgrades.
### Composition: Nested Custom Elements
Complex components use nested custom elements. Each part is a registered element with its own lifecycle. Parts find their parent via `this.closest()`.
```html
<petty-dialog>
<button commandfor="my-dlg" command="show-modal">Open</button>
<dialog id="my-dlg">
<petty-dialog-title>Confirm</petty-dialog-title>
<petty-dialog-description>Are you sure?</petty-dialog-description>
<button commandfor="my-dlg" command="close">Cancel</button>
</dialog>
</petty-dialog>
```
**Rule:** If a part needs its own behavior (keyboard handling, ARIA management, event emission), it's a custom element. If it's pure structure, it's a plain element with `data-part` for styling hooks.
### Data Attributes
Following the established convention (Radix, Shoelace):
- `data-state` — component state: `open`, `closed`, `active`, `inactive`, `checked`, `unchecked`, `on`, `off`
- `data-part` — structural identification for CSS targeting
- `data-disabled` — disabled state flag
- `data-orientation``horizontal` or `vertical`
### Event Pattern
Components dispatch standard `CustomEvent`s with the `petty-` prefix:
```ts
this.dispatchEvent(new CustomEvent("petty-close", {
bubbles: true,
detail: { value: "confirm" },
}));
```
Consumers listen with standard DOM:
```js
dialog.addEventListener("petty-close", (e) => {
if (e.detail.value === "confirm") deleteItem();
});
```
### Property/Attribute Reflection
Components expose both property and attribute APIs:
```ts
// Attribute API (HTML)
<petty-select value="apple">
// Property API (JS)
document.querySelector("petty-select").value = "apple";
```
Properties are the source of truth. Attributes reflect via `attributeChangedCallback` → property setter. This avoids the string-only limitation of attributes for complex values.
## Component List (44 components, same as v1)
### Layout & Display (6)
- `petty-avatar` — image with fallback
- `petty-badge` — status indicator (simple element, no sub-parts)
- `petty-card` — container with `petty-card-header`, `petty-card-content`, `petty-card-footer`
- `petty-image` — image with fallback
- `petty-separator``<hr>` with role and orientation
- `petty-skeleton` — loading placeholder
### Inputs & Forms (14)
- `petty-button``<button>` wrapper with loading/disabled state
- `petty-text-field` — label + input + description + error
- `petty-number-field` — input with increment/decrement
- `petty-checkbox` — tri-state checkbox
- `petty-switch` — on/off toggle
- `petty-radio-group` + `petty-radio-item` — mutually exclusive options
- `petty-slider` — range input with track/thumb
- `petty-toggle` — pressed/unpressed button
- `petty-toggle-group` + items — single/multi toggle selection
- `petty-select` + `petty-select-trigger`, `petty-select-content`, `petty-select-option` — dropdown select using Popover API
- `petty-combobox` + parts — searchable select
- `petty-listbox` + `petty-listbox-option` — inline selectable list
- `petty-form` + `petty-form-field` — Zod-validated form
- `petty-date-picker` + parts — date input with calendar popover
### Navigation (8)
- `petty-link` — anchor with external/disabled support
- `petty-breadcrumbs` + `petty-breadcrumb-item` — navigation trail
- `petty-tabs` + `petty-tab`, `petty-tab-panel` — tabbed interface
- `petty-accordion` + `petty-accordion-item` — collapsible sections (uses `<details>` internally)
- `petty-collapsible` — single disclosure (uses `<details>` internally)
- `petty-pagination` + parts — page navigation
- `petty-nav-menu` + parts — horizontal nav with dropdowns
- `petty-wizard` + parts — multi-step flow
### Overlays (9)
- `petty-dialog` — wraps native `<dialog>` with ARIA + events
- `petty-alert-dialog` — confirmation dialog
- `petty-drawer` — slide-in panel (uses `<dialog>`)
- `petty-popover` — floating content (uses Popover API)
- `petty-tooltip` — hover label (uses Popover API + CSS Anchor)
- `petty-hover-card` — rich hover preview
- `petty-dropdown-menu` + items — action menu (uses Popover API)
- `petty-context-menu` + items — right-click menu
- `petty-command-palette` + parts — search-driven command menu
### Feedback & Status (4)
- `petty-alert` — inline status message
- `petty-toast` + `petty-toast-region` — temporary notification
- `petty-progress` — progress bar
- `petty-meter` — value gauge
### Data (3)
- `petty-calendar` + parts — month grid date picker
- `petty-data-table` + parts — sortable/filterable table
- `petty-virtual-list` — windowed scroll
## SPA Router (petty-router)
PettyUI includes an optional ~15-line SPA router built entirely on browser standards. No external dependency.
### How it works
The Navigation API (Baseline January 2026) provides browser-native SPA routing. View Transitions API (Baseline 2025) animates page swaps. Together they replace React Router, Vue Router, and every other client-side routing library.
```ts
// petty-router: the entire implementation
navigation.addEventListener("navigate", (event) => {
const url = new URL(event.destination.url);
if (url.origin !== location.origin) return;
event.intercept({
async handler() {
const res = await fetch(url.pathname, {
headers: { "X-Partial": "true" }
});
const html = await res.text();
document.startViewTransition(() => {
document.querySelector("[data-petty-outlet]").innerHTML = html;
});
}
});
});
```
### Consumer usage
```html
<script type="module">
import "pettyui/router";
</script>
<nav>
<a href="/dashboard">Dashboard</a>
<a href="/settings">Settings</a>
</nav>
<main data-petty-outlet>
<!-- Content swapped here on navigation -->
</main>
```
Clicking a link: Navigation API intercepts → fetches HTML from server → View Transitions animates the swap → PettyUI components in the new HTML upgrade instantly. Back/forward buttons work. URL bar updates. No full page reload.
### What the server does
The server checks for the `X-Partial` header. If present, it returns just the page fragment (no `<html>`, `<head>`, etc.). If absent, it returns the full page (for direct URL visits, bookmarks, SEO crawlers).
### Browser support
| API | Chrome | Firefox | Safari | Edge |
|-----|--------|---------|--------|------|
| Navigation API | 102+ | 136+ | 18.2+ | 102+ |
| View Transitions (same-doc) | 111+ | 140+ | 18+ | 111+ |
Both are Baseline. For the rare old browser without support, links just work as normal full-page navigations — progressive enhancement.
## Platform APIs Usage Map
| Browser API | Replaces (v1) | Used by |
|-------------|---------------|---------|
| Popover API | `createDismiss()`, z-index management | Select, DropdownMenu, ContextMenu, Tooltip, Popover, HoverCard, CommandPalette, DatePicker |
| `<dialog>` | `createFocusTrap()`, scroll lock, backdrop | Dialog, AlertDialog, Drawer |
| Invoker Commands | JS click handlers for open/close | Dialog, AlertDialog, Popover triggers |
| Navigation API | React Router / client-side routers | petty-router (SPA navigation) |
| View Transitions | Animation JS / Framer Motion | petty-router (page transitions), Accordion, Dialog enter/exit |
| `<details>`/`<summary>` | Disclosure state management | Accordion, Collapsible |
| `inert` attribute | Manual aria-hidden + tabindex management | Dialog background freeze |
## File Structure
```
packages/core/
src/
signals.ts — signal() + effect() (~30 lines)
router.ts — petty-router SPA navigation (~15 lines)
shared/
keyboard.ts — shared keyboard navigation utilities
aria.ts — shared ARIA helpers
components/
dialog/
dialog.ts — PettyDialog custom element
dialog-title.ts — PettyDialogTitle
dialog-description.ts
dialog.schema.ts — Zod schema (carried from v1)
dialog.test.ts
index.ts — registration + exports
select/
select.ts
select-trigger.ts
select-content.ts
select-option.ts
select.schema.ts
select.test.ts
index.ts
... (same pattern for all 44)
index.ts — registers all elements, exports all
package.json
tsconfig.json
```
Each component is independently importable:
```ts
import "pettyui/dialog"; // registers <petty-dialog> etc.
```
Or consumers include a script tag:
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/pettyui/dialog"></script>
```
## Migration from v1
### What carries over unchanged
- Zod schemas (props definitions, validation)
- MCP tools architecture (discover, inspect, compose, validate, add)
- Component registry metadata
- Test scenarios (same behaviors, different implementation)
- NagLint rules
### What gets rewritten
- All 44 component implementations (SolidJS JSX → Custom Elements)
- Utility primitives (createFloating → CSS anchor, createDismiss → Popover API, createFocusTrap → `<dialog>`)
- Test harness (SolidJS testing library → DOM-based testing)
### What gets deleted
- `solid-js` peer dependency
- `@floating-ui/dom` dependency
- `vite-plugin-solid`
- All `.tsx` files (replaced by `.ts`)
- Compound component Object.assign pattern
## Phased Rollout
### Phase 1: Foundation
- Signals utility
- Shared utilities (keyboard, ARIA, anchor fallback)
- Base component patterns established
### Phase 2: Simple Components (Tier 1 + easy Tier 3)
- Dialog, Alert, AlertDialog, Collapsible, Accordion
- Button, Badge, Separator, Link, Toggle, Skeleton, Avatar, Image
- Progress, Meter
### Phase 3: Selection & Input Components
- Select, Combobox, Listbox
- TextField, NumberField, Checkbox, Switch, RadioGroup, Slider
- ToggleGroup, Form
### Phase 4: Complex Components
- Tabs, Popover, Tooltip, HoverCard, DropdownMenu, ContextMenu
- Drawer, CommandPalette, DatePicker, Calendar
- Pagination, NavigationMenu, Wizard, Breadcrumbs
### Phase 5: Data Components + MCP Update
- DataTable, VirtualList, Toast
- Update MCP tools for new architecture
- Update registry
## Size Budget
| Item | v1 (SolidJS) | v2 (Web Components) |
|------|-------------|---------------------|
| Framework runtime | solid-js ~23KB | 0KB |
| Floating UI | @floating-ui/dom ~15KB | 0KB (simple CSS) |
| Router | N/A (framework-specific) | ~400 bytes (Navigation API wrapper) |
| Signals utility | (included in solid-js) | ~500 bytes |
| Per-component avg | ~2-4KB | ~0.5-2KB |
| **Total (all 44 + router)** | **~80-100KB** | **~20-35KB** |
## Success Criteria
1. Zero external runtime dependencies
2. Every component passes existing accessibility test scenarios
3. Tier 1 components function without JavaScript
4. Total gzipped bundle (all 44 components) under 15KB
5. INP < 100ms on component interactions (no hydration)
6. MCP tools work with the new architecture
7. Works in Chrome, Firefox, Safari, Edge (latest 2 versions)