Skip to content

Commit 0409b4d

Browse files
bloveclaude
andauthored
docs(render): technical review — fix accuracy across all 18 render pages (#590)
* docs(spec): render docs technical review design Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): render docs technical review implementation plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(render): technical review findings report Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(render): fix a2ui page technical accuracy (envelope names, catalog props) * docs(render): document getFallback + fallback behavior; clarify ChatComponent origin * docs(render): document getFallback + emitEvent; fix signalStateStore signature * docs(render): complete A2uiSurface/A2uiActionMessage type fences in concepts * docs(render): type example spec inputs as input.required<Spec>() * docs(render): mark resolved review findings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent abeb83d commit 0409b4d

13 files changed

Lines changed: 619 additions & 42 deletions

apps/website/content/docs/render/a2ui/catalog.mdx

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Renders a span of text.
3535
| `text` | `string` | The text content to display |
3636

3737
```json
38-
{"id": "greeting", "component": "Text", "text": "Hello, world!"}
38+
{"id": "greeting", "component": {"Text": {"text": {"literalString": "Hello, world!"}}}}
3939
```
4040

4141
### Image
@@ -103,15 +103,14 @@ Arranges children vertically with a flex column layout.
103103

104104
### Card
105105

106-
Renders children inside a rounded bordered card container with an optional title.
106+
Renders children inside a rounded bordered card container.
107107

108108
| A2UI type | Angular component | Selector |
109109
|-----------|-------------------|----------|
110110
| `Card` | `A2uiCardComponent` | `a2ui-card` |
111111

112112
| Prop | Type | Description |
113113
|------|------|-------------|
114-
| `title` | `string` | Optional card heading |
115114
| `childKeys` | `string[]` | Ordered list of child component IDs |
116115
| `spec` | `Spec` | Injected automatically by the render engine |
117116

@@ -149,8 +148,8 @@ Renders a button that dispatches an action when clicked.
149148

