Skip to content

Commit 0255f87

Browse files
bloveclaude
andauthored
docs(render): six guides teach through their running examples (#1033)
* docs(render): the six render pages leave the pending list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): provideRender teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): registry teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): render-spec component teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): repeat loops teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): specs teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): state store teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): provideRender page — the demo's first spec, the handler type Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): registry page — illustrative entry fence is marked and self-contained Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): render-spec page — context rebuilds on input change; tighter registry region Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): state store page — repeat reads the store directly; five methods; who subscribes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): repeat loops — visible on a repeated element is not evaluated; the demo ships the array half Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): specs page — inert schema fields flagged; every fence parses; the demo's spec quoted verbatim Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(render): render-spec page agrees with its siblings about the agent Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent fee3545 commit 0255f87

19 files changed

Lines changed: 903 additions & 1452 deletions

File tree

Lines changed: 114 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,220 +1,181 @@
1+
---
2+
description: Register the registry, store, computed functions, and handlers that every render surface in an application uses by default.
3+
---
4+
15
# provideRender()
26

3-
Registers global default configuration for `@threadplane/render` via Angular's dependency injection system.
7+
`provideRender()` registers the defaults that every `<render-spec>` in an application falls back to: a component registry, a state store, the named functions a spec may call through `$computed`, and the handlers its actions dispatch to. It returns `EnvironmentProviders`, so it belongs in `ApplicationConfig.providers` or in a `bootstrapApplication()` call. The running example is the computed-functions demo, whose four functions are registered exactly this way, and this page walks the files that make it work.
48

5-
## Import
9+
## What the demo does
610

7-
```typescript
8-
import { provideRender, RENDER_CONFIG } from '@threadplane/render';
9-
```
11+
The Run tab shows a split view. On the left is a live render surface; on the right is the spec JSON that feeds it, arriving character by character. Nothing streams until you press play in the transport bar at the bottom, which also lets you scrub the timeline and change the speed.
1012

11-
## Signature
13+
Press play and the first spec streams in: `hello world` comes out uppercased and `streaming` comes out reversed. Switch to Data Display and an ISO timestamp comes out as a local date while `7 x 6` comes out as `42`. None of those results are in the spec. The spec asks for a function by name, and the four functions registered in `provideRender()` produce the text. The Spec tabs in the header hold three specs -- Text Transforms, Data Display, and Mixed Functions -- and each tab restarts the stream with a different mix of the same four functions.
1214

13-
```typescript
14-
function provideRender(config: RenderConfig): EnvironmentProviders;
15-
```
15+
## How it is built
1616

17-
### Parameters
17+
Four pieces carry the feature: an application config that registers the functions, a registry and store held by the demo component, the `<render-spec>` element that mounts the surface, and a view component that receives the finished value. Open the Code tab to read them in place.
1818

19-
| Parameter | Type | Description |
20-
|-----------|------|-------------|
21-
| `config` | `RenderConfig` | Configuration object with default registry, store, functions, and handlers |
19+
### Registering the computed functions
2220

23-
### Returns
21+
The whole application config is one call. `provideRender()` receives a `functions` map, and every key in it becomes a name a spec may call.
2422

25-
`EnvironmentProviders` -- suitable for use in `ApplicationConfig.providers` or `bootstrapApplication()`.
23+
<ExampleCode file="app.config.ts" title="app.config.ts" />
2624

27-
## RenderConfig
25+
Each function takes a single `args` object and returns a value, which is the `ComputedFunction` contract from `@json-render/core`. The argument names are yours: `formatDate` and `uppercase` and `reverse` read `args['value']`, and `multiply` reads `args['a']` and `args['b']`, so a spec that calls `multiply` has to supply those two keys.
26+
27+
<Callout type="info" title="A computed function runs on every resolution pass">
28+
Prop resolution runs whenever the spec or the bound state changes, which during a stream is many times a second. Keep these functions pure and cheap, and memoize anything expensive outside the function body.
29+
</Callout>
30+
31+
A spec calls one of them with a `$computed` expression in place of a literal prop value. The demo's first spec asks for two of the four:
2832

