@vielzeug/codex 26.11.5 → 26.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/data/catalog.json +44 -44
  2. package/data/llms-full.txt +3269 -2941
  3. package/data/llms.txt +1 -1
  4. package/data/manifest.json +1 -1
  5. package/data/packages/archive.json +3 -3
  6. package/data/packages/arsenal.json +2 -2
  7. package/data/packages/assay.json +2 -2
  8. package/data/packages/beacon.json +3 -3
  9. package/data/packages/clockwork.json +3 -3
  10. package/data/packages/codex.json +3 -3
  11. package/data/packages/coins.json +2 -2
  12. package/data/packages/conduit.json +2 -2
  13. package/data/packages/courier.json +3 -3
  14. package/data/packages/dnd.json +2 -2
  15. package/data/packages/familiar.json +2 -2
  16. package/data/packages/flux.json +3 -3
  17. package/data/packages/focus.json +3 -3
  18. package/data/packages/forge.json +3 -3
  19. package/data/packages/gesture.json +3 -3
  20. package/data/packages/herald.json +3 -3
  21. package/data/packages/illusionist.json +4 -4
  22. package/data/packages/keymap.json +3 -3
  23. package/data/packages/ledger.json +3 -3
  24. package/data/packages/lingua.json +3 -3
  25. package/data/packages/mesh.json +3 -3
  26. package/data/packages/necromancer.json +4 -4
  27. package/data/packages/orbit.json +3 -3
  28. package/data/packages/ore.json +2 -2
  29. package/data/packages/postmaster.json +2 -2
  30. package/data/packages/prism.json +3 -3
  31. package/data/packages/pulse.json +3 -3
  32. package/data/packages/refine.json +2 -2
  33. package/data/packages/ripple.json +2 -2
  34. package/data/packages/rune.json +3 -3
  35. package/data/packages/sandbox.json +2 -2
  36. package/data/packages/scout.json +3 -3
  37. package/data/packages/scroll.json +3 -3
  38. package/data/packages/sentinel.json +2 -2
  39. package/data/packages/sigil.json +3 -3
  40. package/data/packages/sourcerer.json +2 -2
  41. package/data/packages/spell.json +3 -3
  42. package/data/packages/tandem.json +2 -2
  43. package/data/packages/tavern.json +2 -2
  44. package/data/packages/tempo.json +2 -2
  45. package/data/packages/vault.json +4 -4
  46. package/data/packages/ward.json +2 -2
  47. package/data/packages/wayfinder.json +3 -3
  48. package/data/refine.json +4176 -4176
  49. package/data/search.json +115 -115
  50. package/package.json +14 -14
  51. package/skills/vielzeug/SKILL.md +19 -17
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "apiSource": "/**\n * Refine components register through their explicit component entry points.\n *\n * Keeping the package root free of registration side effects makes dependency\n * ownership and bundle contents obvious to application code.\n */\n\nexport { RefineError } from './errors';\nexport type { IconAttributes, IconDefinition, IconElement } from './icons';\nexport { registerIcons } from './icons';\nexport { eventFieldChecked, eventFieldValue } from './inputs/shared/native-field-event';\nexport { getMotionMode, MOTION_MODES, type MotionMode, setMotionMode } from './motion';\nexport type { Reflow, ReflowOptions } from './reflow';\nexport { animateReflow } from './reflow';\n",
3
3
  "docs": {
4
- "index": "---\ntitle: 'Refine: Web component library'\ndescription: Accessible, themeable web components built with Ore for framework and vanilla DOM apps.\npackage: refine\ncategory: ui-components\nkeywords: [web-components, accessible, themeable, ui, components, design-system]\nrelated: [ore, orbit, forge, keymap]\nexports:\n [\n ore-accordion,\n ore-accordion-item,\n ore-alert,\n ore-avatar,\n ore-avatar-group,\n ore-badge,\n ore-box,\n ore-breadcrumb,\n ore-breadcrumb-item,\n ore-button,\n ore-button-group,\n ore-calendar,\n ore-card,\n ore-carousel,\n ore-carousel-slide,\n ore-chat-message,\n ore-checkbox,\n ore-checkbox-group,\n ore-chip,\n ore-code-window,\n ore-color-picker,\n ore-column,\n ore-combobox,\n ore-combobox-option,\n ore-command-palette,\n ore-command-palette-item,\n ore-cookie-banner,\n ore-copy-command,\n ore-counter,\n ore-datagrid,\n ore-date-picker,\n ore-dialog,\n ore-dock,\n ore-drawer,\n ore-file-input,\n ore-grid,\n ore-grid-item,\n ore-icon,\n ore-input,\n ore-keyboard-key,\n ore-keyboard-shortcut,\n ore-list,\n ore-list-item,\n ore-marquee,\n ore-menu,\n ore-menu-item,\n ore-menu-separator,\n ore-message-composer,\n ore-navbar,\n ore-navbar-item,\n ore-navigation-menu,\n ore-navigation-menu-item,\n ore-navigation-menu-panel,\n ore-number-input,\n ore-otp-input,\n ore-pagination,\n ore-password-strength,\n ore-popover,\n ore-progress,\n ore-pull-to-refresh,\n ore-qr-code,\n ore-qr-scanner,\n ore-query-bar,\n ore-radio,\n ore-radio-group,\n ore-rating,\n ore-select,\n ore-separator,\n ore-sidebar,\n ore-sidebar-group,\n ore-sidebar-item,\n ore-skeleton,\n ore-slider,\n ore-speech-player,\n ore-speed-reading,\n ore-spotlight,\n ore-state,\n ore-stats,\n ore-step,\n ore-stepper,\n ore-switch,\n ore-tab-item,\n ore-tab-panel,\n ore-table,\n ore-tabs,\n ore-td,\n ore-text,\n ore-th,\n ore-tr,\n ore-textarea,\n ore-time-picker,\n ore-toast,\n ore-tooltip,\n ore-tour,\n ore-typing-indicator,\n ]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"refine\" />\n\n## Why Refine?\n\nEvery project needs UI primitives. Refine provides accessible web components that work natively anywhere HTML is rendered, no framework required.\n\n```html\n<!-- Before: roll your own button with ARIA -->\n<button class=\"btn btn-primary\" role=\"button\" aria-pressed=\"false\" tabindex=\"0\">\n <span class=\"btn-spinner\" aria-hidden=\"true\"></span>\n Save\n</button>\n\n<!-- After: Refine -->\n<ore-button variant=\"solid\" color=\"primary\" loading>Save</ore-button>\n```\n\n| Feature | Refine | Shoelace | Material Web |\n| ------------------ | ------------------------------------------- | ------------------------------------------ | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"refine\" type=\"size\" /> | ~145 kB | ~200 kB |\n| Built with | Ore | Lit | Lit |\n| Runtime dependencies | Vielzeug UI primitives; Lucide only for the opt-in full-library registrar | Lit + utilities | Lit + Material foundations |\n| Accessible | WCAG AA | WCAG AA | WCAG AA |\n| Framework agnostic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Refine when** you want accessible web components that match the Vielzeug design system without a heavy framework dependency.\n\n**Consider Shoelace or Material Web** if your team is already standardized on those ecosystems and you need their established component catalogs.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/refine\n```\n\n```sh [npm]\nnpm install @vielzeug/refine\n```\n\n```sh [yarn]\nyarn add @vielzeug/refine\n```\n\n:::\n\n## Quick Start\n\n```ts\n// 1. Import global styles once\nimport '@vielzeug/refine/fouc.css'; // Hide unupgraded custom elements until first paint\nimport '@vielzeug/refine/tokens.css'; // Tokens, animations, cascade layers\n\n// 2. Register only the elements you need\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport '@vielzeug/refine/card';\n```\n\n```html\n<ore-button variant=\"solid\" color=\"primary\">Save</ore-button>\n<ore-input label=\"Email\" type=\"email\" required></ore-input>\n<ore-card padding=\"lg\">\n <span slot=\"header\">Account</span>\n <p>Card content goes here.</p>\n</ore-card>\n```\n\n```ts\n```\n\n### CDN / Vanilla HTML\n\nUse the self-contained IIFE bundle to load Refine directly from a CDN in any HTML page: no build step required:\n\n```html\n<!-- 1. Styles -->\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/fouc.css\" />\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/tokens.css\" />\n\n<!-- 2. All components (IIFE: registers global Refine namespace) -->\n<script src=\"https://unpkg.com/@vielzeug/refine/dist/refine.iife.js\"></script>\n```\n\nFor bundler-based projects that still want a CDN URL, use the ESM bundle via an import map:\n\n```html\n<script type=\"importmap\">\n {\n \"imports\": {\n \"@vielzeug/refine\": \"https://esm.sh/@vielzeug/refine\",\n \"@vielzeug/refine/button\": \"https://esm.sh/@vielzeug/refine/button\",\n \"@vielzeug/refine/input\": \"https://esm.sh/@vielzeug/refine/input\"\n }\n }\n</script>\n\n<script type=\"module\">\n import '@vielzeug/refine/button';\n import '@vielzeug/refine/input';\n</script>\n```\n\n### Package Entry Points\n\n| Import | Purpose |\n| ------------------------ | ----------------------------------------- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Global design tokens and cascade layers |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser-default reset (includes FOUC suppression) |\n\nComponent registration happens through side-effect imports such as `@vielzeug/refine/button` and `@vielzeug/refine/dialog`.\n\n### Components\n\nEach entry below is a component with its own documentation page and registration subpath. Sub-element tags (`ore-accordion-item`, `ore-tab-item`, `ore-avatar-group`, and the rest) are documented on their parent's page and registered with it.\n\n**Content:** `ore-accordion`, `ore-avatar`, `ore-card`, `ore-carousel`, `ore-chat-message`, `ore-chat-panel`, `ore-code-window`, `ore-copy-command`, `ore-icon`, `ore-keyboard-key`, `ore-list`, `ore-marquee`, `ore-qr-code`, `ore-rank-list`, `ore-speech-player`, `ore-speed-reading`, `ore-stats`, `ore-table`, `ore-text`\n\n**Feedback:** `ore-alert`, `ore-badge`, `ore-chip`, `ore-cookie-banner`, `ore-password-strength`, `ore-progress`, `ore-pull-to-refresh`, `ore-skeleton`, `ore-state`, `ore-toast`, `ore-typing-indicator`\n\n**Inputs:** `ore-button`, `ore-calendar`, `ore-checkbox`, `ore-color-picker`, `ore-combobox`, `ore-counter`, `ore-datagrid`, `ore-date-picker`, `ore-file-input`, `ore-input`, `ore-message-composer`, `ore-number-input`, `ore-otp-input`, `ore-qr-scanner`, `ore-query-bar`, `ore-radio`, `ore-rating`, `ore-select`, `ore-slider`, `ore-switch`, `ore-textarea`, `ore-time-picker`\n\n**Layout:** `ore-box`, `ore-dock`, `ore-grid`, `ore-navbar`, `ore-separator`, `ore-sidebar`\n\n**Navigation:** `ore-breadcrumb`, `ore-navigation-menu`, `ore-pagination`, `ore-stepper`, `ore-tabs`\n\n**Overlay:** `ore-command-palette`, `ore-dialog`, `ore-drawer`, `ore-menu`, `ore-popover`, `ore-spotlight`, `ore-tooltip`, `ore-tour`\n\n## Component portfolio\n\nRefine keeps one public package and classifies components by composition level rather than stability. All listed components remain supported and tested.\n\n- **Foundations**: buttons, inputs, selection controls, accordions, dialogs, menus, tooltips, layout primitives, and feedback indicators. Start here when building an application-specific interface.\n- **Composed patterns**: data grids, command palettes, navigation menus, steppers, calendars, date/time pickers, file inputs, and message composers. These package recurring accessibility and interaction behavior that is costly to rebuild correctly.\n- **Presentation patterns**: cards, chat messages, code windows, stats, marquees, avatars, badges, and skeletons. Use these when their product vocabulary matches the application; prefer foundations when it does not.\n\nClassification guides discovery and bundle selection. It is not a reason to remove working components or move them into unstable packages; per-component subpath imports remain the tree-shaking boundary.\n\n## Features\n\n<div class=\"features-grid\">\n\n- **Accessible**: keyboard navigation, ARIA wiring, and focus management across interactive components\n- **Themeable**: global tokens plus component-level CSS custom properties\n- **Motion-safe**: every animation routes through duration tokens and honors Motion Mode (`setMotionMode`) with a reduced-motion floor\n- **Framework agnostic**: works anywhere HTML can be rendered\n- **Tree-shakeable**: import only the component entry points you register\n- **Comprehensive surface**: inputs, content, navigation, feedback, layout, and overlay primitives\n- **Native form integration**: form-associated controls submit values, validate supported required constraints, and reset with their form\n- **Typed framework declarations**: DOM, React, and Vue tags derive from the component property contracts\n\n</div>\n\n### Prerequisites\n\n- Browser runtime with Custom Elements support.\n- Import `@vielzeug/refine/fouc.css` and `@vielzeug/refine/tokens.css` before rendering components.\n- For SSR, render placeholders server-side and hydrate components only on the client.\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Animation & Motion](./animation.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ore](/ore/): Web component runtime that powers Refine\n- [Orbit](/orbit/): Floating UI positioning used in Refine's overlays\n- [Forge](/forge/): Form state management for use with Refine inputs\n- [Keymap](/keymap/): Keyboard shortcut manager that powers the command palette's global trigger\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
- "api": "---\ntitle: 'Refine: API Reference'\ndescription: Published component, stylesheet, framework type, and error entry points for @vielzeug/refine.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `@vielzeug/refine/<component>` | Register one custom element and export its public types | Sync | Importing a type alone does not register the element |\n| `@vielzeug/refine/tokens.css` | Load required theme tokens, animations, and cascade layers | CSS | Import once before components render |\n| `@vielzeug/refine/fouc.css` | Hide unregistered `ore-*` elements until upgrade | CSS | Load before first paint |\n| `@vielzeug/refine/styles/preflight.css` | Apply the optional browser reset | CSS | The reset affects global elements |\n| `@vielzeug/refine/frameworks/elements` | Register typed DOM tag mappings | Types only | Import for side effects in TypeScript |\n| `@vielzeug/refine/frameworks/react` | Register typed React JSX elements | Types only | Does not provide runtime wrappers |\n| `@vielzeug/refine/frameworks/vue` | Register typed Vue global components | Types only | Does not install a Vue plugin |\n| `@vielzeug/refine/theme` | `createThemeController()`: light/dark/system document theme | Sync | Does not persist the preference or set an accent color |\n| `@vielzeug/refine/blueprint` | `parseUiBlueprint()`: validate a model-authored UI document against a fixed registry | Sync | Pure contract only: it registers no components and renders nothing |\n| `@vielzeug/refine/blueprint/render` | `renderUiBlueprint()`: render a validated document with registered components | Sync | Registers its thirteen components on import; rejects before touching the container |\n| `eventFieldValue()` / `eventFieldChecked()` | Read a form control's value/checked from `event.currentTarget` | Sync | Exported from the package root; guards non-field targets |\n| `animateReflow()` | Token-driven FLIP reflow for keyed lists | Async (next frame) | Exported from the package root; elements need stable keys or it correctly does nothing |\n| `setMotionMode()` / `getMotionMode()` | Read/write the `data-motion` attribute on `<html>` | Sync | Exported from the package root; an unknown mode throws `RangeError` |\n| `RefineError` | Base class for package-defined public errors | Sync | Component configuration warnings do not throw this error |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/refine` | Export `registerIcons`, icon definition types, `RefineError`, `animateReflow`, the motion-mode functions, and the field readers without registering components |\n| `@vielzeug/refine/<component>` | Register one component and export its tag constant, props, events, and related types |\n| `@vielzeug/refine/theme` | `createThemeController()` and its theme types |\n| `@vielzeug/refine/blueprint` | On-demand UI document contract: `parseUiBlueprint()`, the node types, bounds, `UI_BLUEPRINT_REGISTRY`, and `UI_BLUEPRINT_JSON_SCHEMA` |\n| `@vielzeug/refine/blueprint/render` | `renderUiBlueprint()` and the thirteen component tags it registers |\n| `@vielzeug/refine/tokens.css` | Required global design contract |\n| `@vielzeug/refine/fouc.css` | Optional pre-upgrade visibility rule |\n| `@vielzeug/refine/styles/*` | Focused theme, animation, layer, and preflight stylesheets |\n| `@vielzeug/refine/frameworks/*` | TypeScript augmentations for DOM, React, and Vue |\n\n## Icon Registration and Adapters\n\n`registerIcons(icons: Readonly<Record<string, IconDefinition>>): void` is available from `@vielzeug/refine/icons`, `@vielzeug/refine`, and `@vielzeug/refine/icon`. The common `icons` entry and root remain free of component registration side effects.\n\n`IconDefinition`, `IconElement`, and `IconAttributes` are exported from all three entries. Shared definition and registration modules live in `src/icons/`, alongside adapters and internal SVG helpers/defaults. Definitions contain a numeric `viewBox` tuple, optional presentation attributes, and recursive SVG children; they do not require a provider identity. Invalid registrations throw the root-exported `RefineError` atomically.\n\n| Entry | Export | Purpose |\n| --- | --- | --- |\n| `@vielzeug/refine/icons` | `registerIcons`, `IconDefinition`, `IconElement`, `IconAttributes` | Common library-neutral registration and definition API |\n| `@vielzeug/refine/icons/lucide` | `lucideIcons`, `LucideIconData` | Pure adapter for selected structural Lucide SVG data |\n| `@vielzeug/refine/icons/lucide/all` | Side-effect registration | Explicitly register the full installed Lucide library |\n| `@vielzeug/refine/icons/heroicons` | `heroiconsIcons`, `IconifyIcon` | Convert a caller-named map of individual Heroicons Iconify data |\n| `@vielzeug/refine/icons/tabler` | `tablerIcons`, `IconifyIcon` | Convert a caller-named map of individual Tabler Iconify data |\n| `@vielzeug/refine/icons/mdi` | `mdiIcons`, `IconifyIcon` | Convert a caller-named map of individual MDI Iconify data |\n| `@vielzeug/refine/icons/iconify` | `iconifyIcon`, `iconifyIcons` | Convert individual Iconify data or selected/complete collections |\n| `@vielzeug/refine/icons/iconify` | `IconifyIcon`, `IconifyAlias`, `IconifyCollection`, `IconifyTransform` | Structural data types; no Iconify dependency required |\n\nLucide is an optional peer, not a mandatory dependency. Only `icons/lucide/all` requires an installed `lucide` package; consumers selecting that entry declare it themselves. The structural `icons/lucide` adapter accepts supplied data without importing Lucide.\n\nThe standalone IIFE exposes `registerIcons`, `lucideIcons`, `heroiconsIcons`, `tablerIcons`, `mdiIcons`, `iconifyIcon`, and `iconifyIcons` under `Refine.components.content`. Neither the IIFE nor component entries import Lucide. Iconify conversion drops provider `class` metadata, validates finite numeric offsets and positive finite dimensions before transforms, and retains the strict SVG allowlist. See the [icon reference](./components/icon.md) for presentation overrides, local SVG ID rewriting, supported families, and licenses.\n\nThe three family adapters share this signature:\n\n```ts\nheroiconsIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\ntablerIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\nmdiIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\n```\n\nEach accepts individual Iconify SVG data, preserves input keys without adding a namespace, and returns definitions without registering them. They require `DOMParser`, preserve each icon's geometry/presentation/transforms, and throw `RefineError` for invalid Iconify data. They do not accept React/Vue components, collection JSON, or raw SVG path strings. Use `iconifyIcons(collection, names?)` for collections and alias resolution.\n\n## Styles\n\n```ts\nimport '@vielzeug/refine/fouc.css';\nimport '@vielzeug/refine/tokens.css';\nimport '@vielzeug/refine/styles/preflight.css';\n```\n\n`tokens.css` is required. It defines tokens, animations, and cascade-layer order without resetting global elements. `preflight.css` is optional and includes FOUC suppression.\n\n| Import path | Purpose |\n| --- | --- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Tokens, animation helpers, and cascade layers |\n| `@vielzeug/refine/styles/theme.css` | Theme token declarations |\n| `@vielzeug/refine/styles/animation.css` | Animation helpers |\n| `@vielzeug/refine/styles/layers.css` | Cascade layer declarations |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser reset and FOUC suppression |\n\n## Components\n\nEach component uses one registration and type entry point:\n\n```ts\nimport '@vielzeug/refine/button';\nimport type { OreButtonProps } from '@vielzeug/refine/button';\n```\n\n| Area | Components |\n| --- | --- |\n| Content | `accordion`, `avatar`, `card`, `carousel`, `chat-message`, `chat-panel`, `code-window`, `copy-command`, `icon`, `keyboard-key`, `list`, `marquee`, `qr-code`, `rank-list`, `speech-player`, `speed-reading`, `stats`, `table`, `text` |\n| Feedback | `alert`, `badge`, `chip`, `cookie-banner`, `password-strength`, `progress`, `pull-to-refresh`, `skeleton`, `state`, `toast`, `typing-indicator` |\n| Inputs | `button`, `calendar`, `checkbox`, `color-picker`, `combobox`, `counter`, `datagrid`, `date-picker`, `file-input`, `input`, `message-composer`, `number-input`, `otp-input`, `qr-scanner`, `query-bar`, `radio`, `rating`, `select`, `slider`, `switch`, `textarea`, `time-picker` |\n| Layout | `box`, `dock`, `grid`, `navbar`, `separator`, `sidebar` |\n| Navigation | `breadcrumb`, `navigation-menu`, `pagination`, `stepper`, `tabs` |\n| Overlay | `command-palette`, `dialog`, `drawer`, `menu`, `popover`, `spotlight`, `tooltip`, `tour` |\n\nEach row lists the entry points that own a documentation page. Sub-element entries (`accordion-item`, `tab-item`, `avatar-group`, `list-item`, `step`, and the rest) are documented on their parent's page and share its category.\n\nEach component page lists its attributes, JavaScript properties, events, slots, parts, and CSS custom properties.\n\nThe [`query-bar`](./components/query-bar.md) entry also exports `QueryState`, `QueryTokenDef`, `QueryQuickFilter`, `QueryBarLabels`, and pure grammar helpers. Vocabulary entries default to single selection; `selection: 'multiple'` allows distinct values for the same key. `anyLabel` customizes the unrestricted single-selection choice, such as “Default order” for sorting. Options accept an optional `icon` (registered `ore-icon` name), displayed beside their labels in the drawer. `tokenValues(state, key)` reads all values, `removeToken(state, token)` removes an exact pair, and `clearTokens(state, key)` clears a group. Its unified field contains applied filters, with a compact secondary row for filter entry, quick toggles, and a `toolbar-end` slot for application content such as a result count. Drawer selection uses the same state and vocabulary as typed suggestions. Pending edits block incidental backdrop/swipe dismissal; explicit Cancel, Close, and Escape discard them. Search-hidden selections remain visible in a summary.\n\n## Framework Types\n\n```ts\nimport type {} from '@vielzeug/refine/frameworks/elements';\nimport type {} from '@vielzeug/refine/frameworks/react';\nimport type {} from '@vielzeug/refine/frameworks/vue';\n```\n\nThe declarations cover every supported tag and derive component properties from Refine's authoritative `HTMLElementTagNameMap`. They provide types only; component registration still uses component subpaths.\n\n## Events and Form Controls\n\nForm controls expose `.value` or `.checked` and dispatch standard `input` and `change` events. Read the property from `event.currentTarget`.\n\nFinite-choice properties use Ore's `prop.oneOf()` parsing: values outside a component's documented options resolve to\nthe declared default, or `undefined` when no default is declared.\n\nForm-associated controls participate in native form submission, validity checks, and reset. A named control's value\nis available through `new FormData(form)`, and supported `required` constraints affect `form.checkValidity()`,\n`form.reportValidity()`, and submit blocking. The `error` property renders and announces feedback; it does not by\nitself set native constraint validity.\n\nStateful overlays expose controlled `open`, optional `default-open`, and an `open-change` custom event with `{ open, reason }` detail. Component pages define additional detail fields and reasons.\n\n## Utilities\n\n### `createThemeController(options?)`\n\n```ts\nimport { createThemeController } from '@vielzeug/refine/theme';\n\nconst theme = createThemeController(); // starts in 'system' mode\ntheme.setPreference('dark');\ntheme.dispose();\n```\n\nOwns the light/dark/system theme for the document: a reactive `preference` signal, the effective `resolved` mode (`'dark' | 'light'` after resolving `system` against the OS), and the DOM application `styles/theme.css` expects: the `.dark` class plus the `color-scheme` property on the root element, so every `light-dark()` token resolves to the right branch. While in `system` mode the controller tracks the OS `prefers-color-scheme` via sentinel's `createMediaQuery`.\n\nThe controller does **not** persist the preference or expose an accent color: those are consumer concerns (wire `watch(controller.preference, save)` for persistence; set `--color-primary-hue` yourself). Options: `initial` (starting preference, default `'system'`), `root` (element carrying the classes, default `document.documentElement`), `target` (window whose `matchMedia` backs `system`). The handle is disposable: `dispose()`, `disposed`, `disposalSignal`, `[Symbol.dispose]`.\n\n### `eventFieldValue(event)` / `eventFieldChecked(event)`\n\n```ts\nimport { eventFieldChecked, eventFieldValue } from '@vielzeug/refine';\n\ninput.addEventListener('input', (event) => {\n const value = eventFieldValue(event); // string | undefined: never a blind cast\n});\n```\n\nGuarded readers for the standard `input`/`change` event pattern above: they read `value`/`checked` from `event.currentTarget` only when the target actually carries it, so a handler wired to the wrong element yields `undefined`/`false` instead of a crash or a lie. Exported from the package root, which registers no components.\n\n---\n\n### `animateReflow(host, signal, options)`\n\n```ts\nimport { animateReflow } from '@vielzeug/refine';\n\nconst reflow = animateReflow(list, abortController.signal, {\n elements: () => [...list.querySelectorAll('li')],\n getKey: (item) => item.dataset.id,\n});\nreflow.run(() => renderItems(nextItems));\n```\n\nToken-driven FLIP reflow for keyed lists: captures the current layout, runs `mutate`, then animates the tracked elements into their new positions on the next frame. The same helper the DataGrid, command palette, and toast stack use.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `host` | `HTMLElement` | Element whose computed style carries the timing tokens |\n| `signal` | `AbortSignal` | Aborting cancels in-flight animations |\n| `options.elements` | `() => HTMLElement[]` | Tracked elements, read live on both sides of the mutation |\n| `options.getKey` | `(el) => string \\| undefined` | Stable identity per element; all-or-nothing |\n| `options.durationToken` | `string?` | Duration token, default `'--duration-200'` |\n| `options.easingToken` | `string?` | Easing token, default `'--ease-move'` |\n| `options.stagger` | `number?` | Per-element delay in ms, default `0` |\n\n**Returns** a `Reflow` handle with one method: `run(mutate)` (sync; the animation is fire-and-forget). Timing is read from the tokens at animation time, so Motion Mode reduction skips the reflow instead of playing it instantly. Every path that cannot animate (no elements, missing keys, zeroed tokens, aborted signal, no Web Animations API) still runs `mutate` exactly once and never throws. See [Animation & Motion → List Reflow](./animation#list-reflow-animatereflow) for behavior details.\n\n---\n\n### `setMotionMode(mode)` / `getMotionMode()`\n\n```ts\nimport { getMotionMode, setMotionMode } from '@vielzeug/refine';\n\nsetMotionMode('reduced'); // returns the applied mode\n```\n\nRead/write the `data-motion` attribute on `<html>` (`'full' | 'reduced' | 'system'`; `'system'` removes the attribute). An unknown mode throws `RangeError`. The attribute drives the token layer: see [Animation & Motion → Motion Mode](./animation#motion-mode).\n\n## On-Demand UI Documents\n\n`@vielzeug/refine/blueprint` is the trusted boundary for UI that a model writes. A harness asks a model to describe a small UI in JSON, and this contract decides whether that JSON may be rendered. It is data only: no code, no HTML, no callbacks, no styles, no URLs. See [On-Demand UI Documents](./on-demand-ui) for the full guide.\n\n```ts\nimport { parseUiBlueprint, safeParseUiBlueprint } from '@vielzeug/refine/blueprint';\n\nconst result = safeParseUiBlueprint(JSON.parse(modelOutput)); // model output stays `unknown`\nif (!result.success) showError(result.issues); // safe to display: field paths, no secrets\nelse renderValidated(result.document);\n```\n\n| Export | Purpose |\n| --- | --- |\n| `UI_BLUEPRINT_VERSION` | The only document version accepted (`1`) |\n| `parseUiBlueprint(value)` | Validate `unknown`. Returns the document unchanged, or throws `UiBlueprintError`. Nothing is repaired or partially accepted |\n| `safeParseUiBlueprint(value)` | Non-throwing form: `{ success: true, document }` or `{ success: false, issues }` |\n| `UiBlueprintError` | `RefineError` subclass carrying `issues: readonly string[]`, each a field path such as `root.children[0].size` |\n| `UI_BLUEPRINT_COMPONENTS` | The thirteen accepted component names: `text`, `box`, `card`, `button`, `badge`, `separator`, `slider`, `switch`, `checkbox`, `number-input`, `alert`, `progress`, `icon` |\n| `UI_BLUEPRINT_REGISTRY` | The same registry as data: element tag, child policy, attribute names, slot-text fields, and accepted values per component |\n| `UI_BLUEPRINT_JSON_SCHEMA` | JSON Schema (draft 2020-12) for the contract, for harnesses with structured output |\n| `BlueprintDocument`, `BlueprintNode`, and the node interfaces | TypeScript types for the document shape. Every node accepts an optional bounded `id` through `BlueprintNodeMeta` |\n| `MAX_BLUEPRINT_NODES`, `MAX_BLUEPRINT_DEPTH`, `MAX_BLUEPRINT_CHILDREN`, `MAX_BLUEPRINT_TEXT_LENGTH`, `MAX_BLUEPRINT_LABEL_LENGTH`, `MAX_BLUEPRINT_NUMBER`, `MAX_BLUEPRINT_ID_LENGTH` | Document bounds, not user-configurable |\n\n`@vielzeug/refine/blueprint` imports nothing but `RefineError`: it is pure, DOM-free, and safe to run in a model harness, a worker, or CI. It registers no components.\n\n`UI_BLUEPRINT_JSON_SCHEMA` constrains generation but cannot express the tree-wide node and depth bounds, so runtime validation stays mandatory. Treat the schema as a generation aid, never as the trust boundary.\n\n### `renderUiBlueprint(container, value, options?)`\n\n```ts\nimport { renderUiBlueprint } from '@vielzeug/refine/blueprint/render';\n\ntry {\n const { element, nodes } = renderUiBlueprint(host, JSON.parse(modelOutput), { label: 'Generated summary' });\n nodes.get('rate')?.addEventListener('change', () => recalculate());\n} catch (error) {\n // The container is untouched: the previous preview is still on screen.\n}\n```\n\nValidate `value`, then render it into `container` using only registry components. Importing the entry registers those thirteen components, so no separate component imports are needed.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `container` | `HTMLElement` | Element that receives the rendered tree |\n| `value` | `unknown` | Untrusted model output. Validated before anything is rendered |\n| `options.label` | `string?` | Accessible name for a `role=\"group\"` wrapper. Host-owned: a document cannot set it |\n| `options.replace` | `boolean?` | Clear the container first. Default `true`; `false` appends |\n\n**Returns** a `UiBlueprintRenderResult`: `{ element, nodes }`, the rendered root and a `ReadonlyMap<string, HTMLElement>` keyed by each node's optional `id` so the host can wire behaviour to addressable controls. `id` is host-facing metadata: the renderer never writes it to the DOM. Validation runs first, so a rejected document throws `UiBlueprintError` before the container is read or written: an invalid response can never partially render or erase a valid preview. Text becomes a text node, never markup.\n\n### `createUiBlueprintElement(node, owner?, nodes?)`\n\n```ts\nimport { createUiBlueprintElement } from '@vielzeug/refine/blueprint/render';\n\nconst element = createUiBlueprintElement(document.root, 'root', nodeMap);\n```\n\nBuild one element for an already-validated node. Pass a `Map` as `nodes` to collect every id-tagged element it creates, including nested ones; `owner` labels the node's position in rejection messages. This is the component registry: adding a component means adding a rule in the contract and a case here, and nothing else. It throws `TypeError` for an unregistered node type, so a hand-built node cannot bypass validation either. Embedders that place nodes themselves use this; `renderUiBlueprint()` is the normal entry point.\n\n## Types\n\nComponent props follow the `Ore<Component>Props` naming pattern. Event maps follow `Ore<Component>Events`. Import both from the component subpath that owns them.\n\n`RefineElementMap`, `RefineReactIntrinsicElements`, and `RefineVueGlobalComponents` are exported from their respective `frameworks/*` type entry points.\n\nThe package root also exports `Reflow` (the `run()` handle), `ReflowOptions` (its options object), and `MotionMode` (`'full' | 'reduced' | 'system'`).\n\n## Errors\n\n### `RefineError`\n\n```ts\nclass RefineError extends Error {\n constructor(message: string, opts?: ErrorOptions);\n}\n```\n\nBase class for public Refine errors. It preserves `cause` through `ErrorOptions`.\n\n### `UiBlueprintError`\n\n```ts\nclass UiBlueprintError extends RefineError {\n readonly issues: readonly string[];\n}\n```\n\nThrown by `parseUiBlueprint()` and `renderUiBlueprint()` for a rejected on-demand UI document. Every rejection is collected first, so `issues` lists all of them at once, each as a field path plus a plain reason (`root.children[0].size: expected one of sm, md, lg, received string.`). The messages name fields and allowed values, report a wrong-typed value only as its *type*, and echo an accepted-type value only as a truncated snippet, so they are safe to display to a user. See [On-Demand UI Documents](./on-demand-ui).\n",
4
+ "index": "---\ntitle: 'Refine: Web component library'\ndescription: Accessible, themeable web components built with Ore for framework and vanilla DOM apps.\npackage: refine\ncategory: ui-components\nkeywords: [web-components, accessible, themeable, ui, components, design-system]\nrelated: [ore, orbit, forge, keymap]\nexports:\n [\n ore-accordion,\n ore-accordion-item,\n ore-alert,\n ore-avatar,\n ore-avatar-group,\n ore-badge,\n ore-box,\n ore-breadcrumb,\n ore-breadcrumb-item,\n ore-button,\n ore-button-group,\n ore-calendar,\n ore-card,\n ore-carousel,\n ore-carousel-slide,\n ore-chat-message,\n ore-checkbox,\n ore-checkbox-group,\n ore-chip,\n ore-code-window,\n ore-color-picker,\n ore-column,\n ore-combobox,\n ore-combobox-option,\n ore-command-palette,\n ore-command-palette-item,\n ore-cookie-banner,\n ore-copy-command,\n ore-counter,\n ore-datagrid,\n ore-date-picker,\n ore-dialog,\n ore-dock,\n ore-drawer,\n ore-file-input,\n ore-grid,\n ore-grid-item,\n ore-icon,\n ore-input,\n ore-keyboard-key,\n ore-keyboard-shortcut,\n ore-list,\n ore-list-item,\n ore-marquee,\n ore-menu,\n ore-menu-item,\n ore-menu-separator,\n ore-message-composer,\n ore-navbar,\n ore-navbar-item,\n ore-navigation-menu,\n ore-navigation-menu-item,\n ore-navigation-menu-panel,\n ore-number-input,\n ore-otp-input,\n ore-pagination,\n ore-password-strength,\n ore-popover,\n ore-progress,\n ore-pull-to-refresh,\n ore-qr-code,\n ore-qr-scanner,\n ore-query-bar,\n ore-radio,\n ore-radio-group,\n ore-rating,\n ore-select,\n ore-separator,\n ore-sidebar,\n ore-sidebar-group,\n ore-sidebar-item,\n ore-skeleton,\n ore-slider,\n ore-speech-player,\n ore-speed-reading,\n ore-spotlight,\n ore-state,\n ore-stats,\n ore-step,\n ore-stepper,\n ore-switch,\n ore-tab-item,\n ore-tab-panel,\n ore-table,\n ore-tabs,\n ore-td,\n ore-text,\n ore-th,\n ore-tr,\n ore-textarea,\n ore-time-picker,\n ore-toast,\n ore-tooltip,\n ore-tour,\n ore-typing-indicator,\n ]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"refine\" />\n\n## Why Refine?\n\nEvery project needs UI primitives. Refine provides accessible web components that work natively anywhere HTML is rendered, no framework required.\n\n```html\n<!-- Before: roll your own button with ARIA -->\n<button class=\"btn btn-primary\" role=\"button\" aria-pressed=\"false\" tabindex=\"0\">\n <span class=\"btn-spinner\" aria-hidden=\"true\"></span>\n Save\n</button>\n\n<!-- After: Refine -->\n<ore-button variant=\"solid\" color=\"primary\" loading>Save</ore-button>\n```\n\n| Feature | Refine | Shoelace | Material Web |\n| -------------------- | ------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------- |\n| Bundle size | <PackageInfo package=\"refine\" type=\"size\" /> | ~145 kB | ~200 kB |\n| Built with | Ore | Lit | Lit |\n| Runtime dependencies | Vielzeug UI primitives; Lucide only for the opt-in full-library registrar | Lit + utilities | Lit + Material foundations |\n| Accessible | WCAG AA | WCAG AA | WCAG AA |\n| Framework agnostic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Refine when** you want accessible web components that match the Vielzeug design system without a heavy framework dependency.\n\n**Consider Shoelace or Material Web** if your team is already standardized on those ecosystems and you need their established component catalogs.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/refine\n```\n\n```sh [npm]\nnpm install @vielzeug/refine\n```\n\n```sh [yarn]\nyarn add @vielzeug/refine\n```\n\n:::\n\n## Quick Start\n\n```ts\n// 1. Import global styles once\nimport '@vielzeug/refine/fouc.css'; // Hide unupgraded custom elements until first paint\nimport '@vielzeug/refine/tokens.css'; // Tokens, animations, cascade layers\n\n// 2. Register only the elements you need\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport '@vielzeug/refine/card';\n```\n\n```html\n<ore-button variant=\"solid\" color=\"primary\">Save</ore-button>\n<ore-input label=\"Email\" type=\"email\" required></ore-input>\n<ore-card padding=\"lg\">\n <span slot=\"header\">Account</span>\n <p>Card content goes here.</p>\n</ore-card>\n```\n\n```ts\n```\n\n### CDN / Vanilla HTML\n\nUse the self-contained IIFE bundle to load Refine directly from a CDN in any HTML page: no build step required:\n\n```html\n<!-- 1. Styles -->\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/fouc.css\" />\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/tokens.css\" />\n\n<!-- 2. All components (IIFE: registers global Refine namespace) -->\n<script src=\"https://unpkg.com/@vielzeug/refine/dist/refine.iife.js\"></script>\n```\n\nFor bundler-based projects that still want a CDN URL, use the ESM bundle via an import map:\n\n```html\n<script type=\"importmap\">\n {\n \"imports\": {\n \"@vielzeug/refine\": \"https://esm.sh/@vielzeug/refine\",\n \"@vielzeug/refine/button\": \"https://esm.sh/@vielzeug/refine/button\",\n \"@vielzeug/refine/input\": \"https://esm.sh/@vielzeug/refine/input\"\n }\n }\n</script>\n\n<script type=\"module\">\n import '@vielzeug/refine/button';\n import '@vielzeug/refine/input';\n</script>\n```\n\n### Package Entry Points\n\n| Import | Purpose |\n| --------------------------------------- | ---------------------------------------------------------- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Global design tokens and cascade layers |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser-default reset (includes FOUC suppression) |\n\nComponent registration happens through side-effect imports such as `@vielzeug/refine/button` and `@vielzeug/refine/dialog`.\n\n### Components\n\nEach entry below is a component with its own documentation page and registration subpath. Sub-element tags (`ore-accordion-item`, `ore-tab-item`, `ore-avatar-group`, and the rest) are documented on their parent's page and registered with it.\n\n**Content:** `ore-accordion`, `ore-avatar`, `ore-card`, `ore-carousel`, `ore-chat-message`, `ore-chat-panel`, `ore-code-window`, `ore-copy-command`, `ore-icon`, `ore-keyboard-key`, `ore-list`, `ore-marquee`, `ore-qr-code`, `ore-rank-list`, `ore-speech-player`, `ore-speed-reading`, `ore-stats`, `ore-table`, `ore-text`\n\n**Feedback:** `ore-alert`, `ore-badge`, `ore-chip`, `ore-cookie-banner`, `ore-password-strength`, `ore-progress`, `ore-pull-to-refresh`, `ore-skeleton`, `ore-state`, `ore-toast`, `ore-typing-indicator`\n\n**Inputs:** `ore-button`, `ore-calendar`, `ore-checkbox`, `ore-color-picker`, `ore-combobox`, `ore-counter`, `ore-datagrid`, `ore-date-picker`, `ore-file-input`, `ore-input`, `ore-message-composer`, `ore-number-input`, `ore-otp-input`, `ore-qr-scanner`, `ore-query-bar`, `ore-radio`, `ore-rating`, `ore-select`, `ore-slider`, `ore-switch`, `ore-textarea`, `ore-time-picker`\n\n**Layout:** `ore-box`, `ore-dock`, `ore-grid`, `ore-navbar`, `ore-separator`, `ore-sidebar`\n\n**Navigation:** `ore-breadcrumb`, `ore-navigation-menu`, `ore-pagination`, `ore-stepper`, `ore-tabs`\n\n**Overlay:** `ore-command-palette`, `ore-dialog`, `ore-drawer`, `ore-menu`, `ore-popover`, `ore-spotlight`, `ore-tooltip`, `ore-tour`\n\n## Component portfolio\n\nRefine keeps one public package and classifies components by composition level rather than stability. All listed components remain supported and tested.\n\n- **Foundations**: buttons, inputs, selection controls, accordions, dialogs, menus, tooltips, layout primitives, and feedback indicators. Start here when building an application-specific interface.\n- **Composed patterns**: data grids, command palettes, navigation menus, steppers, calendars, date/time pickers, file inputs, and message composers. These package recurring accessibility and interaction behavior that is costly to rebuild correctly.\n- **Presentation patterns**: cards, chat messages, code windows, stats, marquees, avatars, badges, and skeletons. Use these when their product vocabulary matches the application; prefer foundations when it does not.\n\nClassification guides discovery and bundle selection. It is not a reason to remove working components or move them into unstable packages; per-component subpath imports remain the tree-shaking boundary.\n\n## Features\n\n<div class=\"features-grid\">\n\n- **Accessible**: keyboard navigation, ARIA wiring, and focus management across interactive components\n- **Themeable**: global tokens plus component-level CSS custom properties\n- **Motion-safe**: every animation routes through duration tokens and honors Motion Mode (`setMotionMode`) with a reduced-motion floor\n- **Framework agnostic**: works anywhere HTML can be rendered\n- **Tree-shakeable**: import only the component entry points you register\n- **Comprehensive surface**: inputs, content, navigation, feedback, layout, and overlay primitives\n- **Native form integration**: form-associated controls submit values, validate supported required constraints, and reset with their form\n- **Typed framework declarations**: DOM, React, and Vue tags derive from the component property contracts\n\n</div>\n\n### Prerequisites\n\n- Browser runtime with Custom Elements support.\n- Import `@vielzeug/refine/fouc.css` and `@vielzeug/refine/tokens.css` before rendering components.\n- For SSR, render placeholders server-side and hydrate components only on the client.\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Animation & Motion](./animation.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ore](/ore/): Web component runtime that powers Refine\n- [Orbit](/orbit/): Floating UI positioning used in Refine's overlays\n- [Forge](/forge/): Form state management for use with Refine inputs\n- [Keymap](/keymap/): Keyboard shortcut manager that powers the command palette's global trigger\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: 'Refine: API Reference'\ndescription: Published component, stylesheet, framework type, and error entry points for @vielzeug/refine.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ | -------------------------------------------------------------------------------------- |\n| `@vielzeug/refine/<component>` | Register one custom element and export its public types | Sync | Importing a type alone does not register the element |\n| `@vielzeug/refine/tokens.css` | Load required theme tokens, animations, and cascade layers | CSS | Import once before components render |\n| `@vielzeug/refine/fouc.css` | Hide unregistered `ore-*` elements until upgrade | CSS | Load before first paint |\n| `@vielzeug/refine/styles/preflight.css` | Apply the optional browser reset | CSS | The reset affects global elements |\n| `@vielzeug/refine/frameworks/elements` | Register typed DOM tag mappings | Types only | Import for side effects in TypeScript |\n| `@vielzeug/refine/frameworks/react` | Register typed React JSX elements | Types only | Does not provide runtime wrappers |\n| `@vielzeug/refine/frameworks/vue` | Register typed Vue global components | Types only | Does not install a Vue plugin |\n| `@vielzeug/refine/theme` | `createThemeController()`: light/dark/system document theme | Sync | Does not persist the preference or set an accent color |\n| `@vielzeug/refine/blueprint` | `parseUiBlueprint()`: validate a model-authored UI document against a fixed registry | Sync | Pure contract only: it registers no components and renders nothing |\n| `@vielzeug/refine/blueprint/render` | `renderUiBlueprint()`: render a validated document with registered components | Sync | Registers its thirteen components on import; rejects before touching the container |\n| `eventFieldValue()` / `eventFieldChecked()` | Read a form control's value/checked from `event.currentTarget` | Sync | Exported from the package root; guards non-field targets |\n| `animateReflow()` | Token-driven FLIP reflow for keyed lists | Async (next frame) | Exported from the package root; elements need stable keys or it correctly does nothing |\n| `setMotionMode()` / `getMotionMode()` | Read/write the `data-motion` attribute on `<html>` | Sync | Exported from the package root; an unknown mode throws `RangeError` |\n| `RefineError` | Base class for package-defined public errors | Sync | Component configuration warnings do not throw this error |\n\n## Package Entry Point\n\n| Import | Purpose |\n| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `@vielzeug/refine` | Export `registerIcons`, icon definition types, `RefineError`, `animateReflow`, the motion-mode functions, and the field readers without registering components |\n| `@vielzeug/refine/<component>` | Register one component and export its tag constant, props, events, and related types |\n| `@vielzeug/refine/theme` | `createThemeController()` and its theme types |\n| `@vielzeug/refine/blueprint` | On-demand UI document contract: `parseUiBlueprint()`, the node types, bounds, `UI_BLUEPRINT_REGISTRY`, and `UI_BLUEPRINT_JSON_SCHEMA` |\n| `@vielzeug/refine/blueprint/render` | `renderUiBlueprint()` and the thirteen component tags it registers |\n| `@vielzeug/refine/tokens.css` | Required global design contract |\n| `@vielzeug/refine/fouc.css` | Optional pre-upgrade visibility rule |\n| `@vielzeug/refine/styles/*` | Focused theme, animation, layer, and preflight stylesheets |\n| `@vielzeug/refine/frameworks/*` | TypeScript augmentations for DOM, React, and Vue |\n\n## Icon Registration and Adapters\n\n`registerIcons(icons: Readonly<Record<string, IconDefinition>>): void` is available from `@vielzeug/refine/icons`, `@vielzeug/refine`, and `@vielzeug/refine/icon`. The common `icons` entry and root remain free of component registration side effects.\n\n`IconDefinition`, `IconElement`, and `IconAttributes` are exported from all three entries. Shared definition and registration modules live in `src/icons/`, alongside adapters and internal SVG helpers/defaults. Definitions contain a numeric `viewBox` tuple, optional presentation attributes, and recursive SVG children; they do not require a provider identity. Invalid registrations throw the root-exported `RefineError` atomically.\n\n| Entry | Export | Purpose |\n| ----------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------- |\n| `@vielzeug/refine/icons` | `registerIcons`, `IconDefinition`, `IconElement`, `IconAttributes` | Common library-neutral registration and definition API |\n| `@vielzeug/refine/icons/lucide` | `lucideIcons`, `LucideIconData` | Pure adapter for selected structural Lucide SVG data |\n| `@vielzeug/refine/icons/lucide/all` | Side-effect registration | Explicitly register the full installed Lucide library |\n| `@vielzeug/refine/icons/heroicons` | `heroiconsIcons`, `IconifyIcon` | Convert a caller-named map of individual Heroicons Iconify data |\n| `@vielzeug/refine/icons/tabler` | `tablerIcons`, `IconifyIcon` | Convert a caller-named map of individual Tabler Iconify data |\n| `@vielzeug/refine/icons/mdi` | `mdiIcons`, `IconifyIcon` | Convert a caller-named map of individual MDI Iconify data |\n| `@vielzeug/refine/icons/iconify` | `iconifyIcon`, `iconifyIcons` | Convert individual Iconify data or selected/complete collections |\n| `@vielzeug/refine/icons/iconify` | `IconifyIcon`, `IconifyAlias`, `IconifyCollection`, `IconifyTransform` | Structural data types; no Iconify dependency required |\n\nLucide is an optional peer, not a mandatory dependency. Only `icons/lucide/all` requires an installed `lucide` package; consumers selecting that entry declare it themselves. The structural `icons/lucide` adapter accepts supplied data without importing Lucide.\n\nThe standalone IIFE exposes `registerIcons`, `lucideIcons`, `heroiconsIcons`, `tablerIcons`, `mdiIcons`, `iconifyIcon`, and `iconifyIcons` under `Refine.components.content`. Neither the IIFE nor component entries import Lucide. Iconify conversion drops provider `class` metadata, validates finite numeric offsets and positive finite dimensions before transforms, and retains the strict SVG allowlist. See the [icon reference](./components/icon.md) for presentation overrides, local SVG ID rewriting, supported families, and licenses.\n\nThe three family adapters share this signature:\n\n```ts\nheroiconsIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\ntablerIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\nmdiIcons(icons: Readonly<Record<string, IconifyIcon>>): Record<string, IconDefinition>;\n```\n\nEach accepts individual Iconify SVG data, preserves input keys without adding a namespace, and returns definitions without registering them. They require `DOMParser`, preserve each icon's geometry/presentation/transforms, and throw `RefineError` for invalid Iconify data. They do not accept React/Vue components, collection JSON, or raw SVG path strings. Use `iconifyIcons(collection, names?)` for collections and alias resolution.\n\n## Styles\n\n```ts\nimport '@vielzeug/refine/fouc.css';\nimport '@vielzeug/refine/tokens.css';\nimport '@vielzeug/refine/styles/preflight.css';\n```\n\n`tokens.css` is required. It defines tokens, animations, and cascade-layer order without resetting global elements. `preflight.css` is optional and includes FOUC suppression.\n\n| Import path | Purpose |\n| --------------------------------------- | ----------------------------------------------- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Tokens, animation helpers, and cascade layers |\n| `@vielzeug/refine/styles/theme.css` | Theme token declarations |\n| `@vielzeug/refine/styles/animation.css` | Animation helpers |\n| `@vielzeug/refine/styles/layers.css` | Cascade layer declarations |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser reset and FOUC suppression |\n\n## Components\n\nEach component uses one registration and type entry point:\n\n```ts\nimport '@vielzeug/refine/button';\nimport type { OreButtonProps } from '@vielzeug/refine/button';\n```\n\n| Area | Components |\n| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Content | `accordion`, `avatar`, `card`, `carousel`, `chat-message`, `chat-panel`, `code-window`, `copy-command`, `icon`, `keyboard-key`, `list`, `marquee`, `qr-code`, `rank-list`, `speech-player`, `speed-reading`, `stats`, `table`, `text` |\n| Feedback | `alert`, `badge`, `chip`, `cookie-banner`, `password-strength`, `progress`, `pull-to-refresh`, `skeleton`, `state`, `toast`, `typing-indicator` |\n| Inputs | `button`, `calendar`, `checkbox`, `color-picker`, `combobox`, `counter`, `datagrid`, `date-picker`, `file-input`, `input`, `message-composer`, `number-input`, `otp-input`, `qr-scanner`, `query-bar`, `radio`, `rating`, `select`, `slider`, `switch`, `textarea`, `time-picker` |\n| Layout | `box`, `dock`, `grid`, `navbar`, `separator`, `sidebar` |\n| Navigation | `breadcrumb`, `navigation-menu`, `pagination`, `stepper`, `tabs` |\n| Overlay | `command-palette`, `dialog`, `drawer`, `menu`, `popover`, `spotlight`, `tooltip`, `tour` |\n\nEach row lists the entry points that own a documentation page. Sub-element entries (`accordion-item`, `tab-item`, `avatar-group`, `list-item`, `step`, and the rest) are documented on their parent's page and share its category.\n\nEach component page lists its attributes, JavaScript properties, events, slots, parts, and CSS custom properties.\n\nThe [`query-bar`](./components/query-bar.md) entry also exports `QueryState`, `QueryTokenDef`, `QueryQuickFilter`, `QueryBarLabels`, and pure grammar helpers. Vocabulary entries default to single selection; `selection: 'multiple'` allows distinct values for the same key. `anyLabel` customizes the unrestricted single-selection choice, such as “Default order” for sorting. Options accept an optional `icon` (registered `ore-icon` name), displayed beside their labels in the drawer. `tokenValues(state, key)` reads all values, `removeToken(state, token)` removes an exact pair, and `clearTokens(state, key)` clears a group. Its unified field contains applied filters, with a compact secondary row for filter entry, quick toggles, and a `toolbar-end` slot for application content such as a result count. Drawer selection uses the same state and vocabulary as typed suggestions. Pending edits block incidental backdrop/swipe dismissal; explicit Cancel, Close, and Escape discard them. Search-hidden selections remain visible in a summary.\n\n## Framework Types\n\n```ts\nimport type {} from '@vielzeug/refine/frameworks/elements';\nimport type {} from '@vielzeug/refine/frameworks/react';\nimport type {} from '@vielzeug/refine/frameworks/vue';\n```\n\nThe declarations cover every supported tag and derive component properties from Refine's authoritative `HTMLElementTagNameMap`. They provide types only; component registration still uses component subpaths.\n\n## Events and Form Controls\n\nForm controls expose `.value` or `.checked` and dispatch standard `input` and `change` events. Read the property from `event.currentTarget`.\n\nFinite-choice properties use Ore's `prop.oneOf()` parsing: values outside a component's documented options resolve to\nthe declared default, or `undefined` when no default is declared.\n\nForm-associated controls participate in native form submission, validity checks, and reset. A named control's value\nis available through `new FormData(form)`, and supported `required` constraints affect `form.checkValidity()`,\n`form.reportValidity()`, and submit blocking. The `error` property renders and announces feedback; it does not by\nitself set native constraint validity.\n\nStateful overlays expose controlled `open`, optional `default-open`, and an `open-change` custom event with `{ open, reason }` detail. Component pages define additional detail fields and reasons.\n\n## Utilities\n\n### `createThemeController(options?)`\n\n```ts\nimport { createThemeController } from '@vielzeug/refine/theme';\n\nconst theme = createThemeController(); // starts in 'system' mode\ntheme.setPreference('dark');\ntheme.dispose();\n```\n\nOwns the light/dark/system theme for the document: a reactive `preference` signal, the effective `resolved` mode (`'dark' | 'light'` after resolving `system` against the OS), and the DOM application `styles/theme.css` expects: the `.dark` class plus the `color-scheme` property on the root element, so every `light-dark()` token resolves to the right branch. While in `system` mode the controller tracks the OS `prefers-color-scheme` via sentinel's `createMediaQuery`.\n\nThe controller does **not** persist the preference or expose an accent color: those are consumer concerns (wire `watch(controller.preference, save)` for persistence; set `--color-primary-hue` yourself). Options: `initial` (starting preference, default `'system'`), `root` (element carrying the classes, default `document.documentElement`), `target` (window whose `matchMedia` backs `system`). The handle is disposable: `dispose()`, `disposed`, `disposalSignal`, `[Symbol.dispose]`.\n\n### `eventFieldValue(event)` / `eventFieldChecked(event)`\n\n```ts\nimport { eventFieldChecked, eventFieldValue } from '@vielzeug/refine';\n\ninput.addEventListener('input', (event) => {\n const value = eventFieldValue(event); // string | undefined: never a blind cast\n});\n```\n\nGuarded readers for the standard `input`/`change` event pattern above: they read `value`/`checked` from `event.currentTarget` only when the target actually carries it, so a handler wired to the wrong element yields `undefined`/`false` instead of a crash or a lie. Exported from the package root, which registers no components.\n\n---\n\n### `animateReflow(host, signal, options)`\n\n```ts\nimport { animateReflow } from '@vielzeug/refine';\n\nconst reflow = animateReflow(list, abortController.signal, {\n elements: () => [...list.querySelectorAll('li')],\n getKey: (item) => item.dataset.id,\n});\nreflow.run(() => renderItems(nextItems));\n```\n\nToken-driven FLIP reflow for keyed lists: captures the current layout, runs `mutate`, then animates the tracked elements into their new positions on the next frame. The same helper the DataGrid, command palette, and toast stack use.\n\n| Parameter | Type | Description |\n| ----------------------- | ----------------------------- | --------------------------------------------------------- |\n| `host` | `HTMLElement` | Element whose computed style carries the timing tokens |\n| `signal` | `AbortSignal` | Aborting cancels in-flight animations |\n| `options.elements` | `() => HTMLElement[]` | Tracked elements, read live on both sides of the mutation |\n| `options.getKey` | `(el) => string \\| undefined` | Stable identity per element; all-or-nothing |\n| `options.durationToken` | `string?` | Duration token, default `'--duration-200'` |\n| `options.easingToken` | `string?` | Easing token, default `'--ease-move'` |\n| `options.stagger` | `number?` | Per-element delay in ms, default `0` |\n\n**Returns** a `Reflow` handle with one method: `run(mutate)` (sync; the animation is fire-and-forget). Timing is read from the tokens at animation time, so Motion Mode reduction skips the reflow instead of playing it instantly. Every path that cannot animate (no elements, missing keys, zeroed tokens, aborted signal, no Web Animations API) still runs `mutate` exactly once and never throws. See [Animation & Motion → List Reflow](./animation#list-reflow-animatereflow) for behavior details.\n\n---\n\n### `setMotionMode(mode)` / `getMotionMode()`\n\n```ts\nimport { getMotionMode, setMotionMode } from '@vielzeug/refine';\n\nsetMotionMode('reduced'); // returns the applied mode\n```\n\nRead/write the `data-motion` attribute on `<html>` (`'full' | 'reduced' | 'system'`; `'system'` removes the attribute). An unknown mode throws `RangeError`. The attribute drives the token layer: see [Animation & Motion → Motion Mode](./animation#motion-mode).\n\n## On-Demand UI Documents\n\n`@vielzeug/refine/blueprint` is the trusted boundary for UI that a model writes. A harness asks a model to describe a small UI in JSON, and this contract decides whether that JSON may be rendered. It is data only: no code, no HTML, no callbacks, no styles, no URLs. See [On-Demand UI Documents](./on-demand-ui) for the full guide.\n\n```ts\nimport { parseUiBlueprint, safeParseUiBlueprint } from '@vielzeug/refine/blueprint';\n\nconst result = safeParseUiBlueprint(JSON.parse(modelOutput)); // model output stays `unknown`\nif (!result.success)\n showError(result.issues); // safe to display: field paths, no secrets\nelse renderValidated(result.document);\n```\n\n| Export | Purpose |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `UI_BLUEPRINT_VERSION` | The only document version accepted (`1`) |\n| `parseUiBlueprint(value)` | Validate `unknown`. Returns the document unchanged, or throws `UiBlueprintError`. Nothing is repaired or partially accepted |\n| `safeParseUiBlueprint(value)` | Non-throwing form: `{ success: true, document }` or `{ success: false, issues }` |\n| `UiBlueprintError` | `RefineError` subclass carrying `issues: readonly string[]`, each a field path such as `root.children[0].size` |\n| `UI_BLUEPRINT_COMPONENTS` | The thirteen accepted component names: `text`, `box`, `card`, `button`, `badge`, `separator`, `slider`, `switch`, `checkbox`, `number-input`, `alert`, `progress`, `icon` |\n| `UI_BLUEPRINT_REGISTRY` | The same registry as data: element tag, child policy, attribute names, slot-text fields, and accepted values per component |\n| `UI_BLUEPRINT_JSON_SCHEMA` | JSON Schema (draft 2020-12) for the contract, for harnesses with structured output |\n| `BlueprintDocument`, `BlueprintNode`, and the node interfaces | TypeScript types for the document shape. Every node accepts an optional bounded `id` through `BlueprintNodeMeta` |\n| `MAX_BLUEPRINT_NODES`, `MAX_BLUEPRINT_DEPTH`, `MAX_BLUEPRINT_CHILDREN`, `MAX_BLUEPRINT_TEXT_LENGTH`, `MAX_BLUEPRINT_LABEL_LENGTH`, `MAX_BLUEPRINT_NUMBER`, `MAX_BLUEPRINT_ID_LENGTH` | Document bounds, not user-configurable |\n\n`@vielzeug/refine/blueprint` imports nothing but `RefineError`: it is pure, DOM-free, and safe to run in a model harness, a worker, or CI. It registers no components.\n\n`UI_BLUEPRINT_JSON_SCHEMA` constrains generation but cannot express the tree-wide node and depth bounds, so runtime validation stays mandatory. Treat the schema as a generation aid, never as the trust boundary.\n\n### `renderUiBlueprint(container, value, options?)`\n\n```ts\nimport { renderUiBlueprint } from '@vielzeug/refine/blueprint/render';\n\ntry {\n const { element, nodes } = renderUiBlueprint(host, JSON.parse(modelOutput), { label: 'Generated summary' });\n nodes.get('rate')?.addEventListener('change', () => recalculate());\n} catch (error) {\n // The container is untouched: the previous preview is still on screen.\n}\n```\n\nValidate `value`, then render it into `container` using only registry components. Importing the entry registers those thirteen components, so no separate component imports are needed.\n\n| Parameter | Type | Description |\n| ----------------- | ------------- | ---------------------------------------------------------------------------------- |\n| `container` | `HTMLElement` | Element that receives the rendered tree |\n| `value` | `unknown` | Untrusted model output. Validated before anything is rendered |\n| `options.label` | `string?` | Accessible name for a `role=\"group\"` wrapper. Host-owned: a document cannot set it |\n| `options.replace` | `boolean?` | Clear the container first. Default `true`; `false` appends |\n\n**Returns** a `UiBlueprintRenderResult`: `{ element, nodes }`, the rendered root and a `ReadonlyMap<string, HTMLElement>` keyed by each node's optional `id` so the host can wire behaviour to addressable controls. `id` is host-facing metadata: the renderer never writes it to the DOM. Validation runs first, so a rejected document throws `UiBlueprintError` before the container is read or written: an invalid response can never partially render or erase a valid preview. Text becomes a text node, never markup.\n\n### `createUiBlueprintElement(node, owner?, nodes?)`\n\n```ts\nimport { createUiBlueprintElement } from '@vielzeug/refine/blueprint/render';\n\nconst element = createUiBlueprintElement(document.root, 'root', nodeMap);\n```\n\nBuild one element for an already-validated node. Pass a `Map` as `nodes` to collect every id-tagged element it creates, including nested ones; `owner` labels the node's position in rejection messages. This is the component registry: adding a component means adding a rule in the contract and a case here, and nothing else. It throws `TypeError` for an unregistered node type, so a hand-built node cannot bypass validation either. Embedders that place nodes themselves use this; `renderUiBlueprint()` is the normal entry point.\n\n## Types\n\nComponent props follow the `Ore<Component>Props` naming pattern. Event maps follow `Ore<Component>Events`. Import both from the component subpath that owns them.\n\n`RefineElementMap`, `RefineReactIntrinsicElements`, and `RefineVueGlobalComponents` are exported from their respective `frameworks/*` type entry points.\n\nThe package root also exports `Reflow` (the `run()` handle), `ReflowOptions` (its options object), and `MotionMode` (`'full' | 'reduced' | 'system'`).\n\n## Errors\n\n### `RefineError`\n\n```ts\nclass RefineError extends Error {\n constructor(message: string, opts?: ErrorOptions);\n}\n```\n\nBase class for public Refine errors. It preserves `cause` through `ErrorOptions`.\n\n### `UiBlueprintError`\n\n```ts\nclass UiBlueprintError extends RefineError {\n readonly issues: readonly string[];\n}\n```\n\nThrown by `parseUiBlueprint()` and `renderUiBlueprint()` for a rejected on-demand UI document. Every rejection is collected first, so `issues` lists all of them at once, each as a field path plus a plain reason (`root.children[0].size: expected one of sm, md, lg, received string.`). The messages name fields and allowed values, report a wrong-typed value only as its _type_, and echo an accepted-type value only as a truncated snippet, so they are safe to display to a user. See [On-Demand UI Documents](./on-demand-ui).\n",
6
6
  "usage": "---\ntitle: 'Refine: Usage Guide'\ndescription: Register Refine components, configure properties, handle events, compose slots, and integrate frameworks.\n---\n\n[[toc]]\n\n## Basic Usage\n\nLoad the global design contract once, then register only the components used by the page.\n\n```ts\nimport '@vielzeug/refine/tokens.css';\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport '@vielzeug/refine/dialog';\n\nconst input = document.querySelector('ore-input');\nconst save = document.querySelector('ore-button');\n\nif (!(input instanceof HTMLElement) || !(save instanceof HTMLElement)) {\n throw new Error('Expected Refine controls');\n}\n\nsave.addEventListener('click', () => {\n console.log((input as HTMLElement & { value: string }).value);\n});\n```\n\nAdd the browser reset only when the application owns global element styling:\n\n```ts\nimport '@vielzeug/refine/styles/preflight.css';\n```\n\n## Choose an Icon Library\n\nRefine includes only its internal component icons. Register application icons before mounting them: use `registerIcons` and neutral `IconDefinition` data from `@vielzeug/refine/icons`, the pure `@vielzeug/refine/icons/lucide` adapter for selected Lucide icons, or `@vielzeug/refine/icons/iconify` for local Heroicons, Tabler, and Material Design Icons data. Keep importing the component from `@vielzeug/refine/icon`. Lucide is an optional peer: install `lucide` yourself only when choosing its data or `@vielzeug/refine/icons/lucide/all`; core and structural adapters do not require it. The [icon reference](./components/icon.md) covers imports, licenses, and presentation behavior.\n\nFor selected individual Iconify data, use `heroiconsIcons`, `tablerIcons`, or `mdiIcons` from `@vielzeug/refine/icons/heroicons`, `/tabler`, or `/mdi`. For example, `registerIcons(heroiconsIcons({ Search, Home }))` preserves your app keys. Install the matching `@iconify-icons/<family>` data package yourself; these helpers do not accept framework components. Keep `iconifyIcons` for collection JSON and aliases.\n\n## Set Attributes and Properties\n\nUse attributes for serializable HTML state.\n\n```html\n<ore-button variant=\"outline\" color=\"secondary\" size=\"lg\" disabled>\n Save changes\n</ore-button>\n```\n\nUse JavaScript properties for arrays, objects, callbacks, and controlled state.\n\n```ts\nimport '@vielzeug/refine/datagrid';\nimport type { OreDataGridProps } from '@vielzeug/refine/datagrid';\n\nconst grid = document.querySelector('ore-datagrid') as HTMLElement & OreDataGridProps;\ngrid.columns = [\n { key: 'name', label: 'Name', sortable: true },\n { key: 'role', label: 'Role' },\n];\ngrid.rows = [\n { id: '1', name: 'Ada', role: 'Admin' },\n { id: '2', name: 'Linus', role: 'Editor' },\n];\n```\n\nEnumerated properties such as `variant`, `size`, and `color` accept the values listed on each component's reference\npage. An unsupported value resolves to the property's declared default (or `undefined` when it has no default).\n\nIn an Ore template, `model()` can bind a Refine field's `value` to a Ripple signal and write user input back:\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { html, model } from '@vielzeug/ore';\nimport '@vielzeug/refine/input';\n\nconst email = signal('');\nconst field = html`<ore-input label=\"Email\" type=\"email\" value=${model(email)} />`;\n```\n\n## Handle Events\n\nForm controls dispatch standard `input` and `change` events. Read their current property from `event.currentTarget`.\n\n```ts\nconst input = document.querySelector('ore-input');\ninput?.addEventListener('input', (event) => {\n const target = event.currentTarget as HTMLElement & { value: string };\n console.log(target.value);\n});\n```\n\nCustom events use kebab-case names and typed detail objects.\n\n```ts\nimport type { OreDialogEvents } from '@vielzeug/refine/dialog';\n\nconst dialog = document.querySelector('ore-dialog');\ndialog?.addEventListener('open-change', (event: CustomEvent<OreDialogEvents['open-change']>) => {\n console.log(event.detail.open, event.detail.reason);\n});\n```\n\n## Native Form Validation\n\nRefine's form-associated controls integrate with native form submission, `FormData`, validity checks, and reset.\nUse `required` for a required-value constraint; setting `error` displays and announces feedback but does not, by\nitself, invalidate the form.\n\n```html\n<form id=\"profile-form\">\n <ore-input name=\"email\" type=\"email\" label=\"Email\" required></ore-input>\n <ore-select name=\"role\" label=\"Role\" required>\n <option value=\"\">Choose a role…</option>\n <option value=\"editor\">Editor</option>\n <option value=\"admin\">Admin</option>\n </ore-select>\n <ore-button type=\"submit\">Save</ore-button>\n</form>\n\n<script type=\"module\">\n import '@vielzeug/refine/input';\n import '@vielzeug/refine/select';\n import '@vielzeug/refine/button';\n\n const form = document.querySelector('#profile-form');\n form.addEventListener('submit', (event) => {\n event.preventDefault();\n console.log(new FormData(form).get('email'));\n });\n</script>\n```\n\nThe browser runs native validation before dispatching `submit`. Call `form.reportValidity()` to show validation\nfeedback directly, or `form.checkValidity()` for a boolean check without the browser's validation UI.\n\n## Compose Slots\n\nDefault slots hold primary content. Named slots identify component-owned regions.\n\n```html\n<ore-card>\n <span slot=\"header\">Account</span>\n <p>Profile settings</p>\n <div slot=\"footer\">\n <ore-button variant=\"ghost\">Cancel</ore-button>\n <ore-button>Save</ore-button>\n </div>\n</ore-card>\n```\n\nInput components commonly expose `prefix`, `suffix`, `label`, `helper`, and `error` slots. Each component reference lists its supported slots.\n\n## Load TypeScript Framework Declarations\n\nImport the declaration matching the framework once in an application type entry point.\n\n::: code-group\n\n```ts [DOM]\nimport type {} from '@vielzeug/refine/frameworks/elements';\n```\n\n```ts [React]\nimport type {} from '@vielzeug/refine/frameworks/react';\n```\n\n```ts [Vue]\nimport type {} from '@vielzeug/refine/frameworks/vue';\n```\n\n:::\n\nThese entry points provide typed tags and component properties. They do not register components or install runtime adapters.\n\n## Framework Integration\n\nRefine elements use native DOM properties, events, and slots. Frameworks differ only in how they forward custom-element properties and events.\n\n::: code-group\n\n```tsx [React 19]\nimport '@vielzeug/refine/button';\n\nexport function SaveButton() {\n return <ore-button variant=\"solid\">Save</ore-button>;\n}\n```\n\n```vue [Vue 3]\n<script setup lang=\"ts\">\nimport '@vielzeug/refine/button';\n</script>\n\n<template>\n <ore-button variant=\"solid\">Save</ore-button>\n</template>\n```\n\n```svelte [Svelte]\n<script lang=\"ts\">\n import '@vielzeug/refine/button';\n</script>\n\n<ore-button variant=\"solid\">Save</ore-button>\n```\n\n:::\n\nSee [Framework Integration](./frameworks.md) for custom events, controlled properties, SSR guards, and framework-specific configuration.\n\n## Working with Other Vielzeug Libraries\n\n### Ore\n\nUse Ore when authoring a custom element that composes Refine components.\n\n```ts\nimport '@vielzeug/refine/button';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('save-panel', {\n setup() {\n return html`<ore-button @click=${() => console.log('save')}>Save</ore-button>`;\n },\n});\n```\n\n### Ripple\n\nUse Ripple to drive component properties from application state.\n\n```ts\nimport '@vielzeug/refine/button';\nimport { effect, signal } from '@vielzeug/ripple';\n\nconst loading = signal(false);\nconst button = document.querySelector('ore-button') as HTMLElement & { loading?: boolean };\nconst loadingEffect = effect(() => {\n button.loading = loading.value;\n});\n\nloadingEffect.dispose();\n```\n\n### Necromancer\n\nUse Necromancer directly when you need animation handles, groups, or explicit keyframes. Refine's [`animateReflow`](./animation#list-reflow-animatereflow) is the token-driven wrapper over its FLIP primitive and is enough for keyed lists that reflow.\n\n## Best Practices\n\n- **Import** `tokens.css` once before rendering components.\n- **Register** components through explicit subpaths.\n- **Set** structured data and callbacks as JavaScript properties.\n- **Read** form state from the event target instead of custom event payloads.\n- **Provide** accessible labels for icon-only controls.\n- **Use** component reference pages for supported slots, parts, and CSS properties.\n- **Dispose** application-owned Ripple effects and service handles.\n- **Guard** DOM-dependent component registration during SSR.\n- **Working with AI agents?** Install the `vielzeug` skill (`npx -y @vielzeug/codex skills install`) so agents reach for Refine components and tokens instead of custom widgets: see the [Codex usage guide](../codex/usage.md#agent-skill).\n- **Rendering model-authored UI?** Validate it through the [on-demand UI document contract](./on-demand-ui) instead of trusting model output; never build DOM from unvalidated JSON.\n"
7
7
  },
