Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
089c89a
feat(generative-ui): implement Open Generative UI draft 0.1 POC and s…
jherr Oct 14, 2026
58847c1
fix(generative-ui): repair rendering + interactivity, elevate design,…
jherr Oct 14, 2026
6a4f75d
feat(generative-ui): inline-first chat surface, revise-in-place, live…
jherr Oct 15, 2026
095f6d8
feat(generative-ui): host-portable token renderer, shadcn/expand, Wid…
jherr Oct 16, 2026
7e545e3
feat(generative-ui): Tier F (Frame) core β€” store, registry, shell, br…
jherr Oct 16, 2026
917d032
feat(generative-ui): Tier F serving + example β€” browser-verified fram…
jherr Oct 16, 2026
c69f18d
feat(generative-ui): generativeUiArtifacts chat middleware + async ar…
jherr Oct 16, 2026
dbc61e5
feat(generative-ui): artifact data field + Table bounded-repair for l…
jherr Oct 16, 2026
eba363d
feat(generative-ui example): migrate chat to chat() + generativeUiArt…
jherr Oct 16, 2026
c57462a
feat(generative-ui): ui:// resource identity + MCP Apps ui-resource e…
jherr Oct 16, 2026
de7402c
feat(generative-ui example): render html-fragment artifacts via the S…
jherr Oct 16, 2026
a273d30
feat(generative-ui): MCP-Apps-native html-fragments (ext-apps guest a…
jherr Oct 16, 2026
54dc835
feat(generative-ui): MCP server adapter β€” generative UI over MCP for …
jherr Oct 17, 2026
0b4d9a4
feat(generative-ui): react-tsx MCP-Apps-native via ext-apps AppBridge…
jherr Oct 17, 2026
cedde6d
docs(generative-ui): MCP Apps convergence findings
jherr Oct 17, 2026
b30445b
feat(generative-ui): live data sources for Tier F iframe artifacts
jherr Oct 17, 2026
b50f0ef
chore(generative-ui): CI prep β€” changeset, concurrently alignment, dr…
jherr Oct 21, 2026
d6b8176
ci: apply automated fixes
autofix-ci[bot] Oct 21, 2026
7105f69
docs(generative-ui): add Generative UI guide (overview, artifacts, li…
jherr Oct 21, 2026
acbe624
test(generative-ui): E2E for live data, and render live fixtures in t…
jherr Oct 21, 2026
5669c0c
Merge commit 'fa2a291f5526f6dec9a91fe5b71de15dfaf59ec7' into HEAD
AlemTuzlak Oct 22, 2026
73b2f3b
Merge remote-tracking branch 'origin/main' into HEAD
AlemTuzlak Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/generative-ui-live-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@tanstack/ai-generative-ui': minor
---

Live data sources for Tier F iframe artifacts.

A model can bind an artifact to a host-registered streaming data source via
`create_artifact`'s new `dataSource: { name, params }` field; the host subscribes
on mount and pushes each value into the sandboxed frame, where `useHostData()`
re-renders in place β€” real-time updates without regenerating the artifact.

- New `DataSourceRegistry` + `describeDataSourcesForPrompt` (`@tanstack/ai-generative-ui`),
the streaming sibling of `ComputeRegistry`: the host registers named
`subscribe(params, emit) => unsubscribe` sources, surfaced to the model in the
system prompt the same way compute functions are.
- The binding persists on `ArtifactRecord.dataSource` (threaded through create and
revise) and is emitted on the `genui:artifact` pin event.
- `FrameArtifactRenderer` gains a `subscribeData` prop: subscribe on mount, push
each emission over the active bridge, unsubscribe on unmount; it also re-pushes
the static `data` prop when it changes (previously a one-shot snapshot).
- Live push uses the built-in `ogui/1` bridge (`host.data` β†’ `useHostData`), which
delivers reliably into a sandboxed opaque-origin iframe. The MCP Apps standards
path still renders static artifacts.

Live values stay host-authoritative, like `data` β€” the model renders what arrives,
it does not compute it.
22 changes: 22 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,28 @@
}
]
},
{
"label": "Generative UI",
"tab": "guides",
"children": [
{
"label": "Overview",
"to": "generative-ui/index",
"tab": "guides",
"addedAt": "2026-10-20"
},
{
"label": "Artifacts",
"to": "generative-ui/artifacts",
"addedAt": "2026-10-20"
},
{
"label": "Live data",
"to": "generative-ui/live-data",
"addedAt": "2026-10-20"
}
]
},
{
"label": "Chat UI Recipes",
"tab": "guides",
Expand Down
145 changes: 145 additions & 0 deletions docs/generative-ui/artifacts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
title: Artifacts
id: generative-ui-artifacts
order: 2
description: "Give the model the create_artifact tools with a chat middleware, then render the stored artifact inline in the conversation. Covers the server store and builder, and the client renderer."
keywords:
- tanstack ai
- generative ui
- generativeUiArtifacts
- create_artifact
- FrameArtifactStore
- makeReactBuilder
- FrameArtifactRenderer
---

You want the model to build a working UI during the chat, not only describe one. The model needs tools to create and store a UI, and your app needs to render what it made. This page wires both halves.

## Server: give the model the tools

Add the `generativeUiArtifacts` middleware to your `chat()` call. It injects four server-run tools the model can use mid-conversation: `create_artifact`, `revise_artifact`, `list_artifacts`, and `read_artifact`.

The middleware needs three things from you:

- **`store`** β€” where artifacts live. Use the in-memory store to start, or a blob-backed store to persist them.
- **`builder`** β€” turns a `react-tsx` source into a bundle. An `html-fragment` needs no build.
- **`registry`** β€” the modules a `react-tsx` artifact may import. `REFERENCE_REGISTRY` is a ready set.

```ts ignore
// src/routes/api.chat.ts (TanStack Start)
import { createFileRoute } from '@tanstack/react-router'
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { REFERENCE_REGISTRY, makeReactBuilder } from '@tanstack/ai-generative-ui'
import {
generativeUiArtifacts,
createInMemoryFrameArtifactStore,
} from '@tanstack/ai-generative-ui/artifacts'
import { transformWithEsbuild } from 'vite'

const store = createInMemoryFrameArtifactStore()

// Build a react-tsx source to ESM. Imports stay bare for the host import map.
const builder = makeReactBuilder(async ({ source }) => {
const out = await transformWithEsbuild(source, 'artifact.tsx', {
loader: 'tsx',
jsx: 'transform',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
format: 'esm',
})
return { code: out.code }
})

export const Route = createFileRoute('/api/chat')({
server: {
handlers: {
POST: async ({ request }) => {
const { messages } = await request.json()

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
middleware: [
generativeUiArtifacts({ store, builder, registry: REFERENCE_REGISTRY }),
],
})

return toServerSentEventsResponse(stream)
},
},
},
})
```

On a successful create or revise, the middleware emits a `genui:artifact` custom event that carries the pinned `{ artifactId, revision }`. The client uses that pin to render the frame. A build failure comes back as the tool result, so the model reads the compiler errors and tries again inside the same run.

### Persist artifacts

The in-memory store is gone on restart. For real storage, back it with a `BlobStore` from `@tanstack/ai-persistence`:

```ts
import { memoryPersistence } from '@tanstack/ai-persistence'
import { createBlobArtifactStore } from '@tanstack/ai-generative-ui/artifacts'

const store = createBlobArtifactStore(memoryPersistence().stores.blobs)
```

Swap the blob store for an S3, filesystem, or database implementation with no other change.

## Client: render the artifact inline

The client listens for the `genui:artifact` event to learn the pin, then renders the frame with `FrameArtifactRenderer`. The renderer mounts a sandboxed iframe and asks your host for the artifact's shell document by URL.

```tsx ignore
import { useState } from 'react'
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
import { FrameArtifactRenderer, type ArtifactPin } from '@tanstack/ai-generative-ui'

const sandboxUrl = (pin: ArtifactPin, instanceId: string) =>
`/sandbox/${pin.artifactId}/${pin.revision}?i=${encodeURIComponent(instanceId)}`

export function Chat() {
// toolCallId -> pin, filled by the genui:artifact event.
const [pins, setPins] = useState<Record<string, ArtifactPin>>({})

const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents('/api/chat'),
onCustomEvent: (type, data, ctx) => {
if (type === 'genui:artifact' && ctx.toolCallId && data?.pin) {
setPins((p) => ({ ...p, [ctx.toolCallId]: data.pin }))
}
},
})

return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.parts.map((part, i) => {
if (part.type === 'text') return <p key={i}>{part.content}</p>
if (part.type === 'tool-call' && pins[part.id]) {
return (
<FrameArtifactRenderer
key={i}
pin={pins[part.id]}
sandboxUrl={sandboxUrl}
/>
)
}
return null
})}
</div>
))}
</div>
)
}
```

### Serve the sandbox shell

`FrameArtifactRenderer` loads the artifact from the `sandboxUrl` you give it. Your host serves that URL. The host reads the record from the same `store`, builds the shell document with `buildShellDocument`, and returns it with a strict content security policy.

The shell, the import map, and the theme are host code. The model never writes them. This serving layer is host-specific, so the [`examples/generative-ui`](https://github.com/TanStack/ai/tree/main/examples/generative-ui) app shows a complete `/sandbox/:id/:rev` route, the runtime modules, and a dedicated sandbox origin.

Next: make a view update on its own with [live data](./live-data).
57 changes: 57 additions & 0 deletions docs/generative-ui/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Generative UI
id: generative-ui
order: 1
description: "Let the model author interactive UI during a chat() run. The host builds, stores, and renders it inline in a sandboxed iframe β€” a chord diagram, a calculator, a live data monitor."
keywords:
- tanstack ai
- generative ui
- artifacts
- create_artifact
- sandboxed iframe
- react-tsx
- html-fragment
- FrameArtifactRenderer
- live data
---

A chat reply is usually text. Sometimes the real answer is a thing the user touches: a loan calculator they can tweak, a guitar chord they can inspect, a spend monitor that keeps ticking. Describing that in prose is weak. Rendering it is strong.

**`@tanstack/ai-generative-ui`** lets the model author a UI during a `chat()` run. The model calls a tool with the source for a small component. The host builds it, stores it, and renders it inline in a sandboxed iframe, as part of the assistant's message.

```ts ignore
import { chat } from '@tanstack/ai'
import { generativeUiArtifacts } from '@tanstack/ai-generative-ui/artifacts'

const stream = chat({
adapter,
messages,
middleware: [generativeUiArtifacts({ store, builder, registry })],
})
```

> **Experimental.** This package is a prototype of the Open Generative UI design. The API can change between releases.

## The artifact model

An artifact is one stored, versioned UI. The model authors only the source. Everything structural (the iframe shell, the bridge, the theme, the build) is host code, so a security or theme fix reaches every stored artifact without regeneration.

There are two kinds:

- **`html-fragment`** β€” markup plus an optional `<style>` and `<script>`. Best for hand-tuned figures, like an SVG chord diagram.
- **`react-tsx`** β€” one TSX module whose default export is a component. It may import only host-approved modules (a React table, a chart library, host UI primitives). The host builds it at write time.

Numbers the model presents as computed come from host data, not from model arithmetic. The model renders what the host gives it.

## How it fits with MCP Apps

Generative UI is built on [MCP Apps](../mcp/apps). An artifact addresses as a `ui://` resource with the `text/html;profile=mcp-app` type, so an external MCP host can render the same UI. In your own app, `html-fragment` artifacts render through the same `MCPAppResource` component that renders external MCP server widgets.

The difference: MCP Apps renders widgets a server already wrote. Generative UI lets the model write the widget during the conversation, and the host stores and serves it.

## Where to go next

- [Artifacts](./artifacts) β€” give the model the tools, then render the result inline. Both the server and the client side.
- [Live data](./live-data) β€” bind an artifact to a host data source so the view updates in real time.

The [`examples/generative-ui`](https://github.com/TanStack/ai/tree/main/examples/generative-ui) app is the reference host. It wires the store, the builder, the sandbox serving route, and the chat surface end to end.
Loading
Loading