150149
| Prop | Type | Description |
151150
|------|------|-------------|
152-
| `label` | `string` | Button label text |
153-
| `variant` | `'primary' \| 'borderless'` | Visual style. Defaults to `'primary'` |
151+
| `childKeys` | `string[]` | Child component IDs whose rendered output is the button's content (e.g., a `Text` label) |
152+
| `primary` | `boolean` | Renders the primary visual style. Defaults to `true` |
154153
| `disabled` | `boolean` | Disables the button when `true` |
155154
| `action` | `A2uiAction` | Action to execute on click (event or function call) |
156155
| `validationResult` | `A2uiValidationResult` | Pre-computed validation result — button is disabled if `valid` is `false` |
@@ -183,7 +182,7 @@ A single-line text input with optional label and placeholder.
183182
| Prop | Type | Description |
184183
|------|------|-------------|
185184
| `label` | `string` | Input label |
186-
| `value` | `string` | Current value (resolved from path reference) |
185+
| `text` | `string` | Current value (resolved from path reference). `value` is a read-only computed alias |
187186
| `placeholder` | `string` | Placeholder text |
188187
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
189188
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
@@ -192,9 +191,12 @@ A single-line text input with optional label and placeholder.
192191
```json
193192
{
194193
"id": "name-field",
195-
"component": "TextField",
196-
"label": "Your name",
197-
"value": {"path": "/name"}
194+
"component": {
195+
"TextField": {
196+
"label": {"literalString": "Your name"},
197+
"text": {"path": "/name"}
198+
}
199+
}
198200
}
199201
```
200202
@@ -211,7 +213,8 @@ A labeled checkbox with two-way binding for its checked state.
211213
| Prop | Type | Description |
212214
|------|------|-------------|
213215
| `label` | `string` | Checkbox label |
214-
| `checked` | `boolean` | Current checked state (resolved from path reference) |
216+
| `value` | `boolean` | Current checked state (resolved from path reference) |
217+
| `checked` | `boolean` | Deprecated back-compat alias for `value` |
215218
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below checkbox when invalid |
216219
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
217220
| `emit` | injected | Event emitter provided by the render engine |
@@ -245,20 +248,27 @@ A date, time, or datetime input with two-way binding.
245248
|------|------|-------------|
246249
| `label` | `string` | Input label |
247250
| `value` | `string` | Current value (resolved from path reference) |
248-
| `inputType` | `'date' \| 'time' \| 'datetime-local'` | HTML input type. Defaults to `'date'` |
251+
| `enableDate` | `boolean` | Include the date portion. Defaults to `true` |
252+
| `enableTime` | `boolean` | Include the time portion. Defaults to `false` |
249253
| `min` | `string` | Minimum allowed value |
250254
| `max` | `string` | Maximum allowed value |
251255
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
252256
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
253257
| `emit` | injected | Event emitter provided by the render engine |
254258
259+
The HTML input type (`date`, `time`, or `datetime-local`) is derived internally from `enableDate` and `enableTime`.
260+
255261
```json
256262
{
257263
"id": "date-field",
258-
"component": "DateTimeInput",
259-
"label": "Appointment date",
260-
"value": {"path": "/appointmentDate"},
261-
"inputType": "date"
264+
"component": {
265+
"DateTimeInput": {
266+
"label": {"literalString": "Appointment date"},
267+
"value": {"path": "/appointmentDate"},
268+
"enableDate": true,
269+
"enableTime": false
270+
}
271+
}
262272
}
263273
```
264274
@@ -274,8 +284,8 @@ A range slider input with two-way binding.
274284
|------|------|-------------|
275285
| `label` | `string` | Slider label |
276286
| `value` | `number` | Current value (bind via `_bindings`) |
277-
| `min` | `number` | Minimum value |
278-
| `max` | `number` | Maximum value |
287+
| `minValue` | `number` | Minimum value |
288+
| `maxValue` | `number` | Maximum value |
279289
| `step` | `number` | Step increment |
280290
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below slider when invalid |
281291
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
@@ -284,12 +294,15 @@ A range slider input with two-way binding.
284294
```json
285295
{
286296
"id": "volume",
287-
"component": "Slider",
288-
"label": "Volume",
289-
"value": {"path": "/volume"},
290-
"min": 0,
291-
"max": 100,
292-
"step": 1
297+
"component": {
298+
"Slider": {
299+
"label": {"literalString": "Volume"},
300+
"value": {"path": "/volume"},
301+
"minValue": 0,
302+
"maxValue": 100,
303+
"step": 1
304+
}
305+
}
293306
}
294307
```
295308

apps/website/content/docs/render/a2ui/surface-store.mdx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,20 @@ Processes one `A2uiMessage` and updates the internal surfaces signal. All four m
3737

3838
| Message type | Behavior |
3939
|--------------|----------|
40-
| `createSurface` | Creates a new `A2uiSurface` entry with an empty component map and data model |
41-
| `updateComponents` | Merges the provided components into the surface's component map by `id` — existing components are replaced, others are kept |
42-
| `updateDataModel` | Applies a JSON Pointer patch to the surface's data model (see below) |
40+
| `surfaceUpdate` | Delivers a surface's components — merges the provided components into the surface's component map by `id` so existing components are replaced and others are kept |
41+
| `dataModelUpdate` | Applies a JSON Pointer patch to the surface's data model (see below) |
42+
| `beginRendering` | Marks the surface root and commits the buffered components and data model into a live surface |
4343
| `deleteSurface` | Removes the surface from the map entirely |
4444

45-
Messages for unknown surface IDs are silently ignored (except `createSurface`, which registers the surface).
45+
Messages for unknown surface IDs are silently ignored until a `surfaceUpdate` introduces the surface and a `beginRendering` commits it.
4646

4747
### `surfaces`
4848

4949
A readonly `Signal<Map<string, A2uiSurface>>` containing all active surfaces. Each map operation produces a new `Map` reference so that Angular's change detection triggers correctly.
5050

5151
### `surface(surfaceId)`
5252

53-
Returns a `computed` signal for a single surface. The signal emits `undefined` until a `createSurface` message registers it, and `undefined` again after `deleteSurface` removes it.
53+
Returns a `computed` signal for a single surface. The signal emits `undefined` until a `beginRendering` message commits it, and `undefined` again after `deleteSurface` removes it.
5454