2933
```typescript
30-
interface RenderConfig {
31-
registry?: AngularRegistry;
32-
store?: StateStore;
33-
functions?: Record<string, ComputedFunction>;
34-
handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;
35-
}
34+
const upper = {
35+
type: 'Value',
36+
props: {
37+
label: 'Uppercase',
38+
value: { $computed: 'uppercase', args: { value: 'hello world' } },
39+
},
40+
};
41+
42+
const reversed = {
43+
type: 'Value',
44+
props: {
45+
label: 'Reversed',
46+
value: { $computed: 'reverse', args: { value: 'streaming' } },
47+
},
48+
};
3649
```
3750

38-
| Property | Type | Description |
39-
|----------|------|-------------|
40-
| `registry` | `AngularRegistry` | Default component registry for all `<render-spec>` instances |
41-
| `store` | `StateStore` | Default state store for all `<render-spec>` instances |
42-
| `functions` | `Record<string, ComputedFunction>` | Default computed functions for `$computed` prop expressions |
43-
| `handlers` | `Record<string, Handler>` | Default event handlers for action dispatch |
51+
The `args` values are themselves prop expressions, so an argument may be a literal, as it is here, or a `$state` reference that reads from the store.
4452

45-
All properties are optional. Only provide the defaults you need.
53+
### The registry and store the surface renders with
4654

47-
## RENDER_CONFIG Token
55+
This example puts its functions in the global config and keeps the other two pieces local. The component builds a registry of three inline view components with `defineAngularRegistry()` and an empty store with `signalStateStore()`.
4856

49-
The configuration is stored in the `RENDER_CONFIG` injection token:
57+
<ExampleCode file="computed-functions.component.ts" region="registry-and-store" title="computed-functions.component.ts -- registry and store" />
5058

51-
```typescript
52-
import { InjectionToken } from '@angular/core';
59+
Both are equally valid in `provideRender()`. Keeping them on the component is what you do when one surface needs its own component set, which is the case here because the three view components exist only for this demo.
5360

54-
const RENDER_CONFIG = new InjectionToken<RenderConfig>('RENDER_CONFIG');
55-
```
61+
### Mounting the surface
5662

57-
You can inject it directly if needed:
63+
`<render-spec>` takes the spec and, because this example did not put them in the global config, the registry and store as inputs. The `loading` input tells the registered components that the spec is still arriving, which is how the skeleton rows appear ahead of the real values.
5864

59-
```typescript
60-
import { inject } from '@angular/core';
61-
import { RENDER_CONFIG } from '@threadplane/render';
65+
<ExampleCode file="computed-functions.component.ts" region="live-output" title="computed-functions.component.ts -- the render surface" />
6266

63-
const config = inject(RENDER_CONFIG, { optional: true });
64-
// null if provideRender() was not called
65-
```
67+
No `functions` input appears here, so the component falls back to the map registered in `provideRender()`.
6668

67-
## Usage
69+
### What a view component receives
6870

69-
### Basic Setup
71+
A view component never sees a `$computed` expression. It declares plain inputs and receives resolved values.
7072

71-
```typescript
72-
// app.config.ts
73-
import { ApplicationConfig } from '@angular/core';
74-
import { provideRender, defineAngularRegistry } from '@threadplane/render';
75-
import { TextComponent } from './components/text.component';
76-
import { CardComponent } from './components/card.component';
77-
78-
export const appConfig: ApplicationConfig = {
79-
providers: [
80-
provideRender({
81-
registry: defineAngularRegistry({
82-
Text: TextComponent,
83-
Card: CardComponent,
84-
}),
85-
}),
86-
],
87-
};
88-
```
73+
<ExampleCode file="computed-functions.component.ts" region="value-inputs" title="computed-functions.component.ts -- the Value view" />
8974

90-
### With Store and Handlers
75+
`label` and `value` are the props the spec sets; `childKeys`, `spec`, `bindings`, `emit`, and `loading` are supplied by the render engine to every registered component. `value` is typed as `unknown` and coerced for display because mid-stream a `$computed` expression can still be half-parsed, and the demo prefers a skeleton row over rendering a partial object.
9176

