diff --git a/packages/jumble/src/iframe-ctx.ts b/packages/jumble/src/iframe-ctx.ts
index 1e8700564..1ea2dff73 100644
--- a/packages/jumble/src/iframe-ctx.ts
+++ b/packages/jumble/src/iframe-ctx.ts
@@ -3,7 +3,7 @@ import {
IPC,
setIframeContextHandler,
} from "@commontools/iframe-sandbox";
-import { components } from "@commontools/ui";
+import { v1 } from "@commontools/ui";
import {
Action,
addCommonIDfromObjectID,
@@ -20,7 +20,7 @@ import {
updateJob,
} from "@/contexts/ActivityContext.tsx";
-const CommonCharmElement = components.CommonCharm.CommonCharmElement;
+const CommonCharmElement = v1.components.CommonCharm.CommonCharmElement;
const llm = new LLMClient();
// FIXME(ja): perhaps this could be in common-charm? needed to enable iframe with sandboxing
diff --git a/packages/ui/.claude/settings.local.json b/packages/ui/.claude/settings.local.json
new file mode 100644
index 000000000..460fce581
--- /dev/null
+++ b/packages/ui/.claude/settings.local.json
@@ -0,0 +1,13 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(deno check:*)",
+ "Bash(deno lint:*)",
+ "Bash(deno fmt:*)",
+ "Bash(find:*)",
+ "Bash(ls:*)",
+ "Bash(grep:*)"
+ ],
+ "deny": []
+ }
+}
diff --git a/packages/ui/LLM-COMPONENT-INSTRUCTIONS.md b/packages/ui/LLM-COMPONENT-INSTRUCTIONS.md
new file mode 100644
index 000000000..d415f1d2b
--- /dev/null
+++ b/packages/ui/LLM-COMPONENT-INSTRUCTIONS.md
@@ -0,0 +1,681 @@
+# LLM Component Composition Guide
+
+This document provides comprehensive component specifications for Language
+Models to assist with web component composition using the Common CT library.
+
+## Component Library Overview
+
+The Common CT library provides 39 secure web components that follow the
+shadcn/ui design system. All components:
+
+- Use custom element tags prefixed with `ct-`
+- Support Shadow DOM encapsulation
+- Emit custom events prefixed with `ct-`
+- Follow strict security constraints (no external resources, limited events)
+
+## Component Reference
+
+### 1. ct-button
+
+**Purpose**: Interactive button element **Tag**: `` **Attributes**:
+
+- `variant` - "default" | "destructive" | "outline" | "secondary" | "ghost" |
+ "link"
+- `size` - "default" | "sm" | "lg" | "icon"
+- `disabled` - boolean
+- `type` - "button" | "submit" | "reset" **Events**:
+- `ct-click` - Fired on click with detail: `{ variant, size }` **Slots**:
+ Default slot for button content **Example**:
+
+```html
+Click Me
+```
+
+### 2. ct-input
+
+**Purpose**: Text input field **Tag**: `` **Attributes**:
+
+- `type` - "text" | "email" | "password" | "number" | "search" | "tel" | "url" |
+ "date" | "time" | "datetime-local"
+- `placeholder` - string
+- `value` - string
+- `disabled` - boolean
+- `readonly` - boolean
+- `required` - boolean
+- `name` - string
+- `min` - string/number
+- `max` - string/number
+- `step` - string/number
+- `pattern` - string
+- `autocomplete` - string **Events**:
+- `ct-input` - Fired on input with detail: `{ value, name }`
+- `ct-change` - Fired on change with detail: `{ value, name }`
+- `ct-focus` - Fired on focus
+- `ct-blur` - Fired on blur **Example**:
+
+```html
+
+```
+
+### 3. ct-textarea
+
+**Purpose**: Multi-line text input **Tag**: `` **Attributes**:
+
+- `placeholder` - string
+- `value` - string
+- `disabled` - boolean
+- `readonly` - boolean
+- `required` - boolean
+- `name` - string
+- `rows` - number
+- `cols` - number
+- `maxlength` - number
+- `auto-resize` - boolean **Events**:
+- `ct-input` - Fired on input with detail: `{ value, name }`
+- `ct-change` - Fired on change with detail: `{ value, name }` **Example**:
+
+```html
+
+```
+
+### 4. ct-checkbox
+
+**Purpose**: Binary selection input **Tag**: `` **Attributes**:
+
+- `checked` - boolean
+- `disabled` - boolean
+- `name` - string
+- `value` - string
+- `required` - boolean
+- `indeterminate` - boolean **Events**:
+- `ct-change` - Fired on change with detail: `{ checked, indeterminate }`
+
+**Example**:
+
+```html
+Accept terms
+```
+
+### 5. ct-radio
+
+**Purpose**: Single selection from group **Tag**: `` **Attributes**:
+
+- `checked` - boolean
+- `disabled` - boolean
+- `name` - string (required for grouping)
+- `value` - string (required)
+- `required` - boolean **Events**:
+- `ct-change` - Fired on change with detail: `{ value, checked }` **Note**: Must
+ be used within `ct-radio-group` for proper functionality **Example**:
+
+```html
+
+ Red
+ Blue
+
+```
+
+### 6. ct-radio-group
+
+**Purpose**: Container for radio buttons **Tag**: ``
+**Attributes**:
+
+- `name` - string (required)
+- `value` - string (currently selected value)
+- `disabled` - boolean **Events**:
+- `ct-change` - Fired when selection changes with detail: `{ value }` **Slots**:
+ Default slot for ct-radio elements
+
+### 7. ct-switch
+
+**Purpose**: Toggle switch **Tag**: `` **Attributes**:
+
+- `checked` - boolean
+- `disabled` - boolean
+- `name` - string **Events**:
+- `ct-change` - Fired on toggle with detail: `{ checked }` **Example**:
+
+```html
+Enable notifications
+```
+
+### 8. ct-slider
+
+**Purpose**: Range input slider **Tag**: `` **Attributes**:
+
+- `value` - number
+- `min` - number (default: 0)
+- `max` - number (default: 100)
+- `step` - number (default: 1)
+- `disabled` - boolean
+- `name` - string **Events**:
+- `ct-change` - Fired on value change with detail: `{ value }` **Example**:
+
+```html
+
+```
+
+### 9. ct-toggle
+
+**Purpose**: Toggle button **Tag**: `` **Attributes**:
+
+- `pressed` - boolean
+- `disabled` - boolean
+- `variant` - "default" | "outline"
+- `size` - "default" | "sm" | "lg"
+- `value` - string (for toggle groups) **Events**:
+- `ct-change` - Fired on toggle with detail: `{ pressed }` **Slots**: Default
+ slot for content **Example**:
+
+```html
+Bold
+```
+
+### 10. ct-toggle-group
+
+**Purpose**: Group of toggle buttons **Tag**: ``
+**Attributes**:
+
+- `type` - "single" | "multiple"
+- `value` - string (for single) | string[] (for multiple)
+- `disabled` - boolean **Events**:
+- `ct-change` - Fired on selection change with detail: `{ value }` **Slots**:
+ Default slot for ct-toggle elements
+
+### 11. ct-label
+
+**Purpose**: Form field label **Tag**: `` **Attributes**:
+
+- `for` - string (ID of associated input)
+- `required` - boolean (shows asterisk)
+- `disabled` - boolean **Events**:
+- `ct-label-click` - Fired on click with detail: `{ targetId, targetElement }`
+
+**Slots**: Default slot for label text **Example**:
+
+```html
+Email Address
+
+```
+
+### 12. ct-card
+
+**Purpose**: Content container **Tag**: `` **Attributes**: None
+**Events**: None **Slots**:
+
+- `header` - Card header content
+- `content` - Main card content
+- `footer` - Card footer content **Example**:
+
+```html
+
+ Card Title
+ Card content goes here
+ Action
+
+```
+
+### 13. ct-badge
+
+**Purpose**: Status indicator or label **Tag**: `` **Attributes**:
+
+- `variant` - "default" | "secondary" | "destructive" | "outline"
+- `removable` - boolean (shows X button) **Events**:
+- `ct-remove` - Fired when X clicked (if removable) **Slots**: Default slot for
+ badge text **Example**:
+
+```html
+Status
+```
+
+### 14. ct-alert
+
+**Purpose**: Alert message display **Tag**: `` **Attributes**:
+
+- `variant` - "default" | "destructive"
+- `dismissible` - boolean **Events**:
+- `ct-dismiss` - Fired when dismissed **Slots**:
+- `icon` - Alert icon
+- `title` - Alert title
+- `description` - Alert description
+- Default slot - Alert content **Example**:
+
+```html
+
+ ⚠️
+ Error
+ Something went wrong
+
+```
+
+### 15. ct-separator
+
+**Purpose**: Visual divider **Tag**: `` **Attributes**:
+
+- `orientation` - "horizontal" | "vertical"
+- `decorative` - boolean **Example**:
+
+```html
+
+```
+
+### 16. ct-progress
+
+**Purpose**: Progress indicator **Tag**: `` **Attributes**:
+
+- `value` - number (0-100)
+- `max` - number (default: 100)
+- `indeterminate` - boolean **Example**:
+
+```html
+
+```
+
+### 17. ct-skeleton
+
+**Purpose**: Loading placeholder **Tag**: `` **Attributes**: None
+(style with CSS width/height) **Example**:
+
+```html
+
+```
+
+### 18. ct-accordion
+
+**Purpose**: Collapsible content panels **Tag**: ``
+**Attributes**:
+
+- `type` - "single" | "multiple"
+- `value` - string | string[] (open items)
+- `collapsible` - boolean (for single type) **Events**:
+- `ct-change` - Fired on expand/collapse with detail: `{ value }` **Slots**:
+ Default slot for ct-accordion-item elements **Example**:
+
+```html
+
+
+ Section 1
+ Content 1
+
+
+```
+
+### 19. ct-accordion-item
+
+**Purpose**: Individual accordion panel **Tag**: ``
+**Attributes**:
+
+- `value` - string (required, unique identifier)
+- `disabled` - boolean **Slots**:
+- `trigger` - Clickable header
+- `content` - Collapsible content
+
+### 20. ct-collapsible
+
+**Purpose**: Single collapsible section **Tag**: ``
+**Attributes**:
+
+- `open` - boolean
+- `disabled` - boolean **Events**:
+- `ct-toggle` - Fired on open/close with detail: `{ open }` **Slots**:
+- `trigger` - Clickable trigger element
+- `content` - Collapsible content
+
+### 21. ct-tabs
+
+**Purpose**: Tabbed interface container **Tag**: `` **Attributes**:
+
+- `default-value` - string (initially active tab)
+- `orientation` - "horizontal" | "vertical" **Events**:
+- `ct-change` - Fired on tab change with detail: `{ value }` **Slots**: Default
+ slot for ct-tab-list and ct-tab-panel elements **Example**:
+
+```html
+
+
+ Tab 1
+ Tab 2
+
+ Content 1
+ Content 2
+
+```
+
+### 22. ct-tab-list
+
+**Purpose**: Container for tab buttons **Tag**: `` **Slots**:
+Default slot for ct-tab elements
+
+### 23. ct-tab
+
+**Purpose**: Individual tab button **Tag**: `` **Attributes**:
+
+- `value` - string (required)
+- `disabled` - boolean **Events**:
+- `click` - Native click event
+
+### 24. ct-tab-panel
+
+**Purpose**: Tab content panel **Tag**: `` **Attributes**:
+
+- `value` - string (required, matches tab value) **Slots**: Default slot for
+ content
+
+### 25. ct-scroll-area
+
+**Purpose**: Custom scrollable area **Tag**: `` **Attributes**:
+
+- `orientation` - "vertical" | "horizontal" | "both" **Slots**: Default slot for
+ scrollable content **Example**:
+
+```html
+
+ Long content...
+
+```
+
+### 26. ct-aspect-ratio
+
+**Purpose**: Maintains aspect ratio of content **Tag**: ``
+**Attributes**:
+
+- `ratio` - string (e.g., "16/9", "1/1", "4/3") **Slots**: Default slot for
+ content **Example**:
+
+```html
+
+ Video placeholder
+
+```
+
+### 27. ct-form
+
+**Purpose**: Form wrapper with validation **Tag**: `` **Attributes**:
+
+- `action` - string
+- `method` - string
+- `novalidate` - boolean **Events**:
+- `ct-submit` - Fired on valid submission with detail: `{ formData }`
+- `ct-invalid` - Fired on validation failure with detail: `{ errors }`
+
+**Slots**: Default slot for form elements **Methods**:
+
+- `submit()` - Programmatically submit
+- `reset()` - Reset form
+- `validate()` - Validate and return boolean
+
+### 28. ct-input-otp
+
+**Purpose**: One-time password input **Tag**: `` **Attributes**:
+
+- `length` - number (default: 6)
+- `value` - string
+- `disabled` - boolean
+- `name` - string **Events**:
+- `ct-change` - Fired on value change with detail: `{ value, complete }`
+- `ct-complete` - Fired when all digits entered with detail: `{ value }`
+
+**Methods**:
+
+- `focus()` - Focus first input
+- `clear()` - Clear all inputs **Example**:
+
+```html
+
+```
+
+### 29. ct-resizable-panel-group
+
+**Purpose**: Container for resizable panels **Tag**:
+`` **Attributes**:
+
+- `direction` - "horizontal" | "vertical" **Events**:
+- `ct-layout` - Fired on resize with detail: `{ sizes }` **Slots**: Default slot
+ for panels and handles
+
+### 30. ct-resizable-panel
+
+**Purpose**: Individual resizable panel **Tag**: ``
+**Attributes**:
+
+- `default-size` - number (percentage)
+- `min-size` - number (percentage)
+- `max-size` - number (percentage)
+- `collapsible` - boolean **Slots**: Default slot for content
+
+### 31. ct-resizable-handle
+
+**Purpose**: Drag handle between panels **Tag**: ``
+**Attributes**: None
+
+## Layout Components
+
+### 32. ct-hstack
+
+**Purpose**: Horizontal flexbox container **Tag**: `` **Attributes**:
+
+- `gap` - "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8"
+- `align` - "start" | "center" | "end" | "stretch" | "baseline"
+- `justify` - "start" | "center" | "end" | "between" | "around" | "evenly"
+- `wrap` - boolean
+- `reverse` - boolean **Slots**: Default slot for child elements **Example**:
+
+```html
+
+ Left
+ Right
+
+```
+
+### 33. ct-vstack
+
+**Purpose**: Vertical flexbox container **Tag**: `` **Attributes**:
+Same as ct-hstack **Example**:
+
+```html
+
+ Card 1
+ Card 2
+
+```
+
+### 34. ct-hgroup
+
+**Purpose**: Horizontal group with semantic spacing **Tag**: ``
+**Attributes**:
+
+- `gap` - "xs" | "sm" | "md" | "lg" | "xl" **Slots**: Default slot for grouped
+ elements
+
+### 35. ct-vgroup
+
+**Purpose**: Vertical group with semantic spacing **Tag**: ``
+**Attributes**: Same as ct-hgroup
+
+### 36. ct-hscroll
+
+**Purpose**: Horizontal scroll container **Tag**: `` **Attributes**:
+
+- `fade-edges` - boolean (gradient fade on edges)
+- `show-scrollbar` - boolean
+- `snap` - boolean (scroll snapping) **Events**:
+- `ct-scroll` - Fired on scroll with detail:
+ `{ scrollLeft, scrollWidth, clientWidth }` **Methods**:
+- `scrollToX(x, smooth)` - Scroll to position
+- `scrollByX(x, smooth)` - Scroll by amount
+
+### 37. ct-vscroll
+
+**Purpose**: Vertical scroll container **Tag**: `` **Attributes**:
+
+- `height` - string (CSS height)
+- `fade-edges` - boolean
+- `show-scrollbar` - boolean
+- `snap` - boolean **Events**:
+- `ct-scroll` - Fired on scroll with detail:
+ `{ scrollTop, scrollHeight, clientHeight }` **Methods**:
+- `scrollToY(y, smooth)` - Scroll to position
+- `scrollByY(y, smooth)` - Scroll by amount
+
+### 38. ct-grid
+
+**Purpose**: CSS Grid container **Tag**: `` **Attributes**:
+
+- `columns` - number | string (e.g., "3" or "repeat(auto-fit, minmax(200px,
+ 1fr))")
+- `rows` - number | string
+- `gap` - "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8"
+- `column-gap` - same as gap
+- `row-gap` - same as gap
+- `areas` - string (grid template areas)
+- `auto-flow` - "row" | "column" | "dense" | "row dense" | "column dense"
+
+**Example**:
+
+```html
+
+ Item 1
+ Item 2
+ Item 3
+
+```
+
+### 39. ct-table
+
+**Purpose**: Semantic HTML table **Tag**: `` **Attributes**:
+
+- `striped` - boolean (zebra stripes)
+- `bordered` - boolean
+- `hover` - boolean (row hover effect)
+- `compact` - boolean (reduced padding)
+- `fixed` - boolean (fixed layout) **Slots**: Default slot for thead, tbody,
+ tfoot **Example**:
+
+```html
+
+
+
+ | Name |
+ Value |
+
+
+
+
+ | Item 1 |
+ 100 |
+
+
+
+```
+
+## Component Composition Guidelines
+
+### Form Example
+
+```html
+
+
+
+ Full Name
+
+
+
+
+ Email
+
+
+
+
+ Message
+
+
+
+
+ Cancel
+ Submit
+
+
+
+```
+
+### Dashboard Layout Example
+
+```html
+
+
+ Dashboard
+
+
+
+ Active
+ Total Users
+ 1,234
+
+
+
+
+
+
+
+
+ Overview
+ Analytics
+ Reports
+
+
+
+
+
+
+```
+
+## Event Handling Patterns
+
+All components emit custom events with the `ct-` prefix. Event details are
+always in the `detail` property:
+
+```javascript
+document.querySelector("ct-button").addEventListener("ct-click", (e) => {
+ console.log("Button clicked:", e.detail);
+});
+
+document.querySelector("ct-input").addEventListener("ct-change", (e) => {
+ console.log("Input value:", e.detail.value);
+});
+
+document.querySelector("ct-form").addEventListener("ct-submit", (e) => {
+ e.preventDefault();
+ console.log("Form data:", e.detail.formData);
+});
+```
+
+## Styling Components
+
+Components expose CSS custom properties and parts for styling:
+
+```css
+/* Custom properties */
+ct-button {
+ --background: #3b82f6;
+ --foreground: white;
+}
+
+/* CSS parts */
+ct-input::part(input) {
+ font-family: monospace;
+}
+
+ct-card::part(header) {
+ background: #f3f4f6;
+}
+```
+
+## Security Constraints
+
+When composing components, remember:
+
+- No `
`, `