5555
```typescript
5656
const dashboard = store.surface('dashboard');
@@ -61,9 +61,9 @@ const dashboard = store.surface('dashboard');
6161

6262
The surface's `dataModel` is synchronized into the render-lib `StateStore` when `surfaceToSpec()` converts the surface to a spec. The conversion sets `state: surface.dataModel` on the produced `Spec`, which initializes the render-lib's internal `StateStore` with the surface data. When components with `_bindings` update values (e.g., a text field changing), those updates flow through the render-lib `StateStore`, and each mutation emits a `RenderStateChangeEvent` through the render-lib event system. This means consumers observing the `events` output on `A2uiSurfaceComponent` see all data model changes as typed `RenderStateChangeEvent` objects with `path`, `value`, and `snapshot` fields.
6363

64-
## updateDataModel Semantics
64+
## dataModelUpdate Semantics
6565

66-
The `updateDataModel` message uses JSON Pointer (RFC 6901) paths to address values in the data model.
66+
The `dataModelUpdate` message uses JSON Pointer (RFC 6901) paths to address values in the data model.
6767

6868
| `path` | `value` | Effect |
6969
|--------|---------|--------|
@@ -73,13 +73,13 @@ The `updateDataModel` message uses JSON Pointer (RFC 6901) paths to address valu
7373

7474
```json
7575
// Replace entire model
76-
{"updateDataModel": {"surfaceId": "s1", "value": {"name": "Alice", "score": 42}}}
76+
{"dataModelUpdate": {"surfaceId": "s1", "value": {"name": "Alice", "score": 42}}}
7777
7878
// Set a single field
79-
{"updateDataModel": {"surfaceId": "s1", "path": "/score", "value": 99}}
79+
{"dataModelUpdate": {"surfaceId": "s1", "path": "/score", "value": 99}}
8080
8181
// Delete a field
82-
{"updateDataModel": {"surfaceId": "s1", "path": "/score"}}
82+
{"dataModelUpdate": {"surfaceId": "s1", "path": "/score"}}
8383
```
8484

8585
## Usage with createA2uiMessageParser
@@ -113,7 +113,7 @@ effect(() => {
113113
```
114114

115115
<Callout type="info" title="JSONL envelope format">
116-
The parser expects each line to be wrapped in an envelope object: `{"createSurface": {...}}`, `{"updateComponents": {...}}`, etc. The envelope key determines the message type; its value is the message payload.
116+
The parser expects each line to be wrapped in an envelope object: `{"surfaceUpdate": {...}}`, `{"dataModelUpdate": {...}}`, etc. The envelope key determines the message type; its value is the message payload.
117117
</Callout>
118118

119119
## A2uiSurface Shape

apps/website/content/docs/render/api/define-angular-registry.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ An `AngularRegistry` object with two methods:
3131
```typescript
3232
interface AngularRegistry {
3333
get(name: string): AngularComponentRenderer | undefined;
34+
getFallback(name: string): AngularComponentRenderer | undefined;
3435
names(): string[];
3536
}
3637
```
3738

3839
| Method | Description |
3940
|--------|-------------|
4041
| `get(name)` | Returns the component class for the given type name, or `undefined` if not registered |
42+
| `getFallback(name)` | Returns the configured fallback renderer for a registered name -- the entry's own `fallback`, the library's default fallback if the entry omits one, or `undefined` if the name isn't registered. |
4143
| `names()` | Returns an array of all registered type name strings |
4244

4345
## Usage

apps/website/content/docs/render/api/render-spec-component.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ interface RenderContext {
8383
store: StateStore;
8484
functions?: Record<string, ComputedFunction>;
8585
handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;
86+
emitEvent?: (event: RenderEvent) => void;
8687
loading?: boolean;
8788
}
8889
```

apps/website/content/docs/render/api/signal-state-store.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { signalStateStore } from '@threadplane/render';
1111
## Signature
1212

1313
```typescript
14-
function signalStateStore(initialState?: StateModel): StateStore;
14+
function signalStateStore(initialState: StateModel = {}): StateStore;
1515
```
1616

1717
### Parameters

apps/website/content/docs/render/concepts/json-render-vs-a2ui.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ A surface has an id, catalog id, component map, data model, optional theme, opti
7575
interface A2uiSurface {
7676
surfaceId: string;
7777
catalogId: string;
78+
theme?: A2uiTheme;
79+
sendDataModel?: boolean;
7880
components: Map<string, A2uiComponent>;
7981
dataModel: Record<string, unknown>;
8082
styles?: { font?: string; primaryColor?: string };
@@ -173,6 +175,10 @@ interface A2uiActionMessage {
173175
sourceComponentId: string;
174176
timestamp: string;
175177
context: Record<string, unknown>;
178+
label?: string;
179+
};
180+
metadata?: {
181+
a2uiClientDataModel: A2uiClientDataModel;
176182
};
177183
}
178184
```