92-
```typescript
93-
import {
94-
provideRender,
95-
defineAngularRegistry,
96-
signalStateStore,
97-
} from '@threadplane/render';
98-
99-
const globalStore = signalStateStore({ theme: 'light' });
100-
101-
export const appConfig: ApplicationConfig = {
102-
providers: [
103-
provideRender({
104-
registry: defineAngularRegistry({
105-
Text: TextComponent,
106-
Card: CardComponent,
107-
}),
108-
store: globalStore,
109-
functions: {
110-
uppercase: (args: Record<string, unknown>) =>
111-
String(args['text']).toUpperCase(),
112-
},
113-
handlers: {
114-
toggleTheme: () => {
115-
const current = globalStore.get('/theme');
116-
globalStore.set('/theme', current === 'light' ? 'dark' : 'light');
117-
},
118-
},
119-
}),
120-
],
121-
};
122-
```
77+
### The graph that ships with the example
12378

124-
### Registry Only
79+
The example also carries a LangGraph graph. It plays no part in the render pipeline: nothing in the Angular application connects to it, and the spec that streams on the left comes from a local simulator, not from an agent. The graph is a single node that answers questions about computed functions, and it is included here because the Code tab shows it.
12580

126-
If you only need a global registry and want to provide stores per-instance:
81+
<ExampleCode file="graph.py" title="graph.py" />
82+
83+
## Import
12784

12885
```typescript
129-
provideRender({
130-
registry: defineAngularRegistry({
131-
Text: TextComponent,
132-
Card: CardComponent,
133-
Button: ButtonComponent,
134-
}),
135-
})
86+
import { provideRender, RENDER_CONFIG } from '@threadplane/render';
13687
```
13788

138-
## Resolution Priority
139-
140-
`RenderSpecComponent` resolves each configuration value using this priority:
141-
142-
| Priority | Source | Description |
143-
|----------|--------|-------------|
144-
| 1 (highest) | Component input | `[registry]`, `[store]`, `[functions]`, `[handlers]` on `<render-spec>` |
145-
| 2 | `RENDER_CONFIG` | Global defaults from `provideRender()` |
146-
| 3 (lowest) | Internal fallback | Empty registry, internal `signalStateStore()` from `spec.state` |
147-
148-
This means inputs always win over global config:
89+
## Signature
14990

15091
```typescript
151-
// Global config
152-
provideRender({ registry: registryA, store: storeA });
153-
154-
// In template -- registryB overrides registryA, but storeA is still used
155-
<render-spec [spec]="spec" [registry]="registryB" />
92+
function provideRender(config: RenderConfig): EnvironmentProviders;
15693
```
15794

158-
## Global vs Component-Level Config
95+
The returned providers carry the `RENDER_CONFIG` value and the internal lifecycle service that coordinates mount and unmount events across dynamically rendered components. Call `provideRender()` once per application.
15996

160-
<Tabs>
161-
<Tab label="Global config">
162-
163-
Use `provideRender()` when you want shared defaults across your entire application:
97+
## RenderConfig
16498

16599
```typescript
166-
// All <render-spec> instances use this registry by default
167-
provideRender({ registry: myRegistry })
100+
interface RenderConfig {
101+
telemetry?: boolean;
102+
registry?: AngularRegistry;
103+
store?: StateStore;
104+
functions?: Record<string, ComputedFunction>;
105+
handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;
106+
}
168107
```
169108

170-
This is ideal when you have a single component library that all specs should use.
109+
| Property | Type | Description |
110+
|----------|------|-------------|
111+
| `telemetry` | `boolean` | Set false to disable automatic development browser collection for this render tree |
112+
| `registry` | `AngularRegistry` | Default component registry for all `<render-spec>` instances |
113+
| `store` | `StateStore` | Default state store for all `<render-spec>` instances |
114+
| `functions` | `Record<string, ComputedFunction>` | Named functions a spec may call through `$computed` |
115+
| `handlers` | `Record<string, (params: Record<string, unknown>) => unknown \| Promise<unknown>>` | Named action handlers invoked when interactive elements fire |
116+
117+
Every property is optional. Provide only the defaults you need, as the example does with `functions` alone.
171118

172-
</Tab>
173-
<Tab label="Component-level config">
119+
## The RENDER_CONFIG token
174120

175-
Pass inputs directly to `<render-spec>` when different parts of your app need different configurations:
121+
The configuration object is stored under the `RENDER_CONFIG` injection token, which is exported alongside `provideRender()`. Inject it when you need to read the defaults yourself:
176122

