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

@yessglory/generative-mui-core

v1.0.1

Published

Framework-neutral A2UI v0.9.1 core: message/catalog Zod schemas, JSONL parser, JSON-Pointer binding, and a deterministic SurfaceStore. React-free.

Readme

generative-mui

Let the LLM build the UI — not you.

Write a sentence — get real, working Material UI, in your own theme.

A renderer that maps Google's A2UI (v0.9.1) protocol onto Material UI. The agent emits A2UI — not a nested DOM, but a flat list of components referenced by id plus a separate data model bound by JSON-Pointer. The library turns it into real components inside your app's existing MUI theme.

generative-mui demo

None of the interfaces above were hand-written — the model was prompted, emitted A2UI, and the library rendered it as MUI.

If the idea grabs you, drop the repo a star — it's the biggest boost for a project that takes generative UI seriously.


At a glance

Chatbots return text; but a chart, a table, or a validated form often says more than a sentence. Until now the options were weak: either hand-write a component for every scenario, or trust the model's raw HTML and accept XSS and theme drift.

generative-mui opens a third path. The model emits data, not code: A2UI, a flat, schema-validated, id-referenced UI description. The library takes that description and turns it into real MUI components inside the host's <ThemeProvider>. No unregistered type is ever rendered; sx/style/className from the agent never reach MUI.

Describe → the LLM emits A2UI → deterministic store → real Material UI.


What does A2UI look like? (a concrete example)

The model emits server→client messages streamed as JSON Lines. Here's a small, two-way-bound, validated form:

{"createSurface":{"surfaceId":"s1"}}
{"updateComponents":{"surfaceId":"s1","components":[
  {"id":"root","component":"Column","children":["title","name","submit"]},
  {"id":"title","component":"Text","text":"Reservation","variant":"h4"},
  {"id":"name","component":"TextField","label":"Full name","value":{"path":"/name"}},
  {"id":"submitLabel","component":"Text","text":"Confirm"},
  {"id":"submit","component":"Button","child":"submitLabel",
    "action":{"event":{"name":"confirm"}},
    "checks":[{"condition":{"call":"required","args":{"value":{"path":"/name"}}},
               "message":"Name is required"}]}
]}}
{"updateDataModel":{"surfaceId":"s1","path":"/name","value":""}}

Things to notice:

  • Adjacency list, not a tree. root references its children by id. To update one component you don't resend the whole tree — you just put that id back through updateComponents (merge-by-id).
  • UI and data are separate. TextField.value isn't a literal, it's a {"path":"/name"} binding. As the user types, the store's data model updates; the required inside Button.checks reads that path and disables the button while it's empty.
  • No colors/CSS. The model only states semantics (variant:"h4"); pixels and palette come from the host theme.

How it works — the internals

The pipeline runs through four pure layers; the first three know nothing about React/MUI:

JSONL ─parse─▶ messages ─reduce─▶ SurfaceStore ─resolve─▶ React/MUI tree
             (Zod safeParse)     (deterministic)         (registry + skeleton)

1 · Message protocol — 4 types. createSurface · updateComponents (add/replace by id) · updateDataModel (set/delete a JSON-Pointer path) · deleteSurface. Components are stored loosely ({id, component, ...passthrough}); full per-prop validation happens lazily at render — so a truncated stream or an unknown custom component degrades to a fallback instead of rejecting the whole batch.

2 · Deterministic reducer. The core reduce(state, message) → state is a pure function: it never mutates its input and silently ignores invalid/unknown messages (streaming tolerance). The same message sequence always yields the same state — testable, and it runs on SSR/edge.

3 · SurfaceStore — live, subscribable. Holds immutable state and is built for React's useSyncExternalStore: getState() returns a stable reference until the next change (no needless renders). Key methods:

  • apply(msg | msg[] | jsonl) — feed the stream; skips invalid lines.
  • writeLocal(surfaceId, pointer, value) — updates the data model without a server round-trip when an input changes; this is the engine of two-way binding.
  • compact(surfaceId) — garbage-collects components unreachable from root via BFS (merge-by-id never deletes, so ids accumulate). It's a no-op while the root hasn't arrived yet, so it can't drop not-yet-attached components.

4 · Value resolution. Every catalog prop is one of three things: a literal, a {path} binding, or a FunctionCall. resolveValue collapses all three against the data model in one place; a FunctionCall has its args resolved recursively then runs against local functions (required, regex). The same mechanism powers both dynamic props and Button checks: a check whose condition is falsy disables the button and surfaces the first error message as helper text.


Streaming, resilience, and security

Agent output is untrusted and may arrive half-formed — the library is built for exactly that:

  • 🌊 Incremental render. Components arrive piece by piece; not-yet-received children show a Skeleton. For string sources there's append-only delta detection: a growing stream never clobbers the user's local edits.
  • 🧱 Graph resilience. An empty or root-less surface renders nothing; a cycle or a chain past MAX_RENDER_DEPTH degrades to a quiet placeholder instead of overflowing the stack; a per-node error boundary means one throwing renderer never unmounts the whole surface.
  • 🔒 ReDoS guard. regex is local and both the pattern and the input have length caps (1000 / 10,000), because both the pattern and the tested text come from the untrusted agent and JS has no synchronous regex timeout — the cap shrinks the ReDoS blast radius without a Worker. Over-long inputs count as a non-match.
  • 🛡️ Safe by construction. A type not in the registry never runs arbitrary code — it falls back safely; fields like sx/style/className never reach MUI even if the agent emits them.

Architecture & boundaries

Two packages, a one-way dependency — and that's not a convention, it's a compile-time rule:

| Package | Role | | ------- | ---- | | @yessglory/generative-mui-core | Framework-neutral A2UI v0.9.1: Zod schemas, JSONL parser, JSON-Pointer (get/set/remove), the deterministic SurfaceStore, value resolution, and the agent tool schema. Imports no React/MUI. | | @yessglory/generative-mui-react | The MUI adapter: A2UI Basic Catalog → Material UI, in a single public component: <A2uiSurface>. |

  • The direction core ← react is one-way and enforced by eslint-plugin-boundaries — core leaking into React/MUI is a lint error. This lets the parser, store, resolution, and tool schema run everywhere (server, edge, RSC, tests).
  • Zod schemas are the single source of truth. The Basic Catalog is a discriminated union on the component field; a drift test keeps these schemas locked to the vendored catalog.json (every component has a member, and no member names one the catalog doesn't define).

Making an agent speak A2UI

core ships provider-agnostic (just data + schema) agent glue:

import { a2uiTools, a2uiExtendedTools, extractJson } from '@yessglory/generative-mui-core'

// An AI-SDK-shaped tool whose parameters ARE the A2UI adjacency list (a Zod schema).
// The model produces a render-ready surface in one shot.
const tools = a2uiExtendedTools()               // Basic + charts/table/display/input
// const { jsonSchema } = a2uiToolDefinition({ catalog: 'extended' })  // raw jsonSchema7

// If the model replied in plain prose, pull the JSON out (tolerates ``` fences / prose):
const surface = extractJson(modelReplyText)

Also, describeCatalog() / A2UI_SHAPE_RULES / A2UI_BEHAVIOR_RULES give you a prompt block derived from the schemas — to embed the catalog into your system message.


Install & use

pnpm add @yessglory/generative-mui-react
# core comes with react
import { A2uiSurface, SurfaceStore, extendedRegistry } from '@yessglory/generative-mui-react'

const store = new SurfaceStore()
store.apply(a2uiJsonlFromYourAgent)   // JSONL / message array / single message

function Chat() {
  return (
    <A2uiSurface
      source={store}                    // live store → streaming + two-way binding
      registry={extendedRegistry}       // enable charts too (optional; @mui/x-charts)
      onAction={(a) => sendToAgent(a)}  // action.event goes back to the agent
    />
  )
}
  • Two-way binding: inputs bound to {"path":"/field"} read/write the data model.
  • Actions: action.eventonAction({ surfaceId, name, context }); action.functionCall (e.g. openUrl) runs locally.
  • Host-overridable: registry={{ MyType: MyRenderer }} extends or replaces any mapping.
  • Theme: it has no theme of its own; it inherits the host palette/typography — switch the theme and the UI re-skins top to bottom.

Catalog → MUI

All 18 Basic-Catalog components map one-to-one:

| A2UI | MUI | A2UI | MUI | | ---- | --- | ---- | --- | | Text | Typography | Button | Button (+ checks→disabled) | | Image | Box img | TextField | TextField | | Row/Column | Stack | CheckBox | Checkbox | | List | List | ChoicePicker | RadioGroup / checkbox group | | Card | Card | Slider | Slider | | Tabs | Tabs | DateTimeInput | TextField[type=date] | | Divider | Divider | Modal | Dialog |

Extended Catalog (opt-in, extendedRegistry): LineChart · BarChart · PieChart · ScatterChart · SparkLineChart · Gauge · Table · Alert · Chip · Badge · Accordion · Progress · Stepper · Switch · Rating · Autocomplete. The schemas live in the React-free core (so validation works everywhere); only the renderers are opt-in, so the @mui/x-charts dependency stays optional. Use a2uiExtendedTools() to have the agent emit these too.


Add your own component

Overriding an existing type just needs registry. For a genuinely new type, registerComponent gives you schema + renderer in one call — without editing the core:

import { A2uiSurface, registerComponent, useResolved } from '@yessglory/generative-mui-react'
import { z } from 'zod'

// A brand-new "StarBar" type that shows a score with ★
const StarBar = registerComponent(
  'StarBar',
  { value: z.union([z.number(), z.object({ path: z.string() })]), max: z.number().optional() },
  ({ node }) => {
    const value = Number(useResolved(node.value)) || 0     // literal or {path} binding
    return <span>{'★'.repeat(value).padEnd(node.max ?? 5, '☆')}</span>
  },
)

<A2uiSurface source={store} components={[StarBar]} />

The schema is enforced before the renderer runs, so a custom type is as safe as a built-in one (invalid props → skeleton, never a throw). For more — overriding renderers, adding permanently to the core catalog, and advertising the type to the agent's tool schema → docs/ (overriding renderers · new types).


Examples

The same generative-UI chatbot, in two frameworks:

| Example | Stack | Gemini call | | ------- | ----- | ----------- | | examples/gemini-chat | Vite + React SPA | in the browser (demo) | | examples/gemini-chat-next | Next.js App Router | in a server Route Handler (the key never ships to the client) |

pnpm --filter @yessglory/example-gemini-chat dev        # Vite
pnpm --filter @yessglory/example-gemini-chat-next dev   # Next.js

Both run out of the box in demo mode, no API key needed.


Development

pnpm install
pnpm test        # vitest (all packages)
pnpm typecheck   # tsc --noEmit per package
pnpm build       # tsup per package
pnpm lint        # eslint + boundaries

Test surfacecore: JSON-Pointer round-trip (property-tested), SurfaceStore determinism + incremental/JSONL replay, drift against catalog.json, and the agent tool schema. react: render, two-way binding, required-check disabling, action dispatch, streaming skeletons, and zero axe violations.


Like it?

generative-mui takes seriously that LLMs shouldn't just talk — they should build validated, safe, themed UI. If you like the idea ⭐ star it, watch it, try it — your feedback steers where it goes.

built by yessGlory17