# 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)
