diff --git a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx
index 1ccd2d0f6..1c22ccbb4 100644
--- a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx
+++ b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx
@@ -95,17 +95,15 @@ yarn add @threadplane/chat @threadplane/langgraph marked
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
-import { provideChat } from '@threadplane/chat';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ apiUrl: 'http://localhost:2024' }),
- provideChat({ assistantName: 'Assistant' }),
],
};
```
-`provideAgent` is the transport. `provideChat` is the UI configuration. They are independent on purpose — you can use one without the other.
+`provideAgent` is the transport, and it is the only provider the chat components need. The UI is configured entirely through component inputs, so the two layers stay independent on purpose.
diff --git a/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx b/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx
index cf25cdef0..bd58a65ee 100644
--- a/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx
+++ b/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx
@@ -115,21 +115,19 @@ It is a peer dep so you can swap it.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
-import { provideChat } from '@threadplane/chat';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ url: 'http://localhost:8000/agent' }),
- provideChat({ assistantName: 'Astra' }),
],
};
```
That is the whole bootstrap.
`provideAgent` is the AG-UI transport. It wraps the official `@ag-ui/client` `HttpAgent` and exposes the signal-shaped contract via DI.
-`provideChat` is the chat UI's configuration.
+It is the only provider the chat components need.
-Notice they are independent.
+Notice how little the two layers know about each other.
`@threadplane/chat` does not know it is talking to an AG-UI backend.
It just reads from the `Agent` contract.
We will lean on that boundary later.
diff --git a/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx b/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx
index 8670f6186..d0cf923d8 100644
--- a/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx
+++ b/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx
@@ -117,12 +117,10 @@ That is why `request_approval` can branch on `decision["approved"]` and pick up
```ts
// app.config.ts
import { provideAgent } from '@threadplane/langgraph';
-import { provideChat } from '@threadplane/chat';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ apiUrl: environment.langGraphApiUrl }),
- provideChat({}),
],
};
```
diff --git a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx
index aede30758..094ff645a 100644
--- a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx
+++ b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx
@@ -205,12 +205,10 @@ Two details worth knowing:
// app.config.ts — cockpit/ag-ui/interrupts/angular/src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
-import { provideChat } from '@threadplane/chat';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ url: '/agent' }),
- provideChat({}),
],
};
```
diff --git a/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx b/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx
index 722305668..918d625b9 100644
--- a/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx
+++ b/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx
@@ -230,14 +230,12 @@ Wire both packages into `app.config.ts`:
```ts
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
-import { provideChat } from '@threadplane/chat';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({
url: 'http://localhost:8080/invocations',
}),
- provideChat({ assistantName: 'Strands Assistant' }),
],
};
```
diff --git a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx
index bc3599ba1..c337ccb90 100644
--- a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx
+++ b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx
@@ -167,14 +167,12 @@ npm install @threadplane/chat @threadplane/ag-ui @ag-ui/client @ag-ui/core marke
The provider is one line, because AG-UI's connection surface is one URL:
```ts
-import { provideChat } from '@threadplane/chat';
import { provideAgent } from '@threadplane/ag-ui';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAgent({ url: 'http://localhost:8000/agent' }),
- provideChat({ assistantName: 'Librarian' }),
],
};
```
diff --git a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx
index 6f438d60c..70fc0c8c2 100644
--- a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx
+++ b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx
@@ -173,7 +173,6 @@ import {
signal,
} from '@angular/core';
import { provideRouter } from '@angular/router';
-import { provideChat } from '@threadplane/chat';
import { LANGGRAPH_THREADS_CONFIG, provideAgent } from '@threadplane/langgraph';
import { routes } from './app.routes';
@@ -195,7 +194,6 @@ export const appConfig: ApplicationConfig = {
onThreadId: (id) => ACTIVE_THREAD.set(id),
}),
{ provide: LANGGRAPH_THREADS_CONFIG, useValue: { apiUrl: API_URL } },
- provideChat({ assistantName: 'Assistant' }),
],
};
```
diff --git a/apps/website/content/docs/a2ui/getting-started/introduction.mdx b/apps/website/content/docs/a2ui/getting-started/introduction.mdx
index e8bb26efa..eb539add7 100644
--- a/apps/website/content/docs/a2ui/getting-started/introduction.mdx
+++ b/apps/website/content/docs/a2ui/getting-started/introduction.mdx
@@ -94,7 +94,6 @@ Nothing in this file is specific to A2UI; the surfaces travel as assistant messa
-`provideChat({})` registers the chat composition defaults alongside it.
### Giving the chat composition a catalog
diff --git a/apps/website/content/docs/ag-ui/guides/client-tools.mdx b/apps/website/content/docs/ag-ui/guides/client-tools.mdx
index 78ff28671..a23e61d59 100644
--- a/apps/website/content/docs/ag-ui/guides/client-tools.mdx
+++ b/apps/website/content/docs/ag-ui/guides/client-tools.mdx
@@ -36,7 +36,7 @@ The server is a FastAPI application. `LangGraphAgent` wraps the compiled graph a
### Providing the agent
-`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly.
+`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly.
diff --git a/apps/website/content/docs/ag-ui/guides/interrupts.mdx b/apps/website/content/docs/ag-ui/guides/interrupts.mdx
index 1e60f4269..9e42ebef8 100644
--- a/apps/website/content/docs/ag-ui/guides/interrupts.mdx
+++ b/apps/website/content/docs/ag-ui/guides/interrupts.mdx
@@ -72,7 +72,7 @@ That wrapper is what turns a LangGraph pause into the AG-UI event described belo
### The agent provider
-`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo.
diff --git a/apps/website/content/docs/ag-ui/guides/json-render.mdx b/apps/website/content/docs/ag-ui/guides/json-render.mdx
index 6f188a2aa..2e253f855 100644
--- a/apps/website/content/docs/ag-ui/guides/json-render.mdx
+++ b/apps/website/content/docs/ag-ui/guides/json-render.mdx
@@ -90,7 +90,6 @@ Nothing in this file is specific to generative UI.
-`provideChat({})` registers the chat composition defaults alongside it.
### The view registry and the shared store
diff --git a/apps/website/content/docs/ag-ui/guides/subagents.mdx b/apps/website/content/docs/ag-ui/guides/subagents.mdx
index fad6b96ea..cba5ef0c2 100644
--- a/apps/website/content/docs/ag-ui/guides/subagents.mdx
+++ b/apps/website/content/docs/ag-ui/guides/subagents.mdx
@@ -74,7 +74,7 @@ The AG-UI encoder serializes pydantic `ag_ui.core` event classes, so the subclas
### The agent provider
-`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. Nothing in either provider is subagent-specific. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. Nothing about it is subagent-specific. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory.
diff --git a/apps/website/content/docs/ag-ui/guides/tool-views.mdx b/apps/website/content/docs/ag-ui/guides/tool-views.mdx
index 63a71959a..48033016a 100644
--- a/apps/website/content/docs/ag-ui/guides/tool-views.mdx
+++ b/apps/website/content/docs/ag-ui/guides/tool-views.mdx
@@ -42,7 +42,7 @@ The server is a FastAPI application. `add_langgraph_fastapi_endpoint` mounts the
### Providing the agent
-`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly.
+`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly.
diff --git a/apps/website/content/docs/ag-ui/reference/event-mapping.mdx b/apps/website/content/docs/ag-ui/reference/event-mapping.mdx
index 1cfe9be31..b93961682 100644
--- a/apps/website/content/docs/ag-ui/reference/event-mapping.mdx
+++ b/apps/website/content/docs/ag-ui/reference/event-mapping.mdx
@@ -38,7 +38,7 @@ That wrapper is the piece that translates LangGraph's own stream into the protoc
### Providing the agent
-`provideAgent()` from `@threadplane/ag-ui` registers the agent once for the whole application, and `provideChat({})` registers the chat configuration, here left at its defaults. Your own application passes `{ url: 'https://your-backend.example.com/agent' }` directly; this example passes a factory because it resolves its endpoint at runtime from the host that serves the demo.
+`provideAgent()` from `@threadplane/ag-ui` registers the agent once for the whole application, and it is the only provider the `` composition requires. Your own application passes `{ url: 'https://your-backend.example.com/agent' }` directly; this example passes a factory because it resolves its endpoint at runtime from the host that serves the demo.
diff --git a/apps/website/content/docs/chat/a2ui/overview.mdx b/apps/website/content/docs/chat/a2ui/overview.mdx
index ee98a4fc7..f5823a507 100644
--- a/apps/website/content/docs/chat/a2ui/overview.mdx
+++ b/apps/website/content/docs/chat/a2ui/overview.mdx
@@ -86,7 +86,6 @@ The graph is served by the LangGraph API server, which supplies persistence, so
-`provideChat({})` registers the chat composition defaults alongside it.
### Giving the chat composition a catalog
diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json
index 162182aa1..60d63bddf 100644
--- a/apps/website/content/docs/chat/api/api-docs.json
+++ b/apps/website/content/docs/chat/api/api-docs.json
@@ -1925,7 +1925,7 @@
},
{
"name": "messageContent",
- "type": "(message: BaseMessage<>) => string",
+ "type": "(message: object) => string",
"description": "",
"optional": false
},
@@ -2072,12 +2072,12 @@
},
{
"name": "humanContent",
- "signature": "humanContent(message: unknown): string",
+ "signature": "humanContent(message: object): string",
"description": "Renderable content for a human-role message bubble. Most human\nmessages are typed prompts and pass through `messageContent`\nunchanged. A2UI action messages (e.g. form submits, button clicks\non a rendered surface) flow through the same submit channel and\nland in the message stream as a HumanMessage whose content is a\nJSON-serialized `A2uiActionMessage`. Showing the raw JSON as if\nthe user typed it leaks the protocol; per the A2UI spec\nthose events resemble tool calls more than user utterances.\n\n`a2uiActionLabel` returns a short human-readable label for\nrecognized action shapes (\"Search flights\", \"Selected flight UA123\",\netc.) — or null for any non-action content, in which case we fall\nback to the original text.",
"params": [
{
"name": "message",
- "type": "unknown",
+ "type": "object",
"description": "",
"optional": false
}
@@ -4002,7 +4002,7 @@
{
"name": "resolvedRegistry",
"type": "Signal | RenderViewEntry>>>",
- "description": "",
+ "description": "Most specific wins: the `[viewRegistry]` input, then a registry provided by\nan ancestor injector, then the built-in markdown views.",
"optional": false
},
{
@@ -6467,32 +6467,6 @@
],
"examples": []
},
- {
- "name": "ChatConfig",
- "kind": "interface",
- "description": "Application-wide options for provideChat. Every field is optional;\nthe values are exposed to all chat components in the tree via the\n`CHAT_CONFIG` injection token, so you set them once at bootstrap instead of\nthreading props through every component.",
- "properties": [
- {
- "name": "assistantName",
- "type": "string",
- "description": "Shared assistant display name for consumers that read CHAT_CONFIG (default: \"Assistant\").",
- "optional": true
- },
- {
- "name": "avatarLabel",
- "type": "string",
- "description": "Shared AI avatar label for consumers that read CHAT_CONFIG (default: \"A\").",
- "optional": true
- },
- {
- "name": "renderRegistry",
- "type": "AngularRegistry",
- "description": "Shared render registry for consumers that read CHAT_CONFIG.",
- "optional": true
- }
- ],
- "examples": []
- },
{
"name": "ChatLifecycle",
"kind": "interface",
@@ -8468,7 +8442,7 @@
"name": "ContentType",
"kind": "type",
"description": "",
- "signature": "\"pending\" | \"markdown\" | \"json-render\" | \"a2ui\" | \"mixed\"",
+ "signature": "\"pending\" | \"markdown\" | \"json-render\" | \"a2ui\"",
"examples": []
},
{
@@ -8590,13 +8564,6 @@
"signature": "ViewRegistry",
"examples": []
},
- {
- "name": "CHAT_CONFIG",
- "kind": "const",
- "description": "",
- "signature": "InjectionToken",
- "examples": []
- },
{
"name": "CHAT_LIFECYCLE",
"kind": "const",
@@ -8614,7 +8581,7 @@
{
"name": "MARKDOWN_VIEW_REGISTRY",
"kind": "const",
- "description": "DI token for the markdown view registry consumed by \nand . Maps MarkdownNode.type strings (e.g. \"paragraph\",\n\"heading\") to Angular components that render that node type.\n\n`` provides the runtime registry on its component-level\ninjector — either the consumer-supplied [viewRegistry] input, or\n`cacheplaneMarkdownViews` (the default) — so descendant \ncomponents resolve the right components for each node.",
+ "description": "DI token for the markdown view registry consumed by \nand . Maps MarkdownNode.type strings (e.g. \"paragraph\",\n\"heading\") to Angular components that render that node type.\n\n`` provides the resolved registry on its component-level\ninjector so descendant components resolve the right component\nfor each node. It resolves most-specific-first: the `[viewRegistry]` input,\nthen a registry provided by an ancestor injector (application root or route),\nthen `cacheplaneMarkdownViews` (the default). Providing this token at the\napplication root is therefore a supported app-wide override.",
"signature": "InjectionToken | RenderViewEntry>>>",
"examples": []
},
@@ -9418,13 +9385,13 @@
{
"name": "messageContent",
"kind": "function",
- "description": "Extracts a human-readable string from a message's content.\n\n`BaseMessage.content` is `string | MessageContentComplex[]`. Reasoning-\ncapable models (OpenAI gpt-5/o-series, Anthropic) emit complex arrays of\ntyped blocks: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use\nblocks, etc. We render only the visible text portions and skip anything\nelse. Stringifying the whole array would dump raw JSON like\n`[{\"type\":\"text\",...}]` into the chat bubble.",
- "signature": "messageContent(message: BaseMessage<>): string",
+ "description": "Extracts a human-readable string from a message's content.\n\nMessage content is either a plain string or an array of typed blocks.\nReasoning-capable models (OpenAI gpt-5/o-series, Anthropic) emit complex\narrays: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use blocks, etc.\nOnly the visible text portions are rendered and anything else is skipped.\nStringifying the whole array would dump raw JSON like `[{\"type\":\"text\",...}]`\ninto the chat bubble.\n\nThe parameter is structural on purpose. This function reads nothing but\n`.content`, and callers hold either the runtime-neutral `Message` from\n`agent.messages()` or a LangChain `BaseMessage` depending on where the\nmessage came from. Both satisfy `{ content: unknown }`, so neither has to\ncast.",
+ "signature": "messageContent(message: object): string",
"params": [
{
"name": "message",
- "type": "BaseMessage<>",
- "description": "",
+ "type": "object",
+ "description": "Any object carrying a `content` field.",
"optional": false
}
],
@@ -9531,27 +9498,6 @@
},
"examples": []
},
- {
- "name": "provideChat",
- "kind": "function",
- "description": "Bootstrap `@threadplane/chat` in an Angular application or standalone\ncomponent tree.\n\nCall this once inside `bootstrapApplication` (or the `providers` array of a\nroot `ApplicationConfig`). It registers the shared ChatConfig token\nso every chat component in the tree can read the render registry, avatar\nlabel, and assistant display name without explicit prop threading.",
- "signature": "provideChat(config: ChatConfig): EnvironmentProviders",
- "params": [
- {
- "name": "config",
- "type": "ChatConfig",
- "description": "Options bag that controls the chat feature set:\n - `renderRegistry` — shared AngularRegistry wiring tool-view\n components to their names; pass the value returned by\n `defineAngularRegistry` from `\\@threadplane/render`.\n - `avatarLabel` — short label shown in the AI avatar bubble (default `\"A\"`).\n - `assistantName` — display name shown above assistant messages\n (default `\"Assistant\"`).",
- "optional": false
- }
- ],
- "returns": {
- "type": "EnvironmentProviders",
- "description": ""
- },
- "examples": [
- "```ts\n// main.ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideChat } from '@threadplane/chat';\nimport { defineAngularRegistry, provideRender } from '@threadplane/render';\nimport { DayCardComponent } from './day-card.component';\n\nconst registry = defineAngularRegistry({ day_card: DayCardComponent });\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideChat({ renderRegistry: registry, avatarLabel: 'AI' }),\n provideRender({ registry }),\n ],\n});\n```"
- ]
- },
{
"name": "renderMarkdown",
"kind": "function",
@@ -9771,7 +9717,7 @@
{
"name": "tools",
"kind": "function",
- "description": "Collect named client tools into a frozen, name-keyed registry.\n\nThe overload is generic over the entire map (`const M`) so that each tool's\nprecise type (FunctionToolDef``, ViewToolDef``, or\nAskToolDef``) and every literal key are preserved in the\nClientToolRegistry passed to `provideChat`. This lets downstream\nconsumers look up individual tools without losing generic information.",
+ "description": "Collect named client tools into a frozen, name-keyed registry.\n\nThe overload is generic over the entire map (`const M`) so that each tool's\nprecise type (FunctionToolDef``, ViewToolDef``, or\nAskToolDef``) and every literal key are preserved in the\nClientToolRegistry passed to the `clientTools` input. This lets downstream\nconsumers look up individual tools without losing generic information.",
"signature": "tools(map: M): Readonly",
"params": [
{
diff --git a/apps/website/content/docs/chat/api/chat-config.mdx b/apps/website/content/docs/chat/api/chat-config.mdx
deleted file mode 100644
index 0e8c63559..000000000
--- a/apps/website/content/docs/chat/api/chat-config.mdx
+++ /dev/null
@@ -1,125 +0,0 @@
----
-description: The ChatConfig interface accepted by provideChat(), its three optional fields, and how to read the CHAT_CONFIG token from your own components.
----
-
-# ChatConfig
-
-`ChatConfig` is the configuration interface accepted by `provideChat()`. The object is stored under `CHAT_CONFIG` for application code that wants shared chat defaults.
-
-No library component reads `CHAT_CONFIG` today. Every field below is a value your own components inject and apply; the shipped chat components ignore the token entirely.
-
-**Import:**
-
-```typescript
-import type { ChatConfig } from '@threadplane/chat';
-import type { AngularRegistry } from '@threadplane/render';
-```
-
-## Interface Definition
-
-```typescript
-interface ChatConfig {
- /** Shared render registry for consumers that read CHAT_CONFIG. */
- renderRegistry?: AngularRegistry;
- /** Shared AI avatar label for consumers that read CHAT_CONFIG (default: "A"). */
- avatarLabel?: string;
- /** Shared assistant display name for consumers that read CHAT_CONFIG (default: "Assistant"). */
- assistantName?: string;
-}
-```
-
-## Properties
-
-### avatarLabel
-
-```typescript
-avatarLabel?: string
-```
-
-A short string (typically one or two characters) for wrappers or components that choose to read `CHAT_CONFIG`.
-
-**Default:** `"A"`
-
-**Example:**
-
-```typescript
-provideChat({ avatarLabel: 'AI' });
-```
-
-There is no avatar-specific CSS token. Use the shared `--tplane-chat-*` tokens such as `--tplane-chat-surface`, `--tplane-chat-text`, and `--tplane-chat-text-muted` to align chat surfaces with your app theme.
-
-### assistantName
-
-```typescript
-assistantName?: string
-```
-
-The display name for wrappers or components that choose to read `CHAT_CONFIG`.
-
-**Default:** `"Assistant"`
-
-**Example:**
-
-```typescript
-provideChat({ assistantName: 'Code Copilot' });
-```
-
-### renderRegistry
-
-```typescript
-renderRegistry?: AngularRegistry
-```
-
-A shared render registry value for consumers that inject `CHAT_CONFIG`. `ChatComponent` does not read this value directly; pass a `ViewRegistry` to the `` input for built-in generative UI rendering.
-
-**Example:**
-
-```typescript
-provideChat({ renderRegistry });
-```
-
-## Accessing ChatConfig at Runtime
-
-Inject `CHAT_CONFIG` to read configuration values in your own components:
-
-```typescript
-import { inject } from '@angular/core';
-import { CHAT_CONFIG } from '@threadplane/chat';
-import type { ChatConfig } from '@threadplane/chat';
-
-@Component({
- selector: 'app-chat-header',
- template: `
- {{ assistantName }}
- `,
-})
-export class ChatHeaderComponent {
- private config = inject(CHAT_CONFIG, { optional: true });
-
- get assistantName(): string {
- return this.config?.assistantName ?? 'Assistant';
- }
-}
-```
-
-## Gotcha: Inputs Still Win
-
-`provideChat()` is not a replacement for component inputs. For generative UI, configure the chat surface directly:
-
-```html
-
-```
-
-Use `CHAT_CONFIG` when you are building your own wrappers or want route-level defaults that your code reads explicitly.
-
-## Type Location
-
-The canonical `ChatConfig` interface is defined alongside `provideChat()`:
-
-- `libs/chat/src/lib/provide-chat.ts` -- The canonical definition with JSDoc comments, alongside the `provideChat()` function and `CHAT_CONFIG` token
-
-The public API exports `ChatConfig` as a type-only export:
-
-```typescript
-export type { ChatConfig } from './lib/provide-chat';
-```
diff --git a/apps/website/content/docs/chat/api/content-classifier.mdx b/apps/website/content/docs/chat/api/content-classifier.mdx
index 1ceb1dd0f..6c7e6c048 100644
--- a/apps/website/content/docs/chat/api/content-classifier.mdx
+++ b/apps/website/content/docs/chat/api/content-classifier.mdx
@@ -60,7 +60,7 @@ interface ContentClassifier {
## ContentType
```typescript
-type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed';
+type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui';
```
| Value | Meaning |
@@ -69,7 +69,8 @@ type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed';
| `markdown` | Plain text / markdown prose |
| `json-render` | JSON spec detected (first non-whitespace is `{`) |
| `a2ui` | A2UI payload detected via `---a2ui_JSON---` prefix, parsed as JSONL messages |
-| `mixed` | Reserved. The current implementation never emits this value. |
+
+The union has no member for interleaved content: prose with inline JSON-render specs classifies as `markdown` and the markdown path renders the embedded specs in place.
### a2uiSurfaces
diff --git a/apps/website/content/docs/chat/api/provide-chat.mdx b/apps/website/content/docs/chat/api/provide-chat.mdx
deleted file mode 100644
index 69929ba64..000000000
--- a/apps/website/content/docs/chat/api/provide-chat.mdx
+++ /dev/null
@@ -1,173 +0,0 @@
----
-description: How provideChat() registers the CHAT_CONFIG token, what ChatConfig carries, and why the values are for components you write yourself.
----
-
-# provideChat()
-
-`provideChat` is the provider factory that registers `@threadplane/chat` configuration in Angular's dependency injection system. Call it in your `ApplicationConfig` or at the route level when you need a shared `CHAT_CONFIG` value.
-
-```typescript
-import { provideChat } from '@threadplane/chat';
-
-export const appConfig: ApplicationConfig = {
- providers: [
- provideChat({
- avatarLabel: 'AI',
- assistantName: 'My Assistant',
- }),
- ],
-};
-```
-
-## Signature
-
-```typescript
-function provideChat(config: ChatConfig): EnvironmentProviders
-```
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `config` | `ChatConfig` | Configuration object with optional render registry, avatar label, and assistant name |
-
-**Returns:** `EnvironmentProviders` -- created via `makeEnvironmentProviders()`, compatible with `bootstrapApplication`, `ApplicationConfig`, and route-level `providers`.
-
-## What It Does
-
-`provideChat()` registers a single provider:
-
-```typescript
-{ provide: CHAT_CONFIG, useValue: config }
-```
-
-This makes the `ChatConfig` object available throughout the application via the `CHAT_CONFIG` injection token. `provideChat()` does not automatically wire generative UI into ``; pass `[views]`, `[store]`, and `[handlers]` directly to `ChatComponent`.
-
-## CHAT_CONFIG Injection Token
-
-```typescript
-import { CHAT_CONFIG } from '@threadplane/chat';
-
-const CHAT_CONFIG: InjectionToken;
-```
-
-The token is an `InjectionToken` that can be injected in any component, directive, or service:
-
-```typescript
-import { inject } from '@angular/core';
-import { CHAT_CONFIG } from '@threadplane/chat';
-
-@Component({ /* ... */ })
-export class MyComponent {
- private chatConfig = inject(CHAT_CONFIG);
-}
-```
-
-
-Injecting `CHAT_CONFIG` without calling `provideChat()` will throw a `NullInjectorError`. Use `inject(CHAT_CONFIG, { optional: true })` if your component should work without global configuration.
-
-
-## Configuration Options
-
-See the [ChatConfig API reference](/docs/chat/api/chat-config) for the full interface definition.
-
-| Option | Type | Default | Description |
-|--------|------|---------|-------------|
-| `renderRegistry` | `AngularRegistry` | `undefined` | Stored on `CHAT_CONFIG` for consumers that want a shared render registry. `` uses the `[views]` input directly. |
-| `avatarLabel` | `string` | `"A"` | Shared avatar label for consumers that inject `CHAT_CONFIG` |
-| `assistantName` | `string` | `"Assistant"` | Shared assistant display name for consumers that inject `CHAT_CONFIG` |
-
-## Usage Patterns
-
-### Application-Wide Configuration
-
-```typescript
-// app.config.ts
-import { provideAgent } from '@threadplane/langgraph';
-import { provideChat } from '@threadplane/chat';
-
-export const appConfig: ApplicationConfig = {
- providers: [
- provideAgent({ apiUrl: 'http://localhost:2024', assistantId: 'chat' }),
- provideChat({
- avatarLabel: 'B',
- assistantName: 'Bot',
- }),
- ],
-};
-```
-
-### Route-Level Configuration
-
-Provide different chat configurations for different parts of your application:
-
-```typescript
-// app.routes.ts
-export const routes: Routes = [
- {
- path: 'support',
- loadComponent: () => import('./support/support-chat.component'),
- providers: [
- provideChat({
- assistantName: 'Support Agent',
- avatarLabel: 'S',
- }),
- ],
- },
- {
- path: 'coding',
- loadComponent: () => import('./coding/code-chat.component'),
- providers: [
- provideChat({
- assistantName: 'Code Helper',
- avatarLabel: 'C',
- }),
- ],
- },
-];
-```
-
-### Without provideChat()
-
-All chat components work without `provideChat()`. No library component reads `CHAT_CONFIG` today, so the `avatarLabel` and `assistantName` values are conventions for wrapper components you write yourself rather than settings the shipped components consume. Generative UI still requires the `[views]` input on `ChatComponent`.
-
-```typescript
-// This works fine without provideChat()
-import { injectAgent, provideAgent } from '@threadplane/langgraph';
-
-@Component({
- imports: [ChatComponent],
- providers: [
- provideAgent({
- apiUrl: 'http://localhost:2024',
- assistantId: 'chat',
- threadId: signal(null),
- }),
- ],
- template: ``,
-})
-export class SimpleChatComponent {
- chatRef = injectAgent();
-}
-```
-
-## What's Next
-
-
-
- Full ChatConfig interface reference.
-
-
- Set up view registries for dynamic UI components.
-
-
- Configuration patterns and best practices.
-
-
diff --git a/apps/website/content/docs/chat/components/chat-debug.mdx b/apps/website/content/docs/chat/components/chat-debug.mdx
index bde27f0c9..ca357cbfd 100644
--- a/apps/website/content/docs/chat/components/chat-debug.mdx
+++ b/apps/website/content/docs/chat/components/chat-debug.mdx
@@ -44,7 +44,7 @@ The last line calls `compile()` with no checkpointer. The checkpoints the Timeli
### The agent provider
-`provideAgent()` registers the agent once for the whole application. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them, and it sits alongside `provideChat({})`, which supplies the chat library's own providers.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the chat compositions require. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them.
@@ -55,7 +55,6 @@ provideAgent({
apiUrl: 'https://your-deployment.langgraph.app',
assistantId: 'debug',
}),
-provideChat({}),
```
### Mounting the panel
diff --git a/apps/website/content/docs/chat/components/chat-input.mdx b/apps/website/content/docs/chat/components/chat-input.mdx
index a91ec21ef..0219698e6 100644
--- a/apps/website/content/docs/chat/components/chat-input.mdx
+++ b/apps/website/content/docs/chat/components/chat-input.mdx
@@ -26,7 +26,7 @@ The compiled graph is exported as `graph`, which is the symbol `langgraph.json`
### The application configuration
-`provideAgent()` registers the agent for the whole application. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits beside `provideChat({})`, which registers the chat library's own providers.
+`provideAgent()` registers the agent for the whole application, and it is the only provider the chat components require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them.
diff --git a/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx b/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx
index 9c90a5df6..0ca85673b 100644
--- a/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx
+++ b/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx
@@ -42,7 +42,7 @@ The graph is an ordinary agent and tool loop. `book_flight` is registered next t
### The agent provider
-`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo.
diff --git a/apps/website/content/docs/chat/components/chat-message-list.mdx b/apps/website/content/docs/chat/components/chat-message-list.mdx
index d139580f1..536f2883c 100644
--- a/apps/website/content/docs/chat/components/chat-message-list.mdx
+++ b/apps/website/content/docs/chat/components/chat-message-list.mdx
@@ -121,15 +121,13 @@ const type = getMessageType(message); // 'human' | 'ai' | 'tool' | 'system' | 'f
Runtime-neutral messages have a `content` property that is either a `string` or a `ContentBlock[]`. The library exports a `messageContent()` utility that flattens either shape to a string:
```typescript
-import type { BaseMessage } from '@langchain/core/messages';
-
// If content is a string, returns it directly.
// If content is a block array, concatenates the visible text blocks
// (`text` and `output_text`) and skips reasoning, tool-use, and image blocks.
-function messageContent(message: BaseMessage): string
+function messageContent(message: { content: unknown }): string
```
-Note the parameter type: `messageContent()` consumes the LangChain `BaseMessage` shape, not the runtime-neutral `Message` handed to a `chatMessageTemplate`. Pass it a message from a LangChain-shaped source, not one straight out of `agent.messages()`.
+The parameter is structural: the function reads nothing but `content`. Pass it the `Message` handed to a `chatMessageTemplate` straight out of `agent.messages()`, or a LangChain `BaseMessage` from a LangChain-shaped source. Both type-check, and neither needs a cast.
For custom templates, access `message.content` directly and narrow the type in the component class. Angular template expressions have no `typeof` operator, so the check has to live in a method:
diff --git a/apps/website/content/docs/chat/components/chat-popup.mdx b/apps/website/content/docs/chat/components/chat-popup.mdx
index b532ed040..70a48dd32 100644
--- a/apps/website/content/docs/chat/components/chat-popup.mdx
+++ b/apps/website/content/docs/chat/components/chat-popup.mdx
@@ -126,13 +126,22 @@ Project content into the window header with the `[chatHeader]` slot:
## Styling
-The popup uses the standard `--tplane-chat-*` token system. The launcher position is not tokenized: the host is `position: fixed; bottom: 1rem; right: 1rem`, so move it by overriding those properties on the host element from a global stylesheet:
+The popup uses the standard `--tplane-chat-*` token system. Two tokens control where the launcher sits, both defaulting to `1rem`:
+
+| Token | Default | Controls |
+|-------|---------|----------|
+| `--tplane-chat-launcher-offset-x` | `1rem` | Distance from the right edge of the viewport |
+| `--tplane-chat-launcher-offset-y` | `1rem` | Distance from the bottom edge of the viewport |
+
+Set them anywhere the popup inherits from, such as `:root`, to clear a bottom bar or a consent banner:
```css
-chat-popup {
- bottom: 1.5rem;
- right: 1.5rem;
+:root {
+ --tplane-chat-launcher-offset-x: 1.5rem;
+ --tplane-chat-launcher-offset-y: 5rem;
}
```
+The popup window reads the same horizontal offset, so it stays aligned with the launcher when you move it. Below 640px the window goes full screen and ignores both.
+
See [Theming](/docs/chat/guides/theming) for the full token reference.
diff --git a/apps/website/content/docs/chat/components/chat-subagent-card.mdx b/apps/website/content/docs/chat/components/chat-subagent-card.mdx
index f2572d9da..5e30c77e7 100644
--- a/apps/website/content/docs/chat/components/chat-subagent-card.mdx
+++ b/apps/website/content/docs/chat/components/chat-subagent-card.mdx
@@ -62,7 +62,7 @@ The system prompt tells the orchestrator to dispatch research, then booking, the
### Telling the adapter that task means delegation
-`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the chat configuration at its defaults. The one subagent-specific line is `subagentToolNames`. The default is already `['task']`, so this example states explicitly what it would otherwise inherit.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The one subagent-specific line is `subagentToolNames`. The default is already `['task']`, so this example states explicitly what it would otherwise inherit.
The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes `apiUrl` and `assistantId` directly.
diff --git a/apps/website/content/docs/chat/components/chat-tool-calls.mdx b/apps/website/content/docs/chat/components/chat-tool-calls.mdx
index b84a95074..ebd50483c 100644
--- a/apps/website/content/docs/chat/components/chat-tool-calls.mdx
+++ b/apps/website/content/docs/chat/components/chat-tool-calls.mdx
@@ -38,7 +38,7 @@ The names bound here are the names the component groups by and labels its cards
### The agent provider
-`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the chat library's configuration, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. Your own application does not need the factory.
+`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. Your own application does not need the factory.
diff --git a/apps/website/content/docs/chat/components/chat-trace.mdx b/apps/website/content/docs/chat/components/chat-trace.mdx
index 16f891d48..78b5b50dd 100644
--- a/apps/website/content/docs/chat/components/chat-trace.mdx
+++ b/apps/website/content/docs/chat/components/chat-trace.mdx
@@ -36,7 +36,7 @@ The compiled graph is exported as `graph`, which is the symbol `langgraph.json`
### The agent and the chat configuration
-`provideAgent()` registers the agent for the whole application. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. `provideChat({})` registers the chat library's own configuration at its defaults.
+`provideAgent()` registers the agent for the whole application, and it is the only provider the chat compositions require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them.
diff --git a/apps/website/content/docs/chat/concepts/message-model.mdx b/apps/website/content/docs/chat/concepts/message-model.mdx
index 4edf14e5e..fc2499d2d 100644
--- a/apps/website/content/docs/chat/concepts/message-model.mdx
+++ b/apps/website/content/docs/chat/concepts/message-model.mdx
@@ -28,7 +28,7 @@ Every message the browser renders begins as an entry in that returned list.
### The application configuration
-`provideAgent()` registers the agent for the whole application, keyed by a typed ref. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits beside `provideChat({})`, which registers the chat library's own providers.
+`provideAgent()` registers the agent for the whole application, keyed by a typed ref, and it is the only provider the chat components require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them.
@@ -164,7 +164,7 @@ type ContentBlock =
Plain text is the common case, and it is the only case in the demo: the LangGraph adapter flattens LangChain content arrays down to their visible text before the message reaches a component, keeping the raw payload on `extra`. Adapters that can preserve more shape emit blocks instead.
-Custom templates should therefore check the type before assuming they can interpolate `content` directly. `messageContent()` does this for you, taking a LangChain `BaseMessage` rather than the `Message` shape, and hand-written code can do the same:
+Custom templates should therefore check the type before assuming they can interpolate `content` directly. `messageContent()` does this for you and accepts the `Message` shape directly, and hand-written code can do the same:
```ts
function textOf(message: Message): string {
diff --git a/apps/website/content/docs/chat/getting-started/installation.mdx b/apps/website/content/docs/chat/getting-started/installation.mdx
index 88f6976c7..ada385505 100644
--- a/apps/website/content/docs/chat/getting-started/installation.mdx
+++ b/apps/website/content/docs/chat/getting-started/installation.mdx
@@ -58,15 +58,14 @@ every required peer for you, so the install command above is enough.
| `@threadplane/render` | `0.0.66` | yes |
| `@threadplane/a2ui` | `0.0.66` | yes |
| `@json-render/core` | `^0.16.0` | yes |
-| `@langchain/core` | `^1.1.33` | yes |
| `rxjs` | `~7.8.0` | yes |
| `marked` | `^15.0.0 \|\| ^16.0.0` | yes |
| `zod` | `^3.25.0` | yes |
| `katex` | `^0.16.0 \|\| ^0.17.0` | optional |
Installing `@threadplane/chat` therefore also brings in `@threadplane/telemetry`,
-`@threadplane/render`, `@threadplane/a2ui`, `@json-render/core`,
-`@langchain/core`, `rxjs`, `marked`, and `zod`.
+`@threadplane/render`, `@threadplane/a2ui`, `@json-render/core`, `rxjs`,
+`marked`, and `zod`.
## 2. Configure the runtime
@@ -89,16 +88,9 @@ export const appConfig: ApplicationConfig = {
};
```
-That is the only provider `` needs.
-
-
-`provideChat()` parks a `ChatConfig` object on the `CHAT_CONFIG` injection token
-for your own components to read. No component in `@threadplane/chat` injects that
-token, so calling it changes nothing about what `` renders — including
-`assistantName` and `avatarLabel`, which no built-in component displays. Call it
-only when your own wrappers inject `CHAT_CONFIG`. See
-[Configuration](/docs/chat/guides/configuration).
-
+That is the only provider `` needs. There is no separate chat provider:
+everything the built-in components render is driven by their inputs, and their
+appearance by the `--tplane-chat-*` custom properties.
## 3. Render your first chat
@@ -131,6 +123,6 @@ carries the conformance runners and fixtures, not `mockAgent()`.
## What's next
-- [Configuration](/docs/chat/guides/configuration)
+- [Theming](/docs/chat/guides/theming)
- [ChatComponent](/docs/chat/components/chat)
- [Choosing an adapter](/docs/choosing-an-adapter)
diff --git a/apps/website/content/docs/chat/getting-started/quickstart.mdx b/apps/website/content/docs/chat/getting-started/quickstart.mdx
index 9fc42b073..d4c409709 100644
--- a/apps/website/content/docs/chat/getting-started/quickstart.mdx
+++ b/apps/website/content/docs/chat/getting-started/quickstart.mdx
@@ -46,7 +46,7 @@ export const appConfig: ApplicationConfig = {
};
```
-`provideAgent()` is the only provider `` requires. `provideChat()` exists too, but it only parks values on the `CHAT_CONFIG` token for your own components to read — no built-in component injects it, so adding it changes nothing about what renders.
+`provideAgent()` is the only provider `` requires. There is no separate chat provider: everything `` renders is driven by its inputs.
diff --git a/apps/website/content/docs/chat/guides/client-tools.mdx b/apps/website/content/docs/chat/guides/client-tools.mdx
index b3f76802e..d4796fcfc 100644
--- a/apps/website/content/docs/chat/guides/client-tools.mdx
+++ b/apps/website/content/docs/chat/guides/client-tools.mdx
@@ -48,7 +48,7 @@ The AG-UI twin's sibling graph is a separate file that compiles with `MemorySave
### Providing the agent
-`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its `apiUrl` and `assistantId` at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes them directly. The `CLIENT_TOOLS_AGENT_REF` argument is a typed reference, covered under [typed agent state](#typed-agent-state) below.
+`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its `apiUrl` and `assistantId` at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes them directly. The `CLIENT_TOOLS_AGENT_REF` argument is a typed reference, covered under [typed agent state](#typed-agent-state) below.
diff --git a/apps/website/content/docs/chat/guides/configuration.mdx b/apps/website/content/docs/chat/guides/configuration.mdx
deleted file mode 100644
index 5946d0c40..000000000
--- a/apps/website/content/docs/chat/guides/configuration.mdx
+++ /dev/null
@@ -1,137 +0,0 @@
----
-description: What provideChat() and the CHAT_CONFIG token actually do — a carrier for your own components' shared defaults, not a way to configure the built-in chat UI.
----
-
-# Configuration
-
-`@threadplane/chat` leans on Angular's dependency injection for shared configuration. The `provideChat()` function registers a `ChatConfig` object under the `CHAT_CONFIG` injection token.
-
-
-No component in `@threadplane/chat` injects `CHAT_CONFIG`. `provideChat()` is a
-carrier for your own components and wrappers: it stores values you can read back
-with `inject(CHAT_CONFIG)`. It does not alter ``, ``, or any
-other built-in component — including `assistantName` and `avatarLabel`, which no
-built-in component displays. Everything `` renders is driven by its inputs.
-
-
-## provideChat()
-
-Call `provideChat()` in your application's provider array when your application or wrappers need to inject shared chat configuration.
-
-```typescript
-// app.config.ts
-import { ApplicationConfig } from '@angular/core';
-import { provideChat } from '@threadplane/chat';
-
-export const appConfig: ApplicationConfig = {
- providers: [
- provideChat({
- avatarLabel: 'AI',
- assistantName: 'My Assistant',
- }),
- ],
-};
-```
-
-**Signature:**
-
-```typescript
-function provideChat(config: ChatConfig): EnvironmentProviders
-```
-
-`provideChat()` returns `EnvironmentProviders` (via `makeEnvironmentProviders`), so it works with `bootstrapApplication`, `ApplicationConfig`, or route-level providers.
-
-## ChatConfig Interface
-
-```typescript
-import type { AngularRegistry } from '@threadplane/render';
-
-interface ChatConfig {
- /** Default render registry for consumers that read CHAT_CONFIG. */
- renderRegistry?: AngularRegistry;
-
- /** Override the default AI avatar label (default: "A"). */
- avatarLabel?: string;
-
- /** Override the default assistant display name (default: "Assistant"). */
- assistantName?: string;
-
-}
-```
-
-### Options
-
-| Option | Type | Default | Description |
-|--------|------|---------|-------------|
-| `renderRegistry` | `AngularRegistry` | `undefined` | Stored on `CHAT_CONFIG` for consumers that want a shared render registry. Pass `[views]` directly to `ChatComponent` for built-in generative UI rendering. |
-| `avatarLabel` | `string` | `"A"` | Single character or short string for consumers that inject `CHAT_CONFIG`. |
-| `assistantName` | `string` | `"Assistant"` | Display name for consumers that inject `CHAT_CONFIG`. |
-
-## CHAT_CONFIG Injection Token
-
-The `CHAT_CONFIG` token is an `InjectionToken` that you can inject directly in any component or service:
-
-```typescript
-import { inject } from '@angular/core';
-import { CHAT_CONFIG } from '@threadplane/chat';
-import type { ChatConfig } from '@threadplane/chat';
-
-@Component({ /* ... */ })
-export class MyComponent {
- private config = inject(CHAT_CONFIG);
-
- get avatarText(): string {
- return this.config.avatarLabel ?? 'A';
- }
-}
-```
-
-
-If `provideChat()` has not been called, injecting `CHAT_CONFIG` will throw. Use `inject(CHAT_CONFIG, { optional: true })` if your component needs to work with or without global configuration.
-
-
-## Per-Route Configuration
-
-Because `provideChat()` returns `EnvironmentProviders`, you can provide different configurations at the route level:
-
-```typescript
-// app.routes.ts
-import { provideChat } from '@threadplane/chat';
-
-export const routes: Routes = [
- {
- path: 'support',
- loadComponent: () => import('./support-chat.component'),
- providers: [
- provideChat({
- assistantName: 'Support Bot',
- avatarLabel: 'S',
- }),
- ],
- },
- {
- path: 'code',
- loadComponent: () => import('./code-chat.component'),
- providers: [
- provideChat({
- assistantName: 'Code Assistant',
- avatarLabel: 'C',
- renderRegistry: codeRegistry,
- }),
- ],
- },
-];
-```
-
-## Using the library without provideChat()
-
-Skipping `provideChat()` costs nothing. The built-in components never read
-`CHAT_CONFIG`, so `ChatComponent`, `ChatInputComponent`, and the rest behave
-identically with or without it. The defaults documented above (`"A"`,
-`"Assistant"`) are values your own components can fall back to when they read the
-token optionally — they are not applied by the library.
-
-Everything the built-in UI does is controlled through component inputs instead:
-generative UI through `[views]`, `[store]`, and `[handlers]` on ``, and
-appearance through the `--tplane-chat-*` custom properties covered in the
-[Theming guide](/docs/chat/guides/theming).
diff --git a/apps/website/content/docs/chat/guides/generative-ui.mdx b/apps/website/content/docs/chat/guides/generative-ui.mdx
index 43a3a2d89..b8c42fe07 100644
--- a/apps/website/content/docs/chat/guides/generative-ui.mdx
+++ b/apps/website/content/docs/chat/guides/generative-ui.mdx
@@ -68,7 +68,6 @@ The last line calls `compile()` with no checkpointer, because this graph is serv
-`provideChat({})` registers the chat composition defaults alongside it.
### The view registry and the shared store
diff --git a/apps/website/content/docs/chat/guides/markdown.mdx b/apps/website/content/docs/chat/guides/markdown.mdx
index 35f01e678..7a930b126 100644
--- a/apps/website/content/docs/chat/guides/markdown.mdx
+++ b/apps/website/content/docs/chat/guides/markdown.mdx
@@ -126,16 +126,14 @@ Use the exported `markdownDocument(content, delivery)` helper to derive it from
It resolves each markdown node type against `MARKDOWN_VIEW_REGISTRY` — a chat-internal DI token exported from `@threadplane/chat` and consumed by the markdown node components.
-Every `` instance provides `MARKDOWN_VIEW_REGISTRY` on its own
-component injector, from `[viewRegistry]` when you pass one and from
-`cacheplaneMarkdownViews` (the full 26-node registry) otherwise.
-
-
-Because the component always provides the token itself, a
-`MARKDOWN_VIEW_REGISTRY` provider in your root or route providers is shadowed and
-never reaches the markdown node components. `[viewRegistry]` on
-`` is the only override point.
-
+Every `` instance provides the resolved registry on its own
+component injector, so the markdown node components below it read one value.
+Resolution runs most-specific-first:
+
+1. The `[viewRegistry]` input on that ``.
+2. A `MARKDOWN_VIEW_REGISTRY` provider on an ancestor injector -- your
+ application root or a route.
+3. `cacheplaneMarkdownViews`, the full 26-node registry.
## Overriding Markdown Components
@@ -177,10 +175,38 @@ export class CustomChatComponent {
Use `overrideViews` when replacing an existing node type. Use `withViews` when adding a brand-new node type that `cacheplaneMarkdownViews` does not yet cover — `withViews` is additive-only and the base registry wins on conflicts. See the [render views API](/docs/render/api/views) for full signatures.
-`` renders assistant markdown through its own ``, and
-does not forward a `[viewRegistry]`. To ship a custom node renderer inside a
-conversation, project your own `ai` message template into
-`` and mount `` there.
+### App-wide override
+
+Provide `MARKDOWN_VIEW_REGISTRY` once and every markdown surface below that
+injector picks it up, including the `` that `` mounts
+for assistant messages. `` forwards nothing, and needs to forward nothing.
+
+```typescript
+// app.config.ts
+import { ApplicationConfig } from '@angular/core';
+import {
+ MARKDOWN_VIEW_REGISTRY,
+ cacheplaneMarkdownViews,
+} from '@threadplane/chat';
+import { overrideViews } from '@threadplane/render';
+import { MyCodeBlockComponent } from './my-code-block.component';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ {
+ provide: MARKDOWN_VIEW_REGISTRY,
+ useValue: overrideViews(cacheplaneMarkdownViews, {
+ 'code-block': MyCodeBlockComponent,
+ }),
+ },
+ ],
+};
+```
+
+The same provider works in a route's `providers` array when only one section of
+the application should render markdown differently. A `[viewRegistry]` input on
+an individual `` still wins over both, so a single surface can
+opt out of the application-wide choice.
## Node-Type Reference
diff --git a/apps/website/content/docs/chat/guides/streaming.mdx b/apps/website/content/docs/chat/guides/streaming.mdx
index 16a033312..eee9a25f0 100644
--- a/apps/website/content/docs/chat/guides/streaming.mdx
+++ b/apps/website/content/docs/chat/guides/streaming.mdx
@@ -20,7 +20,7 @@ Each AI message is processed by a `ContentClassifier` that examines the content
| Prose with inline JSON-render specs | `markdown` | Markdown path, with embedded specs rendered in place |
| Any other text | `markdown` | Rendered as markdown prose |
-Prose that interleaves inline JSON-render specs still classifies as `'markdown'` — the markdown path renders the embedded specs in place. The `ContentType` union also includes `'mixed'`, but `createContentClassifier` does not currently emit it, so treat it as reserved: do not branch on `classifier.type() === 'mixed'` expecting inline-spec content to land there. (`'a2ui'` is covered under [A2UI Content Detection](#a2ui-content-detection) below.)
+Prose that interleaves inline JSON-render specs still classifies as `'markdown'` — the markdown path renders the embedded specs in place. There is no separate type for interleaved content. (`'a2ui'` is covered under [A2UI Content Detection](#a2ui-content-detection) below.)
Each message gets its own classifier instance. Classification happens once per message — the type is determined by the first meaningful character and never changes.
@@ -68,7 +68,7 @@ classifier.dispose();
| Signal | Type | Description |
|--------|------|-------------|
-| `type` | `Signal` | `'pending'`, `'markdown'`, `'json-render'`, `'a2ui'`, or `'mixed'` |
+| `type` | `Signal` | `'pending'`, `'markdown'`, `'json-render'`, or `'a2ui'` |
| `markdown` | `Signal` | Accumulated markdown prose (empty for pure JSON) |
| `spec` | `Signal` | Materialized JSON-render spec with structural sharing |
| `elementStates` | `Signal