177123
```typescript
178-
// Dashboard uses one registry
179-
<render-spec [spec]="dashboardSpec" [registry]="dashboardRegistry" />
124+
import { inject } from '@angular/core';
125+
import { RENDER_CONFIG } from '@threadplane/render';
180126

181-
// Form builder uses a different registry
182-
<render-spec [spec]="formSpec" [registry]="formRegistry" />
127+
const config = inject(RENDER_CONFIG, { optional: true });
128+
// null when provideRender() was not called
183129
```
184130

185-
This is useful when you have multiple rendering contexts with different component sets.
131+
## Resolution priority
132+
133+
`RenderSpecComponent` resolves each value independently, and an input always wins over the global default.
186134

187-
</Tab>
188-
<Tab label="Combined">
135+
| Value | Priority |
136+
|-------|----------|
137+
| `registry` | `[registry]` input, then `RENDER_CONFIG`, then the `VIEW_REGISTRY` token from `provideViews()`, then an empty registry |
138+
| `store` | `[store]` input, then `RENDER_CONFIG`, then an internal `signalStateStore()` seeded from `spec.state` |
139+
| `functions` | `[functions]` input, then `RENDER_CONFIG` |
140+
| `handlers` | `[handlers]` input, then `RENDER_CONFIG` |
189141

190-
Use both for a layered approach -- global defaults with per-instance overrides:
142+
Because the fallbacks are per value, a surface may take one piece from the global config and override another. The example does exactly that in reverse: it registers `functions` globally and passes `registry` and `store` as inputs.
191143

192144
```typescript
193-
// Global: shared registry and handlers
145+
// Global: one registry and one handler map for the whole application
194146
provideRender({
195147
registry: baseRegistry,
196-
handlers: { log: (p) => console.log(p) },
148+
handlers: { log: (params: Record<string, unknown>) => console.log(params) },
197149
});
150+
```
198151

199-
// Component: override store, keep global registry and handlers
152+
```html
153+
<!-- This surface keeps the global registry and handlers, but binds its own store -->
200154
<render-spec [spec]="spec" [store]="localStore" />
201155
```
202156

203-
</Tab>
204-
</Tabs>
157+
## Calling provideRender is optional
205158

206-
## Optional Provider
207-
208-
`provideRender()` is not required. If you skip it, `RenderSpecComponent` requires you to pass `registry` and `store` as inputs (or relies on the internal store fallback).
159+
`provideRender()` is not required. Skip it and `<render-spec>` still renders, as long as the registry reaches it another way -- an input, or the `VIEW_REGISTRY` token -- and the internal store fallback covers state.
209160

210161
```html
211-
<!-- Works without provideRender() -- all config via inputs -->
162+
<!-- Works without provideRender() -- every value arrives as an input -->
212163
<render-spec [spec]="spec" [registry]="registry" [store]="store" />
213164
```
214165

215-
## Related
216-
217-
- [RenderSpecComponent API](/docs/render/api/render-spec-component) -- how the component uses `RENDER_CONFIG`
218-
- [defineAngularRegistry()](/docs/render/api/define-angular-registry) -- creating registries to pass to config
219-
- [signalStateStore()](/docs/render/api/signal-state-store) -- creating stores to pass to config
220-
- [Installation](/docs/render/getting-started/installation) -- initial setup with `provideRender()`
166+
## What's Next
167+
168+
<CardGroup cols={2}>
169+
<Card title="RenderSpecComponent" href="/docs/render/api/render-spec-component">
170+
The inputs and outputs of the element that mounts a spec.
171+
</Card>
172+
<Card title="defineAngularRegistry()" href="/docs/render/api/define-angular-registry">
173+
Build the registry you pass to the config or to an input.
174+
</Card>
175+
<Card title="signalStateStore()" href="/docs/render/api/signal-state-store">
176+
Create the signal-backed store that `$bindState` paths read and write.
177+
</Card>
178+
<Card title="Specs and elements" href="/docs/render/guides/specs">
179+
The spec shape that `$computed` expressions live in.
180+
</Card>
181+
</CardGroup>

0 commit comments

Comments
 (0)