npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cool-ai/beach-a2ui-static

v0.1.2

Published

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

Readme

@cool-ai/beach-a2ui-static

Synchronous, 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.

Companion to @cool-ai/beach-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.

Why a separate package

The 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) that need only the static path don't pull a DOM emulator into their dependency tree.

The 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.

Quick start

import { renderToStaticHTML, renderToStaticText, assertEmailSafe } from '@cool-ai/beach-a2ui-static';

const commands = [
  { version: 'v0.9', createSurface: { surfaceId: 's', catalogId: 'a2ui-basic' } },
  {
    version: 'v0.9',
    updateComponents: {
      surfaceId: 's',
      components: [
        { id: 'root', component: 'Card', child: 'body' },
        { id: 'body', component: 'Text', text: 'Hello.' },
      ],
    },
  },
];

const html = renderToStaticHTML(commands);
const text = renderToStaticText(commands);

assertEmailSafe(html); // throws A2uiEmailSafetyError if anything would break in Gmail / Outlook

Both functions are synchronous. No async wrapping, no jsdom bootstrap, no Lit lifecycle waits.

The channel-safety contract

renderToStaticHTML output passes assertEmailSafe(html) by construction. The helper enforces five rules — the intersection of what Gmail, Yahoo, and Outlook all render:

  1. No custom-element tags. <a2ui-…> is stripped by every major client.
  2. No <style> blocks. Outlook routinely strips them; styles must be inline.
  3. No JavaScript. No <script> tags; no on…= event-handler attributes.
  4. No flex / grid CSS. Layout in email is table-based.
  5. No external stylesheet references. No <link rel="stylesheet">.

Consumers writing their own composers can run their output through the same helper to keep them honest.

Data-binding resolution

DynamicString / DynamicBoolean / DynamicValue props resolve at compose time:

  • Literal values pass through unchanged.
  • { 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.
  • { 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.

Consumer-catalogue components via extensions

Components 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.

import { renderToStaticHTML, type HtmlExtension } from '@cool-ai/beach-a2ui-static';

const destinationCard: HtmlExtension = (props, renderedChildren) => `
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border:1px solid #e5e5e5;">
    <tr><td style="padding:16px;">
      <strong>${props.destinationName as string}</strong>
      ${renderedChildren}
    </td></tr>
  </table>
`;

const html = renderToStaticHTML(commands, { extensions: { DestinationCard: destinationCard } });

Returning the empty string drops the component from the output.

Built-in basic-catalogue coverage

| Component | HTML emission | Text emission | |---|---|---| | Card | Table-wrapped cell with inline border / padding / background | Inner text trimmed | | Column / List | Vertical table, one row per child | Newline-separated children | | Row | Horizontal table, one cell per child | Space-separated children | | Text | Variant-aware tag (h1h5, small, p) with inline font sizing | Headings uppercased (h1/h2) or set off (h3-h5); body passes through | | Image | <img> with width per variant; alt from description | [image: <description>] | | Icon | <span aria-label> fallback | Description text | | Divider | <hr> (or thin vertical pipe) | \n---\n | | Button | Styled <span> wrapping child label (no action) | [ label ] brackets | | Tabs | Sequential sections, every tab rendered with title heading | Every tab uppercased + content | | Modal | Emits content only (drops trigger) | Same | | Show | Emits child iff when resolves truthy at compose time | Same | | Video / AudioPlayer | Labelled link fallback (URL anchor) | <label> (<url>) | | TextField / CheckBox / ChoicePicker / Slider / DateTimeInput | Dropped — no form interactivity in batched channels | Same |

Template-repeat children

Layout components (Row / Column / List) accept a static list of component ids or a template-repeat:

{
  id: 'root',
  component: 'Column',
  children: { componentId: 'item-template', path: '/destinations' },
}

The 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.

Error types

  • A2uiStaticRenderError (base) — code: string, every static-render error carries one.
  • A2uiStaticRenderFunctionCallErrorcomponentName, prop, call. Thrown when a binding asks for a function call.
  • A2uiStaticRenderMissingComponentErrorsurfaceId, parentComponent, parentProp, missingId. Thrown when a child / content / tabs[].child reference points at a component id the surface's components map doesn't have.
  • A2uiEmailSafetyErrorviolations: ReadonlyArray<EmailSafetyViolation>. Thrown by assertEmailSafe when HTML violates the channel-safety contract.

Tests

44 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].

pnpm --filter @cool-ai/beach-a2ui-static test

Related

  • @cool-ai/beach-a2ui — Lit-based browser-shaped renderer + the bridge / host-fit conventions.
  • @cool-ai/beach-format — Composer primitives; reaches for the static renderer when emitting per-channel output.
  • CAIB-249 — the CR that introduced this package.
  • CAIB-250 — Composer/A2UI consolidation; migrates the format-adapter render paths onto the static renderer.