Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2febfb9
docs(ag-ui): correct F3 — stray 'Success' was a truncated delta, real…
blove Jun 12, 2026
0f18eaa
docs(ag-ui): Phase 3 gap-closure implementation plan
blove Jun 12, 2026
b025a2f
fix(examples/ag-ui): set data-color-scheme so light mode reaches the …
blove Jun 12, 2026
f05f0f2
fix(chat): chat-input clears the textarea after submit (F1)
blove Jun 12, 2026
35787c7
fix(chat): restore textarea name attribute + test convention cleanups…
blove Jun 12, 2026
5bf93d3
fix(ag-ui): treat stop()-induced aborts as graceful cancellation (F3)
blove Jun 12, 2026
2edfd31
perf(chat): track markdown children by index, not identity (F6)
blove Jun 12, 2026
34ba007
fix(chat): resolve json-render statePath object values to scalars (F4)
blove Jun 12, 2026
9f8b6b4
fix(chat): isolate json-render stores per surface + seed-effect track…
blove Jun 12, 2026
bab0bc3
fix(cockpit): pass explicit stores to json-render dashboards + pin co…
blove Jun 12, 2026
76f1b44
chore(chat): drop unused @angular/forms peer dependency
blove Jun 12, 2026
777d9ba
fix(ag-ui): also settle abort-shaped RUN_ERROR events on stop (F3)
blove Jun 12, 2026
207726f
docs(ag-ui): Phase 3 closure status — F1-F4 + main F6 closed, residua…
blove Jun 12, 2026
8eac8b2
fix(ag-ui): reset abort flags in regenerate + settle duplicate aborts…
blove Jun 12, 2026
0c60707
chore(docs): regenerate api docs
github-actions[bot] Jun 12, 2026
04b8251
ci: retrigger checks after bot docs regeneration
blove Jun 12, 2026
96e6670
Merge remote-tracking branch 'origin/main' into ag-ui-gap-closure-p3
blove Jun 12, 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
33 changes: 32 additions & 1 deletion apps/website/content/docs/chat/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2153,6 +2153,12 @@
"description": "",
"optional": false
},
{
"name": "normalizedSpec",
"type": "Signal<Spec | null>",
"description": "The bound spec with schema-documented `{ statePath }` prop refs\nrewritten to engine-native `{ $bindState }` + `_bindings` so values\nresolve against the state store instead of interpolating as\n\"[object Object]\" (F4).",
"optional": false
},
{
"name": "registry",
"type": "InputSignal<AngularRegistry | undefined>",
Expand Down Expand Up @@ -2393,6 +2399,19 @@
"description": "",
"params": []
},
{
"name": "onInput",
"signature": "onInput(event: Event): void",
"description": "Sync the textarea's value into the signal on user input. A direct\n [value]/(input) pair is used instead of ngModel: NgModel does not\n reliably write a programmatic clear back to the view under zoneless\n + OnPush, leaving sent text visible in the composer (audit F1).",
"params": [
{
"name": "event",
"type": "Event",
"description": "",
"optional": false
}
]
},
{
"name": "onKeydown",
"signature": "onKeydown(event: KeyboardEvent): void",
Expand Down Expand Up @@ -2782,6 +2801,12 @@
"description": "",
"optional": false
},
{
"name": "clientTools",
"type": "InputSignal<Readonly<Record<string, ClientToolDef>> | undefined>",
"description": "Frontend-declared client tools forwarded to the inner `<chat>`.",
"optional": false
},
{
"name": "closeOnEscape",
"type": "InputSignal<boolean>",
Expand Down Expand Up @@ -3277,6 +3302,12 @@
"description": "",
"optional": false
},
{
"name": "clientTools",
"type": "InputSignal<Readonly<Record<string, ClientToolDef>> | undefined>",
"description": "Frontend-declared client tools forwarded to the inner `<chat>`.",
"optional": false
},
{
"name": "closeOnEscape",
"type": "InputSignal<boolean>",
Expand Down Expand Up @@ -4702,7 +4733,7 @@
{
"name": "MarkdownChildrenComponent",
"kind": "class",
"description": "Recursively dispatches a parent node's children through the markdown view\nregistry. Each child's `type` is looked up in the registry; the resolved\ncomponent is rendered with `[node]` bound to that child.\n\nIdentity-preserving: `track $any(child)` keys on the JS reference of the\nchild node. Because @cacheplane/partial-markdown preserves node identity\nacross pushes, unchanged subtrees never re-render.",
"description": "Recursively dispatches a parent node's children through the markdown view\nregistry. Each child's `type` is looked up in the registry; the resolved\ncomponent is rendered with `[node]` bound to that child.\n\nPosition-stable: `track $index` avoids NG0956 re-creation warnings that\noccur when the markdown pipeline re-parses content on every stream delta,\nproducing new child object references even for unchanged nodes.",
"params": [],
"examples": [],
"properties": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Component } from '@angular/core';
import { ChatComponent, ChatWelcomeSuggestionComponent, views } from '@threadplane/chat';
import { injectAgent } from '@threadplane/ag-ui';
import { signalStateStore } from '@threadplane/render';
import { ExampleChatLayoutComponent } from '@threadplane/example-layouts';
import { StatCardComponent } from './views/stat-card.component';
import { ContainerComponent } from './views/container.component';
Expand Down Expand Up @@ -30,7 +31,7 @@ const WELCOME_SUGGESTIONS = [
imports: [ChatComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
template: `
<example-chat-layout>
<chat main [agent]="agent" [views]="dashboardViews" class="flex-1 min-w-0">
<chat main [agent]="agent" [views]="dashboardViews" [store]="dashStore" class="flex-1 min-w-0">
<div chatWelcomeSuggestions>
@for (s of suggestions; track s.value) {
<chat-welcome-suggestion [label]="s.label" [value]="s.value" (selected)="send($event)" />
Expand All @@ -44,5 +45,10 @@ export class JsonRenderComponent {
protected readonly agent = injectAgent();
protected readonly dashboardViews = dashboardViews;
protected readonly suggestions = WELCOME_SUGGESTIONS;
/**
* Explicit shared store: backend state (STATE_SNAPSHOT) syncs into it via
* the chat composition, so every dashboard surface reads live values.
*/
protected readonly dashStore = signalStateStore({});
protected send(text: string): void { void this.agent.submit({ message: text }); }
}
14 changes: 9 additions & 5 deletions cockpit/ag-ui/json-render/python/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ as the backend updates the data.
</Summary>

<Prompt>
Render a backend-authored dashboard with `@threadplane/chat` over the AG-UI adapter. Register your view components in the `views` map and pass it to `<chat>`. Have the agent emit a json-render spec (with `$state` bindings) as the assistant message content, and put the data the spec binds to in the LangGraph graph state so `ag-ui-langgraph` emits it as a `STATE_SNAPSHOT`. The chat composition resolves the bindings automatically.
Render a backend-authored dashboard with `@threadplane/chat` over the AG-UI adapter. Register your view components in the `views` map and pass it to `<chat>`, along with an explicit `[store]` — the composition syncs incoming agent state into that store, and the spec's `$state` bindings resolve against it. Have the agent emit a json-render spec (with `$state` bindings) as the assistant message content, and put the data the spec binds to in the LangGraph graph state so `ag-ui-langgraph` emits it as a `STATE_SNAPSHOT`.
</Prompt>

<Steps>
<Step title="Register the view components">

Build a `views` registry keyed by the component types your spec will reference, and pass it to `<chat>`:
Build a `views` registry keyed by the component types your spec will reference, and pass it to `<chat>` together with an explicit store. Without a `[store]`, each render surface seeds its own isolated store from the spec — the explicit store is what lets backend state (`STATE_SNAPSHOT`) reach the dashboard bindings:

```typescript
// json-render.component.ts
import { ChatComponent, views } from '@threadplane/chat';
import { injectAgent } from '@threadplane/ag-ui';
import { signalStateStore } from '@threadplane/render';
import { StatCardComponent } from './views/stat-card.component';
import { DashboardGridComponent } from './views/dashboard-grid.component';
// …line-chart, bar-chart, data-grid, container
Expand All @@ -31,10 +32,13 @@ const dashboardViews = views({
dashboard_grid: DashboardGridComponent,
// …
});

// In the component class:
readonly dashStore = signalStateStore({});
```

```html
<chat main [agent]="agent" [views]="dashboardViews" />
<chat main [agent]="agent" [views]="dashboardViews" [store]="dashStore" />
```

</Step>
Expand Down Expand Up @@ -76,8 +80,8 @@ data prop uses a `$state` binding rather than a literal:
This is the AG-UI-native part. Instead of pushing data through a side channel,
put it in the **graph state** — `ag-ui-langgraph` emits the state object as a
`STATE_SNAPSHOT`, the adapter writes it to the agent's `state` signal, and the
chat composition syncs it into the render store where the `$state` bindings
resolve:
chat composition syncs it into the explicit `[store]` you passed, where the
`$state` bindings resolve:

```python
# graph.py — emit_state returns the accumulated tool data into state
Expand Down
5 changes: 3 additions & 2 deletions cockpit/ag-ui/json-render/python/src/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

emit_state walks the message history for this turn and returns the tool
results as top-level state fields — ag-ui-langgraph emits them as
STATE_SNAPSHOT; the Angular chat-lib effect syncs them into the render
store, where the spec's $state bindings resolve them.
STATE_SNAPSHOT; the Angular chat-lib effect syncs them into the explicit
[store] the app passes to <chat>, where the spec's $state bindings
resolve them.
"""

import json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Component } from '@angular/core';
import { ChatComponent, ChatWelcomeSuggestionComponent, views } from '@threadplane/chat';
import { injectAgent } from '@threadplane/langgraph';
import { signalStateStore } from '@threadplane/render';
import { ExampleChatLayoutComponent } from '@threadplane/example-layouts';

import { StatCardComponent } from './views/stat-card.component';
Expand Down Expand Up @@ -31,7 +32,7 @@ const WELCOME_SUGGESTIONS = [
imports: [ChatComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
template: `
<example-chat-layout>
<chat main [agent]="agent" [views]="dashboardViews" class="flex-1 min-w-0">
<chat main [agent]="agent" [views]="dashboardViews" [store]="dashStore" class="flex-1 min-w-0">
<div chatWelcomeSuggestions>
@for (s of suggestions; track s.value) {
<chat-welcome-suggestion
Expand All @@ -50,6 +51,12 @@ export class GenerativeUiComponent {
protected readonly dashboardViews = dashboardViews;
protected readonly suggestions = WELCOME_SUGGESTIONS;

/**
* Explicit shared store: backend graph state syncs into it via the chat
* composition, so every dashboard surface reads live values.
*/
protected readonly dashStore = signalStateStore({});

protected send(text: string): void {
void this.agent.submit({ message: text });
}
Expand Down
Loading
Loading