8
8
  "examples": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "apiSource": "export type { AsyncState, Resource, ResourceOptions } from './_async';\nexport { createRipple, type Ripple } from './_default';\nexport type { BridgedReadable, FromSubscribableOptions } from './_subscribable';\nexport type { WatchOptions } from './_watch';\nexport { RippleCycleError, RippleDisposedError, RippleError } from './errors';\nexport { isReactive } from './runtime';\nexport type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n Readable,\n RippleErrorContext,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Subscribable,\n Unsubscribe,\n} from './types';\n\nimport { defaultRipple } from './_default';\n\nexport const signal = defaultRipple.signal;\nexport const computed = defaultRipple.computed;\nexport const effect = defaultRipple.effect;\nexport const batch = defaultRipple.batch;\nexport const createScope = defaultRipple.createScope;\nexport const untrack = defaultRipple.untrack;\nexport const watch = defaultRipple.watch;\nexport const resource = defaultRipple.resource;\nexport const fromSubscribable = defaultRipple.fromSubscribable;\n",
3
3
  "docs": {
4
- "index": "---\ntitle: 'Ripple: Reactive graphs'\ndescription: Framework-agnostic signals, effects, async resources, scopes, and structural bridges.\npackage: ripple\ncategory: state\nkeywords: [reactive, signals, computed, effects, graph, scope, batch, watch]\nrelated: [ore, clockwork, ledger]\nexports: [createRipple, signal, computed, effect, resource]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ripple\" />\n\n## Why Ripple?\n\nHand-rolled reactive state spreads subscription, cleanup, and derived-value rules across application code. Ripple gives you one graph boundary with explicit disposal and fine-grained dependencies while keeping rendering and routing outside the runtime.\n\n```ts\n// Before\nlet count = 0;\nconst listeners = new Set<() => void>();\n\nfunction setCount(next: number) {\n count = next;\n for (const listener of listeners) listener();\n}\n\n// After\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\ncount.value = 1;\nstop.dispose();\nripple.dispose();\n```\n\n| Feature | Ripple | Zustand | Jotai |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"ripple\" type=\"size\" /> | ~3.5 kB | ~7 kB |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Framework-agnostic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | React-first |\n| Explicit graph lifetime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Fine-grained derived values | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Selectors | Atoms |\n\n<div class=\"decision-callout\">\n\n**Use Ripple when** you need framework-independent state with explicit graph lifetime and small composable primitives.\n\n**Consider a framework store when** component bindings, server cache, or framework-specific tooling matter more than portable reactive state.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\nCreate one graph, derive a value, observe it, then dispose when the graph lifetime ends.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\nripple.batch(() => {\n count.value = 1;\n count.value = 2;\n});\n\nstop.dispose();\nripple.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createRipple({ onError })` creates an isolated graph with one runtime failure callback.\n- `signal()` stores writable values with configurable equality.\n- `computed()` derives lazy read-only values.\n- `effect()` reacts to dependency changes with cleanup support.\n- `batch()` coalesces synchronous writes and notifications.\n- `createScope()` groups owned reactive work.\n- `watch()` observes one selected source transition.\n- `resource()` owns focused latest-request async state.\n- `fromSubscribable()` creates a disposable bridge for external `{ getSnapshot, subscribe }` sources.\n- `RippleError`, `RippleCycleError`, and `RippleDisposedError` distinguish package-originated failures.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ore](/ore/): uses Ripple signals and effects for web-component reactivity.\n- [Clockwork](/clockwork/): exposes machine state through reactive Ripple values.\n- [Ledger](/ledger/): adds command-based undo and redo beside Ripple state.\n\n</div>\n\n<!-- markdownlint-enable -->\n",
5
- "api": "---\ntitle: 'Ripple: API Reference'\ndescription: Complete reference for reactive graphs, signals, effects, scopes, and watchers.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createRipple()` | Create isolated graph | Sync | Disposal is terminal; create a new graph instead of reusing it |\n| `signal()` | Create writable value | Sync | Default graph is process-wide |\n| `computed()` | Create lazy derived value | Sync | Keep derivation pure |\n| `effect()` | React to dependency reads | Sync | Dispose handle or return cleanup |\n| `batch()` | Coalesce synchronous writes | Sync | Does not roll back writes |\n| `createScope()` | Group owned reactive work | Sync | Call `run()` to activate it |\n| `untrack()` | Read without tracking | Sync | Read still happens immediately |\n| `watch()` | Observe selected output | Sync | Use `effect()` for broad reads |\n| `resource()` | Load from tracked reactive input | Sync factory / Async loader | `reload()` returns `void`; completion is observable state |\n| `fromSubscribable()` | Bridge external source | Sync | Returned bridge must be disposed |\n| `isReactive()` | Test Ripple's marked readable contract | Sync | Not a security or provenance check |\n| `RippleError` | Base package error | Sync | Runtime callback failures retain their original value |\n| `RippleCycleError` | Computed cycle or runaway flush | Sync | Flush failures reach `onError`, not the write site |\n| `RippleDisposedError` | Work after disposal | Sync | Cached reads remain available |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/ripple` | All primitives, types, and errors: signals, computed, effects, scopes, watch, and the isolated graph factory |\n\n## Graph Creation\n\n### `createRipple(options?)`\n\n```ts\nfunction createRipple(options?: RippleOptions): Ripple;\n```\n\nCreates one isolated reactive graph. Factories on the returned object share scheduling, ownership, and error boundaries. `dispose()` is terminal: `ripple.disposed` becomes `true`, existing owned work is disposed, and creating more graph work throws `RippleDisposedError`. Create a new graph for a new lifetime.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.onError` | `(error, context) => void` | Handles computed refresh, effect, cleanup, listener, and flush-loop failures. Default rethrows asynchronously via `queueMicrotask`; pass a no-op to silence. A failure in one node never interrupts sibling propagation. |\n\n`onError` is the only runtime option. `RippleErrorContext.kind` is `cleanup`, `computed`,\n`effect`, `flush`, or `listener`; `name` is optional. There is no event stream or error-policy option.\n\n**Returns:** `Ripple`.\n\n**Example:**\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst stop = ripple.effect(() => console.log(count.value));\n\nstop.dispose();\nripple.dispose();\n```\n\n---\n\n### `isReactive(value)`\n\n```ts\nfunction isReactive<T>(value: T | Readable<T>): value is Readable<T>;\n```\n\nTests Ripple's shared-symbol marker and complete readable surface (`value`, `peek()`, and `subscribe()`). Recognition works across duplicated Ripple module graphs. The marker is intentionally structural and forgeable; do not use this guard for trust or provenance decisions.\n\n**Returns:** `true` for marked signals, computeds, resources, and external-source bridges with the complete readable contract.\n\n**Example:**\n\n```ts\nimport { isReactive, signal } from '@vielzeug/ripple';\n\nconsole.log(isReactive(signal(0)));\n```\n\n## Default Graph Functions\n\nRoot helpers (`signal`, `computed`, `effect`, `batch`, `createScope`, `untrack`, `watch`, `resource`, `fromSubscribable`) operate on a process-lifetime default graph. The graph itself has no public disposal handle; effects, scopes, resources, and bridges still require owner-managed disposal. For isolated, disposable graphs (tests, SSR, embedded features) use `createRipple()`.\n\n### `signal(initial, options?)`\n\n```ts\nfunction signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n```\n\nCreates writable state on the default graph. Use `update()` for immutable replacement patterns.\n\n**Returns:** `Signal<T>`.\n\n**Example:**\n\n```ts\nimport { signal } from '@vielzeug/ripple';\n\nconst count = signal(0);\ncount.value += 1;\n\nconst cart = signal({ items: 0 });\ncart.update((state) => ({ ...state, items: state.items + 1 }));\n```\n\n---\n\n### `computed(derive, options?)`\n\n```ts\nfunction computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n```\n\nCreates a lazy read-only value from reactive reads in `derive`. `peek()` may trigger derivation and throw directly. During dependency propagation, a computed refresh failure is reported to `onError` with context kind `computed` and does not prevent sibling dependents from updating. A disposed computed returns its last cached value from `peek()` without recomputing; a computed disposed before its first evaluation throws `RippleDisposedError` instead of returning a value.\n\n**Returns:** `Readable<T>`.\n\n**Example:**\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\n\nconst count = signal(2);\nconst doubled = computed(() => count.value * 2);\nconsole.log(doubled.value);\n```\n\n---\n\n### `effect(callback, options?)`\n\n```ts\nfunction effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n```\n\nRuns immediately and reruns when its tracked reads change. A returned cleanup runs before the next callback or disposal.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.name` | `string` | Optional label in error context |\n\nEffects flush synchronously; `EffectOptions` has no scheduler option. Use `batch()` to coalesce writes.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { effect, signal } from '@vielzeug/ripple';\n\nconst connected = signal(false);\nconst stop = effect(() => {\n if (!connected.value) return;\n\n return () => console.log('disconnect');\n});\n\nstop.dispose();\n```\n\n---\n\n### `batch(fn)` and `untrack(fn)`\n\n```ts\nfunction batch<T>(fn: () => T): T;\nfunction untrack<T>(fn: () => T): T;\n```\n\n`batch()` defers effects and listeners until its callback returns. `untrack()` reads current state without adding dependencies to an enclosing effect.\n\n**Returns:** the callback result.\n\n**Example:**\n\n```ts\nimport { batch, signal, untrack } from '@vielzeug/ripple';\n\nconst first = signal('Ada');\nconst last = signal('Lovelace');\nconst locale = signal('en-US');\n\nbatch(() => {\n first.value = 'Grace';\n last.value = 'Hopper';\n});\n\nconsole.log(untrack(() => locale.value));\n```\n\n---\n\n### `createScope(name?)`\n\n```ts\nfunction createScope(name?: string): Scope;\n```\n\nCreates a disposable ownership boundary. Work created inside `scope.run()` belongs to that scope. Explicit scopes created during an effect run attach to the enclosing scope (not the transient effect-run owner) and therefore survive reruns until explicitly or parent-disposed. Disposed children detach from parent ownership immediately.\n\n**Returns:** `Scope`.\n\n**Example:**\n\n```ts\nimport { createScope, effect, signal } from '@vielzeug/ripple';\n\nconst scope = createScope('panel');\nconst count = signal(0);\n\nscope.run(() => effect(() => console.log(count.value)));\nscope.dispose();\n```\n\n### `resource(source, loader, options?)`\n\n```ts\nfunction resource<Source, Value>(\n source: () => Source,\n loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>,\n options?: ResourceOptions,\n): Resource<Value>;\n```\n\nCreates a disposable readable async state and starts loading immediately. Source changes and `reload()` abort stale work before starting the next loader. The last successful value is preserved as `previous`, including when that value is `undefined`.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `source` | `() => Source` | Tracks reactive input reads |\n| `loader` | `(source, context) => Promise<Value>` | Receives input and a request-owned cancellation signal |\n| `options.name` | `string` | Optional resource label |\n\n**Returns:** `Resource<Value>`. Source and loader failures become error state, not `onError` reports.\n\n**Example:**\n\n```ts\nimport { resource, signal } from '@vielzeug/ripple';\n\nconst userId = signal('42');\nconst user = resource(\n () => userId.value,\n (id, { signal }) => fetch(`/users/${id}`, { signal }).then((response) => response.json()),\n);\nconsole.log(user.value);\nuser.dispose();\n```\n\nThe state is `{ status: 'pending', previous? }`, `{ status: 'success', value }`, or `{ status: 'error', error, previous? }`. Reads remain available after disposal; `reload()` and new subscriptions throw `RippleDisposedError`.\n\n## Watch\n\n### `watch(source, callback, options?)`\n\n```ts\nfunction watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, previous: T | undefined) => void,\n options?: WatchOptions<T>,\n): EffectHandle;\n```\n\nObserves selected output changes using the default graph or a `Ripple.watch()` method. Callback-only reactive reads are untracked; without `immediate`, initial evaluation does not count as an invocation. For one-shot observation, dispose the returned handle from inside the callback: with `immediate: true`, do the first invocation at the call site instead, because the callback runs before the handle exists.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst stop = watch(count, (value, previous) => console.log(previous, value), { immediate: true });\nstop.dispose();\n```\n\n---\n\n### `fromSubscribable(source, options?)`\n\n```ts\nfunction fromSubscribable<T>(source: Subscribable<T>, options?: FromSubscribableOptions): BridgedReadable<T>;\n```\n\nBridges an external `{ getSnapshot, subscribe }` source into a disposable reactive readable. The bridge subscribes and then rereads the snapshot to avoid missing a change during registration. The root helper uses the default graph; `ripple.fromSubscribable()` binds ownership to an isolated graph. An optional signal disposes the bridge.\n\n**Returns:** `BridgedReadable<T>`. Disposal invokes the source unsubscribe function. Reads remain available afterward, while new subscriptions throw.\n\n**Example:**\n\n```ts\nimport { fromSubscribable } from '@vielzeug/ripple';\n\nlet current = 'idle';\nconst listeners = new Set<() => void>();\nconst state = fromSubscribable({\n getSnapshot: () => current,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n});\n\ncurrent = 'ready';\nfor (const listener of listeners) listener();\nconsole.log(state.value);\nstate.dispose();\n```\n\n## Types\n\n```ts\ntype Cleanup = () => void;\ntype Equality<T> = (previous: T, next: T) => boolean;\ntype Unsubscribe = () => void;\n\ntype SignalOptions<T> = { equals?: Equality<T>; name?: string };\ntype ComputedOptions<T> = { equals?: Equality<T>; name?: string };\ntype EffectOptions = { name?: string };\ntype WatchOptions<T> = { equals?: Equality<T>; immediate?: boolean; name?: string };\n\ntype RippleErrorContext = { readonly kind: 'cleanup' | 'computed' | 'effect' | 'flush' | 'listener'; readonly name?: string };\ntype RippleOptions = { onError?: (error: unknown, context: RippleErrorContext) => void };\n\ninterface Readable<T> {\n readonly name?: string;\n peek(): T;\n /** Change-only: the listener is not called on subscribe, only after a tracked source changes. */\n subscribe(listener: () => void): Unsubscribe;\n readonly value: T;\n}\n\ninterface Signal<T> extends Readable<T> { update(updater: (prev: T) => T): void; value: T }\ninterface Disposable { dispose(): void; readonly disposed: boolean; readonly disposalSignal: AbortSignal; [Symbol.dispose](): void }\ntype EffectHandle = Disposable;\ninterface Scope extends Disposable { run<T>(fn: () => T): T }\n\ninterface Subscribable<T> {\n getSnapshot(): T;\n subscribe(listener: () => void): Unsubscribe;\n}\n\ntype FromSubscribableOptions = { name?: string; signal?: AbortSignal };\ntype BridgedReadable<T> = Readable<T> & Disposable;\n\ntype AsyncState<T> =\n | { readonly previous?: T; readonly status: 'pending' }\n | { readonly status: 'success'; readonly value: T }\n | { readonly error: unknown; readonly previous?: T; readonly status: 'error' };\n\ninterface Resource<T> extends Readable<AsyncState<T>>, Disposable {\n reload(): void;\n}\n\ntype ResourceOptions = { name?: string };\n\ninterface Ripple extends Disposable {\n batch<T>(fn: () => T): T;\n computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n createScope(name?: string): Scope;\n effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n fromSubscribable<T>(source: Subscribable<T>, options?: FromSubscribableOptions): BridgedReadable<T>;\n resource<Source, Value>(\n source: () => Source,\n loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>,\n options?: ResourceOptions,\n ): Resource<Value>;\n signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n untrack<T>(fn: () => T): T;\n watch<T>(source: Readable<T> | (() => T), callback: (value: T, previous: T | undefined) => void, options?: WatchOptions<T>): EffectHandle;\n}\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `RippleError` | Base Ripple error; a new subscription to a disposed external-source bridge | Use `instanceof RippleError` to narrow unknown values. |\n| `RippleCycleError` | A computed reads itself, or a flush exceeds the iteration limit | Extends `RippleError`. A flush-loop breach is reported to `onError` (context kind `flush`) instead of throwing from the write site. |\n| `RippleDisposedError` | New graph work, writes, or subscriptions on a disposed runtime; `run()` on a disposed scope; `reload()` or new subscriptions on a disposed resource; reading a never-evaluated disposed computed | Extends `RippleError`. Cached reads and repeated disposal remain valid. |\n\nThese are the three exported error classes. User callback failures are reported as their original\nvalues through `onError`; they are not wrapped in `RippleError`. A direct computed read can throw\nits derivation error to the caller.\n",
4
+ "index": "---\ntitle: 'Ripple: Reactive graphs'\ndescription: Framework-agnostic signals, effects, async resources, scopes, and structural bridges.\npackage: ripple\ncategory: state\nkeywords: [reactive, signals, computed, effects, graph, scope, batch, watch]\nrelated: [ore, clockwork, ledger]\nexports: [createRipple, signal, computed, effect, resource]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ripple\" />\n\n## Why Ripple?\n\nHand-rolled reactive state spreads subscription, cleanup, and derived-value rules across application code. Ripple gives you one graph boundary with explicit disposal and fine-grained dependencies while keeping rendering and routing outside the runtime.\n\n```ts\n// Before\nlet count = 0;\nconst listeners = new Set<() => void>();\n\nfunction setCount(next: number) {\n count = next;\n for (const listener of listeners) listener();\n}\n\n// After\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\ncount.value = 1;\nstop.dispose();\nripple.dispose();\n```\n\n| Feature | Ripple | Zustand | Jotai |\n| --------------------------- | -------------------------------------------- | -------------------------------------------- | ---------------------------------------- |\n| Bundle size | <PackageInfo package=\"ripple\" type=\"size\" /> | ~3.5 kB | ~7 kB |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Framework-agnostic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | React-first |\n| Explicit graph lifetime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Fine-grained derived values | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Selectors | Atoms |\n\n<div class=\"decision-callout\">\n\n**Use Ripple when** you need framework-independent state with explicit graph lifetime and small composable primitives.\n\n**Consider a framework store when** component bindings, server cache, or framework-specific tooling matter more than portable reactive state.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\nCreate one graph, derive a value, observe it, then dispose when the graph lifetime ends.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\nripple.batch(() => {\n count.value = 1;\n count.value = 2;\n});\n\nstop.dispose();\nripple.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createRipple({ onError })` creates an isolated graph with one runtime failure callback.\n- `signal()` stores writable values with configurable equality.\n- `computed()` derives lazy read-only values.\n- `effect()` reacts to dependency changes with cleanup support.\n- `batch()` coalesces synchronous writes and notifications.\n- `createScope()` groups owned reactive work.\n- `watch()` observes one selected source transition.\n- `resource()` owns focused latest-request async state.\n- `fromSubscribable()` creates a disposable bridge for external `{ getSnapshot, subscribe }` sources.\n- `RippleError`, `RippleCycleError`, and `RippleDisposedError` distinguish package-originated failures.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ore](/ore/): uses Ripple signals and effects for web-component reactivity.\n- [Clockwork](/clockwork/): exposes machine state through reactive Ripple values.\n- [Ledger](/ledger/): adds command-based undo and redo beside Ripple state.\n\n</div>\n\n<!-- markdownlint-enable -->\n",
5
+ "api": "---\ntitle: 'Ripple: API Reference'\ndescription: Complete reference for reactive graphs, signals, effects, scopes, and watchers.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --------------------- | -------------------------------------- | --------------------------- | -------------------------------------------------------------- |\n| `createRipple()` | Create isolated graph | Sync | Disposal is terminal; create a new graph instead of reusing it |\n| `signal()` | Create writable value | Sync | Default graph is process-wide |\n| `computed()` | Create lazy derived value | Sync | Keep derivation pure |\n| `effect()` | React to dependency reads | Sync | Dispose handle or return cleanup |\n| `batch()` | Coalesce synchronous writes | Sync | Does not roll back writes |\n| `createScope()` | Group owned reactive work | Sync | Call `run()` to activate it |\n| `untrack()` | Read without tracking | Sync | Read still happens immediately |\n| `watch()` | Observe selected output | Sync | Use `effect()` for broad reads |\n| `resource()` | Load from tracked reactive input | Sync factory / Async loader | `reload()` returns `void`; completion is observable state |\n| `fromSubscribable()` | Bridge external source | Sync | Returned bridge must be disposed |\n| `isReactive()` | Test Ripple's marked readable contract | Sync | Not a security or provenance check |\n| `RippleError` | Base package error | Sync | Runtime callback failures retain their original value |\n| `RippleCycleError` | Computed cycle or runaway flush | Sync | Flush failures reach `onError`, not the write site |\n| `RippleDisposedError` | Work after disposal | Sync | Cached reads remain available |\n\n## Package Entry Point\n\n| Import | Purpose |\n| ------------------ | ------------------------------------------------------------------------------------------------------------ |\n| `@vielzeug/ripple` | All primitives, types, and errors: signals, computed, effects, scopes, watch, and the isolated graph factory |\n\n## Graph Creation\n\n### `createRipple(options?)`\n\n```ts\nfunction createRipple(options?: RippleOptions): Ripple;\n```\n\nCreates one isolated reactive graph. Factories on the returned object share scheduling, ownership, and error boundaries. `dispose()` is terminal: `ripple.disposed` becomes `true`, existing owned work is disposed, and creating more graph work throws `RippleDisposedError`. Create a new graph for a new lifetime.\n\n| Parameter | Type | Description |\n| ----------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `options.onError` | `(error, context) => void` | Handles computed refresh, effect, cleanup, listener, and flush-loop failures. Default rethrows asynchronously via `queueMicrotask`; pass a no-op to silence. A failure in one node never interrupts sibling propagation. |\n\n`onError` is the only runtime option. `RippleErrorContext.kind` is `cleanup`, `computed`,\n`effect`, `flush`, or `listener`; `name` is optional. There is no event stream or error-policy option.\n\n**Returns:** `Ripple`.\n\n**Example:**\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst stop = ripple.effect(() => console.log(count.value));\n\nstop.dispose();\nripple.dispose();\n```\n\n---\n\n### `isReactive(value)`\n\n```ts\nfunction isReactive<T>(value: T | Readable<T>): value is Readable<T>;\n```\n\nTests Ripple's shared-symbol marker and complete readable surface (`value`, `peek()`, and `subscribe()`). Recognition works across duplicated Ripple module graphs. The marker is intentionally structural and forgeable; do not use this guard for trust or provenance decisions.\n\n**Returns:** `true` for marked signals, computeds, resources, and external-source bridges with the complete readable contract.\n\n**Example:**\n\n```ts\nimport { isReactive, signal } from '@vielzeug/ripple';\n\nconsole.log(isReactive(signal(0)));\n```\n\n## Default Graph Functions\n\nRoot helpers (`signal`, `computed`, `effect`, `batch`, `createScope`, `untrack`, `watch`, `resource`, `fromSubscribable`) operate on a process-lifetime default graph. The graph itself has no public disposal handle; effects, scopes, resources, and bridges still require owner-managed disposal. For isolated, disposable graphs (tests, SSR, embedded features) use `createRipple()`.\n\n### `signal(initial, options?)`\n\n```ts\nfunction signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n```\n\nCreates writable state on the default graph. Use `update()` for immutable replacement patterns.\n\n**Returns:** `Signal<T>`.\n\n**Example:**\n\n```ts\nimport { signal } from '@vielzeug/ripple';\n\nconst count = signal(0);\ncount.value += 1;\n\nconst cart = signal({ items: 0 });\ncart.update((state) => ({ ...state, items: state.items + 1 }));\n```\n\n---\n\n### `computed(derive, options?)`\n\n```ts\nfunction computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n```\n\nCreates a lazy read-only value from reactive reads in `derive`. `peek()` may trigger derivation and throw directly. During dependency propagation, a computed refresh failure is reported to `onError` with context kind `computed` and does not prevent sibling dependents from updating. A disposed computed returns its last cached value from `peek()` without recomputing; a computed disposed before its first evaluation throws `RippleDisposedError` instead of returning a value.\n\n**Returns:** `Readable<T>`.\n\n**Example:**\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\n\nconst count = signal(2);\nconst doubled = computed(() => count.value * 2);\nconsole.log(doubled.value);\n```\n\n---\n\n### `effect(callback, options?)`\n\n```ts\nfunction effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n```\n\nRuns immediately and reruns when its tracked reads change. A returned cleanup runs before the next callback or disposal.\n\n| Parameter | Type | Description |\n| -------------- | -------- | ------------------------------- |\n| `options.name` | `string` | Optional label in error context |\n\nEffects flush synchronously; `EffectOptions` has no scheduler option. Use `batch()` to coalesce writes.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { effect, signal } from '@vielzeug/ripple';\n\nconst connected = signal(false);\nconst stop = effect(() => {\n if (!connected.value) return;\n\n return () => console.log('disconnect');\n});\n\nstop.dispose();\n```\n\n---\n\n### `batch(fn)` and `untrack(fn)`\n\n```ts\nfunction batch<T>(fn: () => T): T;\nfunction untrack<T>(fn: () => T): T;\n```\n\n`batch()` defers effects and listeners until its callback returns. `untrack()` reads current state without adding dependencies to an enclosing effect.\n\n**Returns:** the callback result.\n\n**Example:**\n\n```ts\nimport { batch, signal, untrack } from '@vielzeug/ripple';\n\nconst first = signal('Ada');\nconst last = signal('Lovelace');\nconst locale = signal('en-US');\n\nbatch(() => {\n first.value = 'Grace';\n last.value = 'Hopper';\n});\n\nconsole.log(untrack(() => locale.value));\n```\n\n---\n\n### `createScope(name?)`\n\n```ts\nfunction createScope(name?: string): Scope;\n```\n\nCreates a disposable ownership boundary. Work created inside `scope.run()` belongs to that scope. Explicit scopes created during an effect run attach to the enclosing scope (not the transient effect-run owner) and therefore survive reruns until explicitly or parent-disposed. Disposed children detach from parent ownership immediately.\n\n**Returns:** `Scope`.\n\n**Example:**\n\n```ts\nimport { createScope, effect, signal } from '@vielzeug/ripple';\n\nconst scope = createScope('panel');\nconst count = signal(0);\n\nscope.run(() => effect(() => console.log(count.value)));\nscope.dispose();\n```\n\n### `resource(source, loader, options?)`\n\n```ts\nfunction resource<Source, Value>(\n source: () => Source,\n loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>,\n options?: ResourceOptions,\n): Resource<Value>;\n```\n\nCreates a disposable readable async state and starts loading immediately. Source changes and `reload()` abort stale work before starting the next loader. The last successful value is preserved as `previous`, including when that value is `undefined`.\n\n| Parameter | Type | Description |\n| -------------- | ------------------------------------- | ------------------------------------------------------ |\n| `source` | `() => Source` | Tracks reactive input reads |\n| `loader` | `(source, context) => Promise<Value>` | Receives input and a request-owned cancellation signal |\n| `options.name` | `string` | Optional resource label |\n\n**Returns:** `Resource<Value>`. Source and loader failures become error state, not `onError` reports.\n\n**Example:**\n\n```ts\nimport { resource, signal } from '@vielzeug/ripple';\n\nconst userId = signal('42');\nconst user = resource(\n () => userId.value,\n (id, { signal }) => fetch(`/users/${id}`, { signal }).then((response) => response.json()),\n);\nconsole.log(user.value);\nuser.dispose();\n```\n\nThe state is `{ status: 'pending', previous? }`, `{ status: 'success', value }`, or `{ status: 'error', error, previous? }`. Reads remain available after disposal; `reload()` and new subscriptions throw `RippleDisposedError`.\n\n## Watch\n\n### `watch(source, callback, options?)`\n\n```ts\nfunction watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, previous: T | undefined) => void,\n options?: WatchOptions<T>,\n): EffectHandle;\n```\n\nObserves selected output changes using the default graph or a `Ripple.watch()` method. Callback-only reactive reads are untracked; without `immediate`, initial evaluation does not count as an invocation. For one-shot observation, dispose the returned handle from inside the callback: with `immediate: true`, do the first invocation at the call site instead, because the callback runs before the handle exists.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst stop = watch(count, (value, previous) => console.log(previous, value), { immediate: true });\nstop.dispose();\n```\n\n---\n\n### `fromSubscribable(source, options?)`\n\n```ts\nfunction fromSubscribable<T>(source: Subscribable<T>, options?: FromSubscribableOptions): BridgedReadable<T>;\n```\n\nBridges an external `{ getSnapshot, subscribe }` source into a disposable reactive readable. The bridge subscribes and then rereads the snapshot to avoid missing a change during registration. The root helper uses the default graph; `ripple.fromSubscribable()` binds ownership to an isolated graph. An optional signal disposes the bridge.\n\n**Returns:** `BridgedReadable<T>`. Disposal invokes the source unsubscribe function. Reads remain available afterward, while new subscriptions throw.\n\n**Example:**\n\n```ts\nimport { fromSubscribable } from '@vielzeug/ripple';\n\nlet current = 'idle';\nconst listeners = new Set<() => void>();\nconst state = fromSubscribable({\n getSnapshot: () => current,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n});\n\ncurrent = 'ready';\nfor (const listener of listeners) listener();\nconsole.log(state.value);\nstate.dispose();\n```\n\n## Types\n\n```ts\ntype Cleanup = () => void;\ntype Equality<T> = (previous: T, next: T) => boolean;\ntype Unsubscribe = () => void;\n\ntype SignalOptions<T> = { equals?: Equality<T>; name?: string };\ntype ComputedOptions<T> = { equals?: Equality<T>; name?: string };\ntype EffectOptions = { name?: string };\ntype WatchOptions<T> = { equals?: Equality<T>; immediate?: boolean; name?: string };\n\ntype RippleErrorContext = {\n readonly kind: 'cleanup' | 'computed' | 'effect' | 'flush' | 'listener';\n readonly name?: string;\n};\ntype RippleOptions = { onError?: (error: unknown, context: RippleErrorContext) => void };\n\ninterface Readable<T> {\n readonly name?: string;\n peek(): T;\n /** Change-only: the listener is not called on subscribe, only after a tracked source changes. */\n subscribe(listener: () => void): Unsubscribe;\n readonly value: T;\n}\n\ninterface Signal<T> extends Readable<T> {\n update(updater: (prev: T) => T): void;\n value: T;\n}\ninterface Disposable {\n dispose(): void;\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.dispose](): void;\n}\ntype EffectHandle = Disposable;\ninterface Scope extends Disposable {\n run<T>(fn: () => T): T;\n}\n\ninterface Subscribable<T> {\n getSnapshot(): T;\n subscribe(listener: () => void): Unsubscribe;\n}\n\ntype FromSubscribableOptions = { name?: string; signal?: AbortSignal };\ntype BridgedReadable<T> = Readable<T> & Disposable;\n\ntype AsyncState<T> =\n | { readonly previous?: T; readonly status: 'pending' }\n | { readonly status: 'success'; readonly value: T }\n | { readonly error: unknown; readonly previous?: T; readonly status: 'error' };\n\ninterface Resource<T> extends Readable<AsyncState<T>>, Disposable {\n reload(): void;\n}\n\ntype ResourceOptions = { name?: string };\n\ninterface Ripple extends Disposable {\n batch<T>(fn: () => T): T;\n computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n createScope(name?: string): Scope;\n effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n fromSubscribable<T>(source: Subscribable<T>, options?: FromSubscribableOptions): BridgedReadable<T>;\n resource<Source, Value>(\n source: () => Source,\n loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>,\n options?: ResourceOptions,\n ): Resource<Value>;\n signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n untrack<T>(fn: () => T): T;\n watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, previous: T | undefined) => void,\n options?: WatchOptions<T>,\n ): EffectHandle;\n}\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |\n| `RippleError` | Base Ripple error; a new subscription to a disposed external-source bridge | Use `instanceof RippleError` to narrow unknown values. |\n| `RippleCycleError` | A computed reads itself, or a flush exceeds the iteration limit | Extends `RippleError`. A flush-loop breach is reported to `onError` (context kind `flush`) instead of throwing from the write site. |\n| `RippleDisposedError` | New graph work, writes, or subscriptions on a disposed runtime; `run()` on a disposed scope; `reload()` or new subscriptions on a disposed resource; reading a never-evaluated disposed computed | Extends `RippleError`. Cached reads and repeated disposal remain valid. |\n\nThese are the three exported error classes. User callback failures are reported as their original\nvalues through `onError`; they are not wrapped in `RippleError`. A direct computed read can throw\nits derivation error to the caller.\n",
6
6
  "usage": "---\ntitle: 'Ripple: Usage Guide'\ndescription: Build reactive state with one explicit graph boundary.\n---\n\n[[toc]]\n\n## Basic Usage\n\nUse top-level functions when one application-lifetime graph is sufficient. Read a signal inside an effect to make that read reactive.\n\n```ts\nimport { computed, effect, signal } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst label = computed(() => `Count: ${count.value}`);\nconst stop = effect(() => console.log(label.value));\n\ncount.value = 1;\nstop.dispose();\n```\n\n## Isolated Graphs\n\nUse `createRipple()` for tests, SSR requests, embedded applications, or independently disposable features. Never mix reactive values from separate graphs.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple({\n onError: (error, context) => console.log(context.kind, error),\n});\n\nconst count = ripple.signal(0);\nconst stop = ripple.effect(() => console.log(count.value));\n\nstop.dispose();\nripple.dispose();\n```\n\n## Derived Values and Batches\n\nUse `computed()` for pure derivation. Use `untrack()` when a current read must not become an effect dependency. Use `batch()` for related synchronous writes.\n\n```ts\nconst first = ripple.signal('Ada');\nconst last = ripple.signal('Lovelace');\nconst locale = ripple.signal('en-US');\nconst name = ripple.computed(() => `${first.value} ${last.value}`);\n\nripple.effect(() => {\n console.log({ locale: ripple.untrack(() => locale.value), name: name.value });\n});\n\nripple.batch(() => {\n first.value = 'Grace';\n last.value = 'Hopper';\n});\n```\n\n## Scheduling and Subscriptions\n\nRipple propagates every synchronous write before flushing effects. Each flush pass runs effects queued at its\nstart before direct `subscribe()` listeners queued at its start. Work queued by either runs in a later pass.\nEffects coalesce writes made inside the same synchronous flush: a `batch()` (or any writes during a flush) produces a single effect run.\nEach `subscribe()` registration fires once per flush, even when several subscriptions share one callback function.\n\n```ts\nconst count = ripple.signal(0);\nconst log: string[] = [];\n\nripple.effect(() => log.push(`effect: ${count.value}`));\ncount.subscribe(() => log.push(`listener: ${count.value}`));\n\nlog.length = 0; // Ignore synchronous creation runs.\nripple.batch(() => {\n count.value = 1;\n count.value = 2;\n});\nconsole.log(log); // ['effect: 2', 'listener: 2']\n```\n\n## Ownership with Scopes\n\nCreate a scope when a group of effects or derived values shares one lifetime. Dispose the scope when its feature ends.\n\n```ts\nconst scope = ripple.createScope('panel');\nconst count = ripple.signal(0);\n\nscope.run(() => {\n ripple.effect(() => console.log(`Panel count: ${count.value}`));\n});\n\ncount.value = 1;\nscope.dispose();\n```\n\nEffects and computeds created directly during an effect run belong to that run and are disposed before rerun. Explicit scopes intentionally attach to the enclosing scope and survive effect reruns; create them outside rerunning effects or retain and dispose each scope yourself.\n\nDisposed owned nodes detach from their parent immediately, so long-lived graphs do not retain released effects or scopes.\n\n## Watch Selected Values\n\nUse `watch()` for one selected output. Reactive reads made only inside its callback are untracked. For one-shot observation, dispose the returned handle from inside the callback; with `immediate: true` the callback runs before the handle exists, so do the first invocation at the call site. Use `effect()` when every callback read should be a dependency.\n\n```ts\nconst stopWatch = ripple.watch(\n () => `${first.value} ${last.value}`,\n (value, previous) => console.log({ previous, value }),\n { immediate: true },\n);\n\nstopWatch.dispose();\n```\n\n## Error Handling\n\nFailures inside effect callbacks, computed derivations, cleanups, and listeners never corrupt the graph: the failing node is reported and siblings keep propagating. The `onError` option decides what reporting means. By default failures rethrow asynchronously (via `queueMicrotask`), surfacing as unhandled errors; pass a handler to route them to a logger or error boundary instead:\n\n```ts\nconst ripple = createRipple({\n onError: (error, context) => {\n // context.kind: 'cleanup' | 'computed' | 'effect' | 'flush' | 'listener'\n console.error(`${context.kind}:`, error);\n },\n});\n```\n\n`onError` is the only runtime option. Effect options accept only a `name`; effects do not have a\nmicrotask scheduler. Use `batch()` to coalesce related writes.\n\nA direct computed read can throw its derivation error to the caller. `onError` handles computed\nrefresh failures during propagation, not every possible synchronous exception. The exported\nhierarchy is `RippleError`, `RippleCycleError`, and `RippleDisposedError`; callback failures retain\ntheir original values.\n\nA runaway effect (one that keeps re-triggering itself) is caught by the flush iteration limit and reported with context kind `flush`; the flush is abandoned and the graph stays usable for later writes.\n\n## Bridging External Sources\n\n`fromSubscribable()` bridges external `{ getSnapshot, subscribe }` sources into a disposable reactive readable. Use `ripple.fromSubscribable()` inside isolated graphs; the root helper uses the process-lifetime default graph. The bridge rereads after subscription to close registration races, and disposal invokes the external unsubscribe function.\n\n```ts\nimport { fromSubscribable, effect } from '@vielzeug/ripple';\n\nconst routerState = fromSubscribable({\n getSnapshot: () => router.getSnapshot(),\n subscribe: (cb) => router.subscribe(cb),\n});\n\nconst stop = effect(() => {\n console.log('Current route:', routerState.value);\n});\n\nstop.dispose();\nrouterState.dispose();\n```\n\n## Load Async Resources\n\n`resource()` starts immediately, tracks its source reads, and aborts the previous loader whenever the source changes or `reload()` is called. It preserves the last successful value (including `undefined`) as `previous` while newer work is pending or fails.\n\n```ts\nconst userId = ripple.signal('42');\nconst user = ripple.resource(\n () => userId.value,\n (id, { signal }) => fetch(`/users/${id}`, { signal }).then((response) => response.json()),\n { name: 'user' },\n);\n\nconst stop = ripple.effect(() => {\n const state = user.value;\n if (state.status === 'success') console.log(state.value);\n if (state.status === 'error') console.error(state.error);\n});\n\nstop.dispose();\nuser.dispose();\n```\n\nSource and loader failures become resource error state rather than `onError` reports.\n`reload()` returns `void`; observe state to know when loading finishes. Disposed resources retain\nreadable state but throw `RippleDisposedError` from `reload()` and new subscriptions. Use Sourcerer\nfor paginated collection state and a dedicated server-state cache for shared keyed reads, retries,\nand invalidation.\n\n## Object State\n\n`signal()` with `update()` holds one value and supports immutable replacement patterns. Return replacement objects from `update()` when object consumers depend on immutable updates.\n\n```ts\nconst cart = ripple.signal({ items: 0, label: 'empty' });\nconst items = ripple.computed(() => cart.value.items);\n\ncart.update((state) => ({ ...state, items: state.items + 1 }));\ncart.value = { items: 3, label: 'ready' };\n\nconsole.log(items.value);\n```\n\n## Testing\n\nCreate an isolated graph per test. Disposal prevents effects from leaking into later tests.\n\n```ts\nimport { expect, test } from 'vitest';\nimport { createRipple } from '@vielzeug/ripple';\n\ntest('derives a doubled count', () => {\n const ripple = createRipple();\n const count = ripple.signal(2);\n const doubled = ripple.computed(() => count.value * 2);\n\n expect(doubled.value).toBe(4);\n ripple.dispose();\n});\n```\n\n## Framework Integration\n\nUse signals and effects with any renderer. Dispose component-owned effects when the component unmounts.\n\n::: code-group\n\n```ts [React]\nimport { useEffect, useState } from 'react';\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\n\nexport function Counter() {\n const [, rerender] = useState(0);\n\n useEffect(() => {\n const stop = ripple.effect(() => {\n void count.value;\n rerender((revision) => revision + 1);\n });\n\n return () => stop.dispose();\n }, []);\n\n return <button onClick={() => (count.value += 1)}>{count.value}</button>;\n}\n```\n\n```ts [Vue 3]\nimport { onUnmounted, ref } from 'vue';\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst revision = ref(0);\nconst stop = ripple.effect(() => {\n void count.value;\n revision.value++;\n});\n\nonUnmounted(() => stop.dispose());\n```\n\n```ts [Svelte]\n<script lang=\"ts\">\n import { onDestroy } from 'svelte';\n import { createRipple } from '@vielzeug/ripple';\n\n const ripple = createRipple();\n const count = ripple.signal(0);\n let revision = 0;\n const stop = ripple.effect(() => {\n void count.value;\n revision++;\n });\n\n onDestroy(() => stop.dispose());\n</script>\n\n<button on:click={() => (count.value += 1)}>{count.value}</button>\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\nOre uses Ripple for component reactivity. Clockwork actors expose framework-neutral snapshots; bridge actor subscriptions into a Ripple signal. Ledger adds undo/redo commands around state changes without replacing graph.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\nimport { defineMachine } from '@vielzeug/clockwork';\n\nconst ripple = createRipple();\nconst actor = defineMachine<Record<string, never>, { type: 'START' }>()({\n initial: 'idle',\n states: { active: {}, idle: { on: { START: { target: 'active' } } } },\n}).createActor();\n\nconst snapshot = ripple.signal(actor.snapshot);\nconst stop = actor.subscribe((next) => (snapshot.value = next));\nconst status = ripple.computed(() => snapshot.value.state);\nconsole.log(status.value);\n\nstop();\nactor.dispose();\nripple.dispose();\n```\n\n## Gotchas\n\n### `subscribe()` is change-only\n\n`Readable.subscribe(listener)` never calls the listener on subscribe: it fires only after a tracked source changes. Read the current value at the call site when you need an initial state.\n\n### `subscribe()` forces computed evaluation\n\n`Readable.subscribe()` calls `peek()` before registering the listener. For signals this is a no-op, but for computeds it forces `refresh()`: the derivation runs immediately even if no one reads `.value`. This ensures `equals` comparison works on the first dependency change. Avoid subscribing to expensive computeds unless you need their value.\n\n### Diamonds recompute each path once per write\n\nA computed with dependents refreshes eagerly during propagation, so effects never re-run for unchanged derived values. The tradeoff: in a diamond (`a → b, c → d`), one write to `a` recomputes both `b` and `c` and then `d` once per changed path. Values a consumer observes are always consistent: a dependent reading a stale intermediate pulls the fresh value on read, so no glitched state is ever visible. Prefer keeping diamond depth shallow when a derivation is expensive.\n\n### Computed first-run failure is recoverable\n\nIf a computed's `derive` throws on its first run (e.g., a source is `null`), the computed commits the partial dependencies it tracked before the throw. When a dependency changes and the derivation can succeed, the computed refreshes and notifies its dependents. Effects that read a failing computed report the error through `onError` and re-run when the computed recovers.\n\n## Best Practices\n\n- Create one graph per ownership boundary.\n- Keep computed callbacks pure.\n- Return cleanup from effects.\n- Dispose test and feature graphs.\n- Batch related synchronous writes.\n- Use `watch()` only for selected source transitions; callback-only reads are untracked.\n- Dispose external-source bridges when their owner ends.\n- Route runtime failures with `onError` and observe async failures through resource state.\n",
7
7
  "examples": "---\ntitle: 'Ripple: Examples'\ndescription: Practical Ripple recipes.\n---\n\n## Examples\n\n- [Reactive Counter](./examples/reactive-counter.md)\n- [Batch and Untrack](./examples/batch-and-untrack.md)\n- [Scope Ownership](./examples/scope-ownership.md)\n- [Watch Selected Value](./examples/watch-selected-value.md)\n- [Async Resource](./examples/async-resource.md)\n- [Immutable State](./examples/immutable-store.md)\n- [Isolated Graph](./examples/isolated-runtime.md)\n"
8
8
  },