{"_id":"@cool-ai/beach-a2ui-static","_rev":"4-80cf6e0ab2cf59803d46f23ce7cdad7e","name":"@cool-ai/beach-a2ui-static","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@cool-ai/beach-a2ui-static","version":"0.1.0","license":"Apache-2.0","_id":"@cool-ai/beach-a2ui-static@0.1.0","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"364de07e492e6e0f176700cfce0809a749464dd6","tarball":"https://registry.npmjs.org/@cool-ai/beach-a2ui-static/-/beach-a2ui-static-0.1.0.tgz","fileCount":51,"integrity":"sha512-qOD/urSNpIEjptBTD3g6Mgmjus4+OTAb89bDovLP6fZfHOs+8P2mLU/e8Y93J5wd46oP5ooqImL0xAyozM3iLg==","signatures":[{"sig":"MEUCIQDiueCzTDipzltbYxYBNVd9xx03qXpE2+H9DImPIx36QgIgMbvLH1axQhmmoYkKhenBkT1BEsq7wUHL4bGXnFOLYaI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129318},"type":"module","_from":"file:cool-ai-beach-a2ui-static-0.1.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/dc2a2de3ea9db4daf23b86c71a64112b/cool-ai-beach-a2ui-static-0.1.0.tgz","_integrity":"sha512-qOD/urSNpIEjptBTD3g6Mgmjus4+OTAb89bDovLP6fZfHOs+8P2mLU/e8Y93J5wd46oP5ooqImL0xAyozM3iLg==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/a2ui-static"},"_npmVersion":"10.9.8","description":"Synchronous Lit-free static renderer for A2UI v0.9 SurfaceCommand streams. Emits email-client-safe HTML and structured plain text for batched non-browser channels (email, WhatsApp, archive). Zero heavy dependencies; companion to @cool-ai/beach-a2ui's brow","directories":{},"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.5.0","@a2ui/web_core":"^0.9.2"},"peerDependencies":{"@a2ui/web_core":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/beach-a2ui-static_0.1.0_1779188302219_0.513093129804258","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @cool-ai/beach-a2ui-renderer-static."},"0.1.1":{"name":"@cool-ai/beach-a2ui-static","version":"0.1.1","license":"Apache-2.0","_id":"@cool-ai/beach-a2ui-static@0.1.1","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"6bc0547ba810c14edfc2f9ec64368b9905b49d05","tarball":"https://registry.npmjs.org/@cool-ai/beach-a2ui-static/-/beach-a2ui-static-0.1.1.tgz","fileCount":51,"integrity":"sha512-zcRj4mAMo6gVQ5fQKNWRBW0eyn1lNc3AWf6STLJlycMQ5QArTQWid2M7A5n9+5EYNtFJuaWThVg4l0nIItiFUA==","signatures":[{"sig":"MEYCIQCamV6pXUxfxTkYdKZ+gZ3OH+UhC9Ua71eEZhyoV0znLgIhANLNdMZcNbu20zV97mr+qWHT4BgOin8KZroit6G9T9r8","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134447},"type":"module","_from":"file:cool-ai-beach-a2ui-static-0.1.1.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/a7ce3835ff4107d3d9e8ee6cc5a19804/cool-ai-beach-a2ui-static-0.1.1.tgz","_integrity":"sha512-zcRj4mAMo6gVQ5fQKNWRBW0eyn1lNc3AWf6STLJlycMQ5QArTQWid2M7A5n9+5EYNtFJuaWThVg4l0nIItiFUA==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/a2ui-static"},"_npmVersion":"10.9.8","description":"Synchronous Lit-free static renderer for A2UI v0.9 SurfaceCommand streams. Emits email-client-safe HTML and structured plain text for batched non-browser channels (email, WhatsApp, archive). Zero heavy dependencies; companion to @cool-ai/beach-a2ui's brow","directories":{},"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.5.0","@a2ui/web_core":"^0.9.2"},"peerDependencies":{"@a2ui/web_core":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/beach-a2ui-static_0.1.1_1779273702031_0.8738471539604078","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @cool-ai/beach-a2ui-renderer-static."},"0.1.2":{"name":"@cool-ai/beach-a2ui-static","version":"0.1.2","license":"Apache-2.0","_id":"@cool-ai/beach-a2ui-static@0.1.2","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"c18191fe1c2a17d3f933bb57e44e83ade9ad8755","tarball":"https://registry.npmjs.org/@cool-ai/beach-a2ui-static/-/beach-a2ui-static-0.1.2.tgz","fileCount":67,"integrity":"sha512-FAhiaKmTrrbQRtDcZqPdA9p2FkMe3a+SnlykY8eUngMQHYcblzdxox+wkPOYwOKZWPXSquMMIVCACegSXl1BYg==","signatures":[{"sig":"MEQCIBHhnMA5pkEpu69Z/zm32iX6/ayaY0+ZVjsSx1NCfSY2AiAP4m1SVciCgfEBZPF9hb9OCSN3vnqbqZ338N8uwVN8Qg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":432970},"type":"module","_from":"file:cool-ai-beach-a2ui-static-0.1.2.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/3783919ef5243ee16d8febf6cfd42eb2/cool-ai-beach-a2ui-static-0.1.2.tgz","_integrity":"sha512-FAhiaKmTrrbQRtDcZqPdA9p2FkMe3a+SnlykY8eUngMQHYcblzdxox+wkPOYwOKZWPXSquMMIVCACegSXl1BYg==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/a2ui-static"},"_npmVersion":"10.9.8","description":"Synchronous Lit-free static renderer for A2UI v0.9 SurfaceCommand streams. Emits email-client-safe HTML and structured plain text for batched non-browser channels (email, WhatsApp, archive). Zero heavy dependencies; companion to @cool-ai/beach-a2ui's brow","directories":{},"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.5.0","@a2ui/web_core":"^0.9.2"},"peerDependencies":{"@a2ui/web_core":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/beach-a2ui-static_0.1.2_1779381726092_0.7144771226209587","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @cool-ai/beach-a2ui-renderer-static."}},"time":{"created":"2026-05-19T10:58:22.158Z","modified":"2026-06-06T08:01:17.513Z","0.1.0":"2026-05-19T10:58:22.429Z","0.1.1":"2026-05-20T10:41:42.204Z","0.1.2":"2026-05-21T16:42:06.261Z"},"bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"license":"Apache-2.0","homepage":"https://cool-ai.org","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/a2ui-static"},"description":"Synchronous Lit-free static renderer for A2UI v0.9 SurfaceCommand streams. Emits email-client-safe HTML and structured plain text for batched non-browser channels (email, WhatsApp, archive). Zero heavy dependencies; companion to @cool-ai/beach-a2ui's brow","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"readme":"# @cool-ai/beach-a2ui-static\n\nSynchronous, Lit-free renderer for A2UI v0.9 `SurfaceCommand` streams. Emits email-client-safe HTML and structured plain text for batched non-browser channels — outbound email, WhatsApp summary, archive renders, the `plainText` alternative on multi-part MIME bodies.\n\nCompanion to [`@cool-ai/beach-a2ui`](../a2ui), which keeps the Lit-based renderer for the browser-shaped path under jsdom. Use this package when the consumer never reaches a browser; use `@cool-ai/beach-a2ui` when the same surface that drives a browser session also needs a server render of the live state.\n\n## Why a separate package\n\nThe Lit-based `@cool-ai/beach-a2ui` ships `jsdom` (~25 MB) and `juice` as runtime dependencies. Beach's static walker uses neither — it's pure data. Splitting the package keeps the install surface honest: consumers (e.g. [`@cool-ai/beach-format`](../format)) that need only the static path don't pull a DOM emulator into their dependency tree.\n\nThe same names remain importable from `@cool-ai/beach-a2ui/render-server` for backwards compatibility; new consumers should import from `@cool-ai/beach-a2ui-static` directly.\n\n## Quick start\n\n```typescript\nimport { renderToStaticHTML, renderToStaticText, assertEmailSafe } from '@cool-ai/beach-a2ui-static';\n\nconst commands = [\n  { version: 'v0.9', createSurface: { surfaceId: 's', catalogId: 'a2ui-basic' } },\n  {\n    version: 'v0.9',\n    updateComponents: {\n      surfaceId: 's',\n      components: [\n        { id: 'root', component: 'Card', child: 'body' },\n        { id: 'body', component: 'Text', text: 'Hello.' },\n      ],\n    },\n  },\n];\n\nconst html = renderToStaticHTML(commands);\nconst text = renderToStaticText(commands);\n\nassertEmailSafe(html); // throws A2uiEmailSafetyError if anything would break in Gmail / Outlook\n```\n\nBoth functions are synchronous. No async wrapping, no jsdom bootstrap, no Lit lifecycle waits.\n\n## The channel-safety contract\n\n`renderToStaticHTML` output passes `assertEmailSafe(html)` by construction. The helper enforces five rules — the intersection of what Gmail, Yahoo, and Outlook all render:\n\n1. **No custom-element tags.** `<a2ui-…>` is stripped by every major client.\n2. **No `<style>` blocks.** Outlook routinely strips them; styles must be inline.\n3. **No JavaScript.** No `<script>` tags; no `on…=` event-handler attributes.\n4. **No flex / grid CSS.** Layout in email is table-based.\n5. **No external stylesheet references.** No `<link rel=\"stylesheet\">`.\n\nConsumers writing their own composers can run their output through the same helper to keep them honest.\n\n## Data-binding resolution\n\n`DynamicString` / `DynamicBoolean` / `DynamicValue` props resolve at compose time:\n\n- **Literal values** pass through unchanged.\n- **`{ path }` bindings** read from the surface's data model via RFC-6901 JSON-Pointer. The data model is accumulated from `updateDataModel` commands in the stream.\n- **`{ call, args, returnType }` function-call bindings** throw `A2uiStaticRenderFunctionCallError`. The live runtime in the browser owns function-call evaluation; the static walker has no client runtime. Resolve such props server-side before composing, or persist their result into the data model via `updateDataModel`.\n\n## Consumer-catalogue components via `extensions`\n\nComponents outside the basic catalogue plug in via `options.extensions`. Each extension receives **resolved props** (data-bindings already evaluated) plus the renderer's **pre-rendered children string**, so the extension wraps the inner content in domain-specific markup without re-implementing basic-catalogue emission.\n\n```typescript\nimport { renderToStaticHTML, type HtmlExtension } from '@cool-ai/beach-a2ui-static';\n\nconst destinationCard: HtmlExtension = (props, renderedChildren) => `\n  <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border:1px solid #e5e5e5;\">\n    <tr><td style=\"padding:16px;\">\n      <strong>${props.destinationName as string}</strong>\n      ${renderedChildren}\n    </td></tr>\n  </table>\n`;\n\nconst html = renderToStaticHTML(commands, { extensions: { DestinationCard: destinationCard } });\n```\n\nReturning the empty string drops the component from the output.\n\n## Built-in basic-catalogue coverage\n\n| Component | HTML emission | Text emission |\n|---|---|---|\n| `Card` | Table-wrapped cell with inline border / padding / background | Inner text trimmed |\n| `Column` / `List` | Vertical table, one row per child | Newline-separated children |\n| `Row` | Horizontal table, one cell per child | Space-separated children |\n| `Text` | Variant-aware tag (`h1`–`h5`, `small`, `p`) with inline font sizing | Headings uppercased (h1/h2) or set off (h3-h5); body passes through |\n| `Image` | `<img>` with width per variant; alt from `description` | `[image: <description>]` |\n| `Icon` | `<span aria-label>` fallback | Description text |\n| `Divider` | `<hr>` (or thin vertical pipe) | `\\n---\\n` |\n| `Button` | Styled `<span>` wrapping child label (no action) | `[ label ]` brackets |\n| `Tabs` | Sequential sections, every tab rendered with title heading | Every tab uppercased + content |\n| `Modal` | Emits `content` only (drops `trigger`) | Same |\n| `Show` | Emits child iff `when` resolves truthy at compose time | Same |\n| `Video` / `AudioPlayer` | Labelled link fallback (URL anchor) | `<label> (<url>)` |\n| `TextField` / `CheckBox` / `ChoicePicker` / `Slider` / `DateTimeInput` | Dropped — no form interactivity in batched channels | Same |\n\n## Template-repeat children\n\nLayout components (`Row` / `Column` / `List`) accept a static list of component ids **or** a template-repeat:\n\n```typescript\n{\n  id: 'root',\n  component: 'Column',\n  children: { componentId: 'item-template', path: '/destinations' },\n}\n```\n\nThe renderer reads the array at `path` from the data model and renders `componentId` once per element. Paths inside the template resolve against the **element** as the local data-model root, not the surface root — so the template can carry `{ path: '/name' }` and pull `destinations[i].name` for each iteration.\n\n## Error types\n\n- `A2uiStaticRenderError` (base) — `code: string`, every static-render error carries one.\n- `A2uiStaticRenderFunctionCallError` — `componentName`, `prop`, `call`. Thrown when a binding asks for a function call.\n- `A2uiStaticRenderMissingComponentError` — `surfaceId`, `parentComponent`, `parentProp`, `missingId`. Thrown when a child / `content` / `tabs[].child` reference points at a component id the surface's components map doesn't have.\n- `A2uiEmailSafetyError` — `violations: ReadonlyArray<EmailSafetyViolation>`. Thrown by `assertEmailSafe` when HTML violates the channel-safety contract.\n\n## Tests\n\n44 tests across the static-HTML and static-text paths cover every basic-catalogue component, data-binding resolution, function-call rejection, template-repeat with local data scope, missing-component throw, every extensions code path, all six `assertEmailSafe` rules, and the reference fixture `Card → Column[Text, Text]`.\n\n```bash\npnpm --filter @cool-ai/beach-a2ui-static test\n```\n\n## Related\n\n- [`@cool-ai/beach-a2ui`](../a2ui) — Lit-based browser-shaped renderer + the bridge / host-fit conventions.\n- [`@cool-ai/beach-format`](../format) — Composer primitives; reaches for the static renderer when emitting per-channel output.\n- CAIB-249 — the CR that introduced this package.\n- CAIB-250 — Composer/A2UI consolidation; migrates the format-adapter render paths onto the static renderer.\n","readmeFilename":"README.md"}