apps/website/content/docs/render/getting-started/installation.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export const appConfig: ApplicationConfig = {
8888

8989
```typescript
9090
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
91+
import type { Spec } from '@json-render/core';
9192

9293
@Component({
9394
selector: 'app-text',
@@ -98,7 +99,7 @@ import { Component, ChangeDetectionStrategy, input } from '@angular/core';
9899
export class TextComponent {
99100
readonly label = input<string>('');
100101
readonly childKeys = input<string[]>([]);
101-
readonly spec = input<unknown>(null);
102+
readonly spec = input.required<Spec>();
102103
}
103104
```
104105

apps/website/content/docs/render/getting-started/quickstart.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Let's start with a simple Angular component for the spec to render. Every render
1414
```typescript
1515
// text.component.ts
1616
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
17+
import type { Spec } from '@json-render/core';
1718

1819
@Component({
1920
selector: 'app-text',
@@ -24,7 +25,7 @@ import { Component, ChangeDetectionStrategy, input } from '@angular/core';
2425
export class TextComponent {
2526
readonly label = input<string>('');
2627
readonly childKeys = input<string[]>([]);
27-
readonly spec = input<unknown>(null);
28+
readonly spec = input.required<Spec>();
2829
}
2930
```
3031

apps/website/content/docs/render/guides/events.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ const handlers = {
167167
};
168168
```
169169

170-
This works for handlers passed via `[handlers]` on `<render-spec>`, `provideRender()`, or `ChatComponent`.
170+
This works for handlers passed via `[handlers]` on `<render-spec>`, `provideRender()`, or other render-enabled components like `ChatComponent` (from `@threadplane/chat`).
171171

172172
### Resolution Priority
173173

apps/website/content/docs/render/guides/registry.mdx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,23 @@ export const uiRegistry = defineAngularRegistry({
2121
});
2222
```
2323

24-
The returned `AngularRegistry` object has two methods:
24+
The returned `AngularRegistry` object has three methods:
2525

2626
- `get(name: string)` -- returns the component class for the given type name, or `undefined` if not registered
27+
- `getFallback(name: string)` -- returns the configured fallback renderer for a registered name -- the entry's own `fallback`, or the library's default fallback if the entry omits one, or `undefined` if the name isn't registered
2728
- `names()` -- returns an array of all registered type names
2829

2930
```typescript
30-
uiRegistry.get('Text'); // TextComponent
31-
uiRegistry.get('Unknown'); // undefined
32-
uiRegistry.names(); // ['Text', 'Card', 'Button', 'Container']
31+
uiRegistry.get('Text'); // TextComponent
32+
uiRegistry.get('Unknown'); // undefined
33+
uiRegistry.getFallback('Text'); // fallback renderer (or default)
34+
uiRegistry.names(); // ['Text', 'Card', 'Button', 'Container']
3335
```
3436

37+
### Fallback Rendering
38+
39+
When an element's type is not registered, it renders that type's configured fallback if one exists, and otherwise renders nothing. Fallbacks also fill a transient gap during rendering: while an element's state-bound props are still resolving, the library can render a fallback to give visual feedback until the real component is ready. Once the real component mounts, it stays mounted -- later re-renders never revert to the fallback.
40+
3541
## The Component Input Contract
3642

3743
Every component rendered by `@threadplane/render` receives inputs conforming to the `AngularComponentInputs` interface. Your custom props from the spec are spread as additional inputs alongside the standard ones.

0 commit comments

Comments
 (0)