{"_id":"@cool-ai/beach-a2ui-renderer-static","name":"@cool-ai/beach-a2ui-renderer-static","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.2":{"name":"@cool-ai/beach-a2ui-renderer-static","version":"0.1.2","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-render","license":"Apache-2.0","repository":{"type":"git","url":"git+https://gitlab.com/johncandrew/beach.git","directory":"packages/beach-a2ui-renderer-static"},"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"type":"module","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"publishConfig":{"access":"public"},"peerDependencies":{"@a2ui/web_core":"^0.9.0"},"devDependencies":{"@a2ui/web_core":"^0.9.2","typescript":"^5.5.0","vitest":"^2.0.0"},"scripts":{"build":"tsc --project tsconfig.json","dev":"tsc --project tsconfig.json --watch","test":"vitest run","coverage":"vitest run --coverage","test:watch":"vitest","typecheck":"tsc --noEmit"},"_id":"@cool-ai/beach-a2ui-renderer-static@0.1.2","_integrity":"sha512-0DncP25XM8X+b4EnwpwWBB7rE1Ul5TmnRD8YnSAdS/ROZjhPO8nBQBhtNxU4WG2g4wSk/YyZHjsa8zdlnGJjUQ==","_resolved":"/tmp/e146bf2d7b84c2b02b9e8a10f29d99cd/cool-ai-beach-a2ui-renderer-static-0.1.2.tgz","_from":"file:cool-ai-beach-a2ui-renderer-static-0.1.2.tgz","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-0DncP25XM8X+b4EnwpwWBB7rE1Ul5TmnRD8YnSAdS/ROZjhPO8nBQBhtNxU4WG2g4wSk/YyZHjsa8zdlnGJjUQ==","shasum":"fec83b4e8dc36270d4fbefafc81adeb4244e9410","tarball":"https://registry.npmjs.org/@cool-ai/beach-a2ui-renderer-static/-/beach-a2ui-renderer-static-0.1.2.tgz","fileCount":67,"unpackedSize":431271,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEGJsVDdCxxEFd9sB6xiwolLF82famQCxKOiP2Dp4EuTAiA9E8Kgo5wGdaA9DgcotSNDQYBnon07YREUkJSmpyjm3Q=="}]},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"directories":{},"maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/beach-a2ui-renderer-static_0.1.2_1780677376143_0.5414728226758083"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-05T16:36:15.973Z","0.1.2":"2026-06-05T16:36:16.277Z","modified":"2026-06-05T16:36:16.515Z"},"maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"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-render","homepage":"https://cool-ai.org","repository":{"type":"git","url":"git+https://gitlab.com/johncandrew/beach.git","directory":"packages/beach-a2ui-renderer-static"},"bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"license":"Apache-2.0","readme":"# @cool-ai/beach-a2ui-renderer-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-renderer-lit`](../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-renderer-lit` 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-renderer-lit` 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-renderer-lit/render-server` for backwards compatibility; new consumers should import from `@cool-ai/beach-a2ui-renderer-static` directly.\n\n## Quick start\n\n```typescript\nimport { renderToStaticHTML, renderToStaticText, assertEmailSafe } from '@cool-ai/beach-a2ui-renderer-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-renderer-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-renderer-static test\n```\n\n## Related\n\n- [`@cool-ai/beach-a2ui-renderer-lit`](../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","_rev":"1-0f53b35a0084d66595a3967a27ce0e73"}