@urbicon-ui/blocks 6.40.3 → 6.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Calendar/calendar.variants.d.ts +63 -63
- package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
- package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
- package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
- package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
- package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
- package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
- package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
- package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
- package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
- package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
- package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
- package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
- package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
- package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
- package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
- package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
- package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
- package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
- package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
- package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
- package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
- package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
- package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
- package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
- package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
- package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
- package/dist/components/Chat/A2UIView/index.d.ts +35 -2
- package/dist/components/Chat/A2UIView/index.js +13 -0
- package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
- package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
- package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
- package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
- package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +19 -19
- package/dist/components/CopyButton/copy-button.variants.d.ts +4 -4
- package/dist/components/FileUpload/FileUpload.svelte +8 -5
- package/dist/components/FileUpload/fileUpload.variants.js +15 -4
- package/dist/components/PinInput/PinInput.svelte +1 -0
- package/dist/components/PinInput/pin-input.variants.js +14 -1
- package/dist/components/TimeInput/time-input.variants.js +16 -1
- package/dist/i18n/index.d.ts +2 -398
- package/dist/internal/field-chrome.d.ts +18 -1
- package/dist/internal/field-chrome.js +18 -1
- package/dist/primitives/Alert/alert.variants.d.ts +8 -8
- package/dist/primitives/Avatar/avatar.variants.js +4 -1
- package/dist/primitives/Badge/badge.variants.js +11 -2
- package/dist/primitives/Button/button.variants.js +4 -1
- package/dist/primitives/Card/card.variants.js +4 -1
- package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
- package/dist/primitives/Combobox/Combobox.svelte +9 -1
- package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
- package/dist/primitives/Combobox/combobox.variants.js +32 -3
- package/dist/primitives/Combobox/index.d.ts +8 -2
- package/dist/primitives/Dialog/dialog.variants.js +4 -1
- package/dist/primitives/Drawer/drawer.variants.js +4 -1
- package/dist/primitives/Input/input.variants.js +17 -1
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
- package/dist/primitives/Menu/menu.variants.js +5 -1
- package/dist/primitives/Progress/progress.variants.js +5 -2
- package/dist/primitives/RadioGroup/radioGroup.variants.js +68 -9
- package/dist/primitives/Select/select.variants.js +13 -4
- package/dist/primitives/Slider/Slider.svelte +4 -1
- package/dist/primitives/Slider/slider.variants.js +9 -2
- package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
- package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
- package/dist/primitives/Textarea/textarea.variants.js +15 -1
- package/dist/primitives/Toast/Toaster.svelte +6 -9
- package/dist/primitives/Toast/toast.variants.d.ts +12 -12
- package/dist/primitives/Toast/toast.variants.js +5 -1
- package/dist/primitives/Toggle/toggle.variants.js +92 -18
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
- package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
- package/dist/style/themes/index.css +8 -2
- package/dist/utils/guide.svelte.js +16 -4
- package/dist/utils/persistent-state.svelte.d.ts +25 -1
- package/dist/utils/persistent-state.svelte.js +98 -10
- package/docs/MIGRATION-v5.md +29 -0
- package/package.json +3 -3
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* System-prompt generator for
|
|
3
|
-
* description straight from
|
|
4
|
-
* never drift (a drift-guard test asserts every component and enum
|
|
5
|
-
* covered). Pure TS, no Svelte — a server building the prompt has no
|
|
2
|
+
* System-prompt generator for an A2UI catalog. Renders the agent-facing catalog
|
|
3
|
+
* description straight from the catalog's registry, so the prompt and the
|
|
4
|
+
* validator can never drift (a drift-guard test asserts every component and enum
|
|
5
|
+
* value is covered). Pure TS, no Svelte — a server building the prompt has no
|
|
6
|
+
* DOM, and this module never imports catalog VALUE code from `urbicon/` (the
|
|
7
|
+
* caller passes the spec), so it stays out of the Basic bundle.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* With no options the output is the Basic-catalog prompt, byte-identical to
|
|
10
|
+
* before the catalog refactor. Pass `catalog` to render a richer catalog: when
|
|
11
|
+
* its specs carry categories / shared axes (the Urbicon catalog), the prompt
|
|
12
|
+
* grows a "Shared axes" section, groups components by category, compresses
|
|
13
|
+
* shared-axis props, and adds catalog-specific don'ts — all gated on features
|
|
14
|
+
* the Basic catalog does not have, so the default stays untouched.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately excludes any TRANSPORT section (how envelopes reach the client)
|
|
17
|
+
* and any data-schema section: those are app-specific and each app appends them
|
|
18
|
+
* (the chat-demo appends its fenced `a2ui` protocol + `a2uiDataSchemaSection`).
|
|
10
19
|
*/
|
|
20
|
+
import { type A2uiCatalogSpec } from './a2ui-catalog.js';
|
|
11
21
|
/**
|
|
12
|
-
* Build the A2UI system prompt
|
|
13
|
-
*
|
|
14
|
-
*
|
|
22
|
+
* Build the A2UI system prompt. Pass `catalogId` to override the advertised
|
|
23
|
+
* `createSurface` catalog id, and `catalog` to render a specific catalog's
|
|
24
|
+
* components (defaults to the Basic catalog — byte-identical output).
|
|
15
25
|
*/
|
|
16
26
|
export declare function a2uiSystemPrompt(options?: {
|
|
17
27
|
catalogId?: string;
|
|
28
|
+
catalog?: A2uiCatalogSpec;
|
|
18
29
|
}): string;
|
|
@@ -1,17 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* System-prompt generator for
|
|
3
|
-
* description straight from
|
|
4
|
-
* never drift (a drift-guard test asserts every component and enum
|
|
5
|
-
* covered). Pure TS, no Svelte — a server building the prompt has no
|
|
2
|
+
* System-prompt generator for an A2UI catalog. Renders the agent-facing catalog
|
|
3
|
+
* description straight from the catalog's registry, so the prompt and the
|
|
4
|
+
* validator can never drift (a drift-guard test asserts every component and enum
|
|
5
|
+
* value is covered). Pure TS, no Svelte — a server building the prompt has no
|
|
6
|
+
* DOM, and this module never imports catalog VALUE code from `urbicon/` (the
|
|
7
|
+
* caller passes the spec), so it stays out of the Basic bundle.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* With no options the output is the Basic-catalog prompt, byte-identical to
|
|
10
|
+
* before the catalog refactor. Pass `catalog` to render a richer catalog: when
|
|
11
|
+
* its specs carry categories / shared axes (the Urbicon catalog), the prompt
|
|
12
|
+
* grows a "Shared axes" section, groups components by category, compresses
|
|
13
|
+
* shared-axis props, and adds catalog-specific don'ts — all gated on features
|
|
14
|
+
* the Basic catalog does not have, so the default stays untouched.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately excludes any TRANSPORT section (how envelopes reach the client)
|
|
17
|
+
* and any data-schema section: those are app-specific and each app appends them
|
|
18
|
+
* (the chat-demo appends its fenced `a2ui` protocol + `a2uiDataSchemaSection`).
|
|
10
19
|
*/
|
|
11
|
-
import {
|
|
20
|
+
import { basicA2uiCatalogSpec } from './a2ui-catalog.js';
|
|
12
21
|
/** Props documented once in the shared section rather than per component. */
|
|
13
22
|
const COMMON_PROP_KEYS = new Set(['accessibility', 'weight']);
|
|
23
|
+
/** Category order for grouped rendering; unknown categories follow, sorted. */
|
|
24
|
+
const CATEGORY_ORDER = ['Layout', 'Text', 'Form', 'Status', 'Media'];
|
|
25
|
+
function sharedAxisOf(spec) {
|
|
26
|
+
return spec.sharedAxis;
|
|
27
|
+
}
|
|
28
|
+
function categoryOf(spec) {
|
|
29
|
+
return spec.category;
|
|
30
|
+
}
|
|
31
|
+
/** Collect the distinct shared axes (name → value list) referenced across the registry. */
|
|
32
|
+
function collectSharedAxes(registry) {
|
|
33
|
+
const axes = new Map();
|
|
34
|
+
for (const spec of Object.values(registry)) {
|
|
35
|
+
for (const propSpec of Object.values(spec.props)) {
|
|
36
|
+
const shared = sharedAxisOf(propSpec);
|
|
37
|
+
if (shared && propSpec.values && !axes.has(shared))
|
|
38
|
+
axes.set(shared, propSpec.values);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return axes;
|
|
42
|
+
}
|
|
14
43
|
function renderKind(spec) {
|
|
44
|
+
const shared = sharedAxisOf(spec);
|
|
45
|
+
if (shared)
|
|
46
|
+
return `shared ${shared} (see Shared axes)`;
|
|
15
47
|
switch (spec.kind) {
|
|
16
48
|
case 'enum':
|
|
17
49
|
case 'icon':
|
|
@@ -28,10 +60,14 @@ function renderKind(spec) {
|
|
|
28
60
|
return 'component id (string)';
|
|
29
61
|
case 'childList':
|
|
30
62
|
return 'array of ids OR template { componentId, path }';
|
|
63
|
+
case 'labeledChildren':
|
|
64
|
+
return 'array of { label, child } (child is a component id)';
|
|
31
65
|
case 'action':
|
|
32
66
|
return 'action { event: { name, context } }';
|
|
33
67
|
case 'options':
|
|
34
|
-
return
|
|
68
|
+
return spec.dynamic
|
|
69
|
+
? 'array of { label, value }, or { path } binding to such an array'
|
|
70
|
+
: 'array of { label, value }';
|
|
35
71
|
case 'accessibility':
|
|
36
72
|
return 'object { label?, description? }';
|
|
37
73
|
}
|
|
@@ -44,9 +80,8 @@ function renderProp(key, spec) {
|
|
|
44
80
|
flags.push(`default ${JSON.stringify(spec.default)}`);
|
|
45
81
|
return ` - ${key} (${flags.join('; ')}): ${spec.description}`;
|
|
46
82
|
}
|
|
47
|
-
function renderComponent(name) {
|
|
48
|
-
const
|
|
49
|
-
const lines = [`### ${name}`, spec.description, 'Props:'];
|
|
83
|
+
function renderComponent(name, spec, heading) {
|
|
84
|
+
const lines = [`${heading} ${name}`, spec.description, 'Props:'];
|
|
50
85
|
for (const [key, propSpec] of Object.entries(spec.props)) {
|
|
51
86
|
if (COMMON_PROP_KEYS.has(key) || propSpec.promptHidden)
|
|
52
87
|
continue;
|
|
@@ -54,14 +89,53 @@ function renderComponent(name) {
|
|
|
54
89
|
}
|
|
55
90
|
return lines.join('\n');
|
|
56
91
|
}
|
|
92
|
+
/** Flat component list — the Basic rendering (byte-identical). */
|
|
93
|
+
function renderFlatComponents(registry, names) {
|
|
94
|
+
return [
|
|
95
|
+
'## Components',
|
|
96
|
+
'',
|
|
97
|
+
...names.map((name) => renderComponent(name, registry[name], '###'))
|
|
98
|
+
].join('\n\n');
|
|
99
|
+
}
|
|
100
|
+
/** Category-grouped component list — the Urbicon rendering. */
|
|
101
|
+
function renderGroupedComponents(registry, names) {
|
|
102
|
+
const byCategory = new Map();
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
const category = categoryOf(registry[name]) ?? 'Other';
|
|
105
|
+
let list = byCategory.get(category);
|
|
106
|
+
if (!list) {
|
|
107
|
+
list = [];
|
|
108
|
+
byCategory.set(category, list);
|
|
109
|
+
}
|
|
110
|
+
list.push(name);
|
|
111
|
+
}
|
|
112
|
+
const known = CATEGORY_ORDER.filter((category) => byCategory.has(category));
|
|
113
|
+
const extra = [...byCategory.keys()]
|
|
114
|
+
.filter((category) => !CATEGORY_ORDER.includes(category))
|
|
115
|
+
.sort();
|
|
116
|
+
const parts = ['## Components'];
|
|
117
|
+
for (const category of [...known, ...extra]) {
|
|
118
|
+
parts.push(`### ${category}`);
|
|
119
|
+
for (const name of byCategory.get(category) ?? []) {
|
|
120
|
+
parts.push(renderComponent(name, registry[name], '####'));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return parts.join('\n\n');
|
|
124
|
+
}
|
|
57
125
|
/**
|
|
58
|
-
* Build the A2UI system prompt
|
|
59
|
-
*
|
|
60
|
-
*
|
|
126
|
+
* Build the A2UI system prompt. Pass `catalogId` to override the advertised
|
|
127
|
+
* `createSurface` catalog id, and `catalog` to render a specific catalog's
|
|
128
|
+
* components (defaults to the Basic catalog — byte-identical output).
|
|
61
129
|
*/
|
|
62
130
|
export function a2uiSystemPrompt(options) {
|
|
63
|
-
const
|
|
64
|
-
const
|
|
131
|
+
const catalog = options?.catalog ?? basicA2uiCatalogSpec;
|
|
132
|
+
const catalogId = options?.catalogId ?? catalog.catalogId;
|
|
133
|
+
const registry = catalog.registry;
|
|
134
|
+
const componentNames = Object.keys(registry);
|
|
135
|
+
const sharedAxes = collectSharedAxes(registry);
|
|
136
|
+
const grouped = componentNames.some((name) => categoryOf(registry[name]) !== undefined);
|
|
137
|
+
const hasRichText = 'RichText' in registry;
|
|
138
|
+
const hasSelectArray = 'Select' in registry && registry.Select.props.value?.kind === 'stringList';
|
|
65
139
|
const sections = [];
|
|
66
140
|
sections.push([
|
|
67
141
|
'# A2UI rendering',
|
|
@@ -99,6 +173,46 @@ export function a2uiSystemPrompt(options) {
|
|
|
99
173
|
' The client buffers until "root" exists and shows placeholders for',
|
|
100
174
|
' children that have not arrived yet.'
|
|
101
175
|
].join('\n'));
|
|
176
|
+
sections.push([
|
|
177
|
+
'## Surfaces live on, and you only hear from them when the user acts',
|
|
178
|
+
'',
|
|
179
|
+
'A surfaceId is unique for as long as the client runs, and a surface stays',
|
|
180
|
+
'alive and editable after the reply that created it. Send further envelopes',
|
|
181
|
+
'for the SAME surfaceId later and it updates in place, keeping what the user',
|
|
182
|
+
'typed. Prefer ONE surface that grows over a chain of near-identical ones.',
|
|
183
|
+
'',
|
|
184
|
+
'- updateDataModel to fill in a value you just learned; updateComponents to',
|
|
185
|
+
' add or change controls. Re-sending a component id REPLACES it — to append',
|
|
186
|
+
' a child, re-send its container with the full children list.',
|
|
187
|
+
'- Never createSurface twice for one id, and never rebuild an equivalent',
|
|
188
|
+
" surface under a new id to change a field: both discard the user's input.",
|
|
189
|
+
'- deleteSurface when a surface has served its purpose.',
|
|
190
|
+
'',
|
|
191
|
+
'But you are NOT watching the form. The client speaks to you in exactly one',
|
|
192
|
+
'case: the user activates a control carrying an `action`. Typing, picking a',
|
|
193
|
+
'date, choosing an option — all of it updates the data model LOCALLY and',
|
|
194
|
+
'tells you nothing.',
|
|
195
|
+
'',
|
|
196
|
+
'- Anything you mean to fill in later needs a control the user can press to',
|
|
197
|
+
' ask you for it ("Show available times"), with the fields it depends on in',
|
|
198
|
+
' its action.context. An empty area with no trigger is a dead end — never',
|
|
199
|
+
' write "pick a date and I will show the times" without one.',
|
|
200
|
+
'- But do NOT route a plain choice through you. A control that only records',
|
|
201
|
+
' what the user picked should BIND: a single-choice input with',
|
|
202
|
+
' value: { path } highlights the selection instantly, no round-trip. A row',
|
|
203
|
+
' of Buttons that each set a value is the wrong shape — buttons show no',
|
|
204
|
+
' selected state, so the choice looks lost, and every tap costs a turn.',
|
|
205
|
+
' Reach for an action only when YOU must fetch or commit something.',
|
|
206
|
+
'- Options you fetched belong in the data model: write them with',
|
|
207
|
+
' updateDataModel and bind options: { path } (see the prop docs), rather',
|
|
208
|
+
' than rewriting the component for every result.',
|
|
209
|
+
'- Set "sendDataModel": true on createSurface: every action then carries the',
|
|
210
|
+
" surface's ENTIRE data model, so you see what the user filled in even for",
|
|
211
|
+
' fields you left out of context. Prefer it on any form you will act on.',
|
|
212
|
+
'- If an action arrives with required fields still empty, do NOT carry it',
|
|
213
|
+
' out — patch a short message next to the offending field instead. You are',
|
|
214
|
+
' the only validator; the client submits whatever it is given.'
|
|
215
|
+
].join('\n'));
|
|
102
216
|
sections.push([
|
|
103
217
|
'## Data bindings',
|
|
104
218
|
'',
|
|
@@ -120,7 +234,27 @@ export function a2uiSystemPrompt(options) {
|
|
|
120
234
|
'"context": { ... } } }. Context values may be literals or { path } bindings and',
|
|
121
235
|
'are resolved before dispatch. Do NOT use local function-call actions.'
|
|
122
236
|
].join('\n'));
|
|
123
|
-
|
|
237
|
+
if (sharedAxes.size > 0) {
|
|
238
|
+
const lines = [
|
|
239
|
+
'## Shared axes',
|
|
240
|
+
'',
|
|
241
|
+
'Several components share these value sets. A prop documented as "shared intent"',
|
|
242
|
+
'or "shared size" accepts exactly the values listed here — pick one:'
|
|
243
|
+
];
|
|
244
|
+
for (const axis of ['intent', 'size']) {
|
|
245
|
+
const values = sharedAxes.get(axis);
|
|
246
|
+
if (values)
|
|
247
|
+
lines.push(`- ${axis}: ${values.join(' | ')}`);
|
|
248
|
+
}
|
|
249
|
+
for (const [axis, values] of sharedAxes) {
|
|
250
|
+
if (axis !== 'intent' && axis !== 'size')
|
|
251
|
+
lines.push(`- ${axis}: ${values.join(' | ')}`);
|
|
252
|
+
}
|
|
253
|
+
sections.push(lines.join('\n'));
|
|
254
|
+
}
|
|
255
|
+
sections.push(grouped
|
|
256
|
+
? renderGroupedComponents(registry, componentNames)
|
|
257
|
+
: renderFlatComponents(registry, componentNames));
|
|
124
258
|
sections.push([
|
|
125
259
|
'## Common props (every component)',
|
|
126
260
|
'',
|
|
@@ -130,18 +264,30 @@ export function a2uiSystemPrompt(options) {
|
|
|
130
264
|
sections.push([
|
|
131
265
|
'## Icons',
|
|
132
266
|
'',
|
|
133
|
-
`Icon.name must be one of: ${
|
|
267
|
+
`Icon.name must be one of: ${catalog.iconNames.join(', ')}. Unknown names render a`,
|
|
134
268
|
'fallback glyph.'
|
|
135
269
|
].join('\n'));
|
|
136
|
-
|
|
270
|
+
const donts = [
|
|
137
271
|
"## Don'ts",
|
|
138
272
|
'',
|
|
139
273
|
'- Do NOT define children inline; reference them by id.',
|
|
140
274
|
'- Do NOT use more than one "root".',
|
|
141
275
|
'- Do NOT use function-call bindings or local function-call actions.',
|
|
142
|
-
`- Do NOT use unsupported components: ${[...
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
276
|
+
`- Do NOT use unsupported components: ${[...catalog.unsupportedComponents].join(', ')}.`
|
|
277
|
+
];
|
|
278
|
+
if (hasRichText) {
|
|
279
|
+
donts.push('- Text renders PLAIN — a literal "**x**" shows the asterisks. For bold/italic/', ' lists/links/code use RichText, never Text.');
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
donts.push('- Do NOT put HTML, images or links in Text markdown (inline bold/italic/code only).');
|
|
283
|
+
}
|
|
284
|
+
if (hasSelectArray) {
|
|
285
|
+
donts.push('- A Select `value` is ALWAYS a string ARRAY — even single-select writes a', ' one-element array. RadioGroup `value` is a single string.');
|
|
286
|
+
}
|
|
287
|
+
if (hasRichText) {
|
|
288
|
+
donts.push('- Do NOT set an intent on a form field for errors — use its `error` string prop.');
|
|
289
|
+
}
|
|
290
|
+
donts.push('- Images are blocked by default — always give Image a meaningful description.');
|
|
291
|
+
sections.push(donts.join('\n'));
|
|
146
292
|
return sections.join('\n\n');
|
|
147
293
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* is written to be read by an agent — it is emitted verbatim into the prompt.
|
|
14
14
|
*/
|
|
15
15
|
/** How a prop value is shaped and validated. */
|
|
16
|
-
export type A2uiPropKind = 'string' | 'number' | 'boolean' | 'stringList' | 'enum' | 'childId' | 'childList' | 'action' | 'options' | 'icon' | 'accessibility';
|
|
16
|
+
export type A2uiPropKind = 'string' | 'number' | 'boolean' | 'stringList' | 'enum' | 'childId' | 'childList' | 'labeledChildren' | 'action' | 'options' | 'icon' | 'accessibility';
|
|
17
17
|
export interface A2uiPropSpec {
|
|
18
18
|
kind: A2uiPropKind;
|
|
19
19
|
/** Required on the component. @default false */
|
|
@@ -283,7 +283,8 @@ export const A2UI_REGISTRY = Object.freeze({
|
|
|
283
283
|
options: {
|
|
284
284
|
kind: 'options',
|
|
285
285
|
required: true,
|
|
286
|
-
|
|
286
|
+
dynamic: true,
|
|
287
|
+
description: 'The available options as an array of { label, value } objects; `value` is a stable string. Bind it when the options come from data you fetched.'
|
|
287
288
|
},
|
|
288
289
|
value: {
|
|
289
290
|
kind: 'stringList',
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* still produced by `collectGraphIssues` in `a2ui-validate.ts`; this walk only
|
|
15
15
|
* shapes what renders.
|
|
16
16
|
*/
|
|
17
|
+
import type { Component, Snippet } from 'svelte';
|
|
17
18
|
import type { IconComponent } from '../../../icons/index.js';
|
|
18
19
|
import type { MarkdownUrlPolicy } from '../markdown/types.js';
|
|
19
20
|
import type { A2uiActionEvent, A2uiValidationIssue } from './a2ui.types.js';
|
|
@@ -37,6 +38,12 @@ export interface A2uiRenderNode {
|
|
|
37
38
|
children: A2uiRenderNode[];
|
|
38
39
|
/** flex-grow, present only when this node is a direct Row/Column child with a finite `weight`. */
|
|
39
40
|
weight?: number;
|
|
41
|
+
/**
|
|
42
|
+
* The parent prop this child came from (e.g. Card `header`/`footer`/`child`).
|
|
43
|
+
* Lets a dispatcher with several named childId slots pick the right child by
|
|
44
|
+
* name instead of position; single-slot / list components ignore it.
|
|
45
|
+
*/
|
|
46
|
+
slot?: string;
|
|
40
47
|
}
|
|
41
48
|
/** Static text labels threaded to the node dispatcher (i18n comes from A2UIView props). */
|
|
42
49
|
export interface A2uiRenderLabels {
|
|
@@ -60,6 +67,12 @@ export interface A2uiRenderContext {
|
|
|
60
67
|
inline: boolean;
|
|
61
68
|
surfaceId: string;
|
|
62
69
|
onAction: ((event: A2uiActionEvent) => void) | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* The data model to attach to a dispatched action, or `undefined` when the
|
|
72
|
+
* surface did not ask for it (`createSurface.sendDataModel`). Read as a
|
|
73
|
+
* function so the action carries the state at CLICK time, not at render time.
|
|
74
|
+
*/
|
|
75
|
+
actionDataModel: (() => unknown) | undefined;
|
|
63
76
|
/** Resolve a Dynamic value (literal | { path } | function-call) against the model in `scope`. */
|
|
64
77
|
resolve: (value: unknown, scopePrefix: string | undefined) => {
|
|
65
78
|
value: unknown;
|
|
@@ -74,6 +87,23 @@ export interface A2uiRenderContext {
|
|
|
74
87
|
/** Fallback glyph for an unmapped icon name. */
|
|
75
88
|
fallbackIcon: IconComponent;
|
|
76
89
|
labels: A2uiRenderLabels;
|
|
90
|
+
/**
|
|
91
|
+
* The catalog's recursive node dispatcher (Basic → `A2UINode`; a custom
|
|
92
|
+
* catalog → its own dispatcher). A2UIView's `renderNode` snippet renders
|
|
93
|
+
* THIS rather than importing one dispatcher directly, so each surface renders
|
|
94
|
+
* through the component that matches its catalog.
|
|
95
|
+
*/
|
|
96
|
+
nodeComponent: Component<A2uiNodeProps>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Props of a catalog node dispatcher. Every dispatcher (Basic/Urbicon/…) takes
|
|
100
|
+
* the same triple: the node, its render context, and the self-referencing
|
|
101
|
+
* `renderChild` snippet A2UIView threads down for bounded recursion.
|
|
102
|
+
*/
|
|
103
|
+
export interface A2uiNodeProps {
|
|
104
|
+
node: A2uiRenderNode;
|
|
105
|
+
context: A2uiRenderContext;
|
|
106
|
+
renderChild: Snippet<[A2uiRenderNode, A2uiRenderContext]>;
|
|
77
107
|
}
|
|
78
108
|
/**
|
|
79
109
|
* Resolve a `{ path }` data binding to the ABSOLUTE JSON Pointer it addresses,
|
|
@@ -58,33 +58,67 @@ export function toInputString(value) {
|
|
|
58
58
|
return String(value);
|
|
59
59
|
return '';
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a component's child references by walking its spec's child-bearing
|
|
63
|
+
* props (`childId` = one id; `childList` = an id array or a `{ componentId,
|
|
64
|
+
* path }` template; `labeledChildren` = a `[{ label, child }]` list). Kind-
|
|
65
|
+
* driven rather than reading fixed `child`/`children` keys, so a catalog may
|
|
66
|
+
* name its child slots freely (Card header/footer, Section child, Accordion
|
|
67
|
+
* items, …). Behaviour is unchanged for the Basic catalog, whose only child
|
|
68
|
+
* props ARE `child`/`children`. One ref is pushed per `labeledChildren` item
|
|
69
|
+
* (even a dangling one) so the dispatcher can zip labels with resolved children
|
|
70
|
+
* by index.
|
|
71
|
+
*/
|
|
61
72
|
function childRefs(instance, scopePrefix, state) {
|
|
62
73
|
const out = [];
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
out
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (typeof
|
|
70
|
-
out.push({ id:
|
|
74
|
+
const spec = state.registry[instance.component];
|
|
75
|
+
if (!spec)
|
|
76
|
+
return out;
|
|
77
|
+
for (const [key, propSpec] of Object.entries(spec.props)) {
|
|
78
|
+
if (propSpec.kind === 'childId') {
|
|
79
|
+
const single = instance.props.get(key);
|
|
80
|
+
if (typeof single === 'string')
|
|
81
|
+
out.push({ id: single, scope: scopePrefix, slot: key });
|
|
71
82
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
for (let i = 0; i < list.length; i++) {
|
|
82
|
-
out.push({ id: template.componentId, scope: `${absPath}/${i}` });
|
|
83
|
+
else if (propSpec.kind === 'labeledChildren') {
|
|
84
|
+
const items = instance.props.get(key);
|
|
85
|
+
if (Array.isArray(items)) {
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
if (item !== null && typeof item === 'object') {
|
|
88
|
+
const child = item.child;
|
|
89
|
+
if (typeof child === 'string')
|
|
90
|
+
out.push({ id: child, scope: scopePrefix, slot: key });
|
|
91
|
+
}
|
|
83
92
|
if (out.length > state.maxNodes)
|
|
84
|
-
break;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (propSpec.kind === 'childList') {
|
|
98
|
+
const children = instance.props.get(key);
|
|
99
|
+
if (Array.isArray(children)) {
|
|
100
|
+
for (const childId of children) {
|
|
101
|
+
if (typeof childId === 'string')
|
|
102
|
+
out.push({ id: childId, scope: scopePrefix, slot: key });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (children !== null && typeof children === 'object') {
|
|
106
|
+
const template = children;
|
|
107
|
+
if (typeof template.componentId === 'string' && typeof template.path === 'string') {
|
|
108
|
+
const absPath = template.path.startsWith('/')
|
|
109
|
+
? template.path
|
|
110
|
+
: `${scopePrefix ?? ''}/${template.path}`;
|
|
111
|
+
const list = getAtPointer(state.dataModel, absPath);
|
|
112
|
+
if (Array.isArray(list)) {
|
|
113
|
+
for (let i = 0; i < list.length; i++) {
|
|
114
|
+
out.push({ id: template.componentId, scope: `${absPath}/${i}`, slot: key });
|
|
115
|
+
if (out.length > state.maxNodes)
|
|
116
|
+
break; // bound the expansion itself
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// non-array / undefined → render nothing (issue reported by collectGraphIssues)
|
|
85
120
|
}
|
|
86
121
|
}
|
|
87
|
-
// non-array / undefined → render nothing (issue reported by collectGraphIssues)
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
return out;
|
|
@@ -104,7 +138,7 @@ function buildNode(id, scopePrefix, key, parentIsFlex, ancestors, depth, state)
|
|
|
104
138
|
node.weight = clampWeight(weight, 0, 100);
|
|
105
139
|
}
|
|
106
140
|
}
|
|
107
|
-
const isFlex =
|
|
141
|
+
const isFlex = state.flexContainers.has(instance.component);
|
|
108
142
|
const nextAncestors = new Set(ancestors);
|
|
109
143
|
nextAncestors.add(id);
|
|
110
144
|
const refs = childRefs(instance, scopePrefix, state);
|
|
@@ -116,8 +150,10 @@ function buildNode(id, scopePrefix, key, parentIsFlex, ancestors, depth, state)
|
|
|
116
150
|
continue; // cycle: truncate (issue reported elsewhere)
|
|
117
151
|
const childKey = `${ref.id}@${ref.scope ?? ''}#${i}`;
|
|
118
152
|
const child = buildNode(ref.id, ref.scope, childKey, isFlex, nextAncestors, depth + 1, state);
|
|
119
|
-
if (child)
|
|
153
|
+
if (child) {
|
|
154
|
+
child.slot = ref.slot;
|
|
120
155
|
node.children.push(child);
|
|
156
|
+
}
|
|
121
157
|
}
|
|
122
158
|
return node;
|
|
123
159
|
}
|
|
@@ -132,6 +168,8 @@ export function buildRenderTree(surface, options) {
|
|
|
132
168
|
const state = {
|
|
133
169
|
components: surface.components,
|
|
134
170
|
dataModel: surface.dataModel,
|
|
171
|
+
registry: surface.catalog.registry,
|
|
172
|
+
flexContainers: surface.catalog.flexContainers,
|
|
135
173
|
maxDepth: options?.maxDepth ?? A2UI_MAX_DEPTH,
|
|
136
174
|
maxNodes: options?.maxNodes ?? A2UI_MAX_NODES,
|
|
137
175
|
count: 0
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface routing across message boundaries.
|
|
3
|
+
*
|
|
4
|
+
* `A2UIView` is payload-driven: one view renders one envelope array, and its
|
|
5
|
+
* processor lives with that view. In a chat that means one view per assistant
|
|
6
|
+
* message — so a surface dies with the message that created it. The A2UI spec
|
|
7
|
+
* says the opposite: a `surfaceId` is "globally unique for the renderer's
|
|
8
|
+
* lifetime", `deleteSurface` is the only way to retire one, and
|
|
9
|
+
* `updateComponents`/`updateDataModel` are patches an agent may send at any
|
|
10
|
+
* later point. A model that patches a form it built two turns ago is reading
|
|
11
|
+
* the protocol correctly; the renderer just had nowhere to put the envelope.
|
|
12
|
+
*
|
|
13
|
+
* This router closes that gap WITHOUT giving the engine a second state
|
|
14
|
+
* lifetime. It is a pure bookkeeping layer over the transcript: it remembers
|
|
15
|
+
* which source (message + a2ui part) owns which surface, and splits each
|
|
16
|
+
* source's envelopes into the ones that stay put and the ones that belong to an
|
|
17
|
+
* earlier source's payload. The consumer appends those to that payload
|
|
18
|
+
* (`[...payload, ...patch]`, keeping element identity) and `A2UIView` consumes
|
|
19
|
+
* them incrementally — two-way edits the user already made survive, because the
|
|
20
|
+
* view never rebuilds.
|
|
21
|
+
*
|
|
22
|
+
* The transcript stays the single source of truth: every envelope still lives
|
|
23
|
+
* in exactly one payload, so undo (`revoke`) and replay are plain array work.
|
|
24
|
+
*
|
|
25
|
+
* Zero-dependency and Svelte-free — usable from a server-side transcript
|
|
26
|
+
* reducer, not just from the browser.
|
|
27
|
+
*/
|
|
28
|
+
import { type A2uiValidationIssue } from './a2ui.types.js';
|
|
29
|
+
/**
|
|
30
|
+
* Envelopes destined for one source's payload.
|
|
31
|
+
*
|
|
32
|
+
* `envelopes` are the very objects the caller passed in — element identity is
|
|
33
|
+
* what lets `A2UIView` apply them incrementally instead of rebuilding, and what
|
|
34
|
+
* makes {@link A2uiSurfaceRouter.revoke} a reference filter.
|
|
35
|
+
*/
|
|
36
|
+
export interface A2uiRoutePatch {
|
|
37
|
+
/** The `sourceKey` whose payload these envelopes belong to. */
|
|
38
|
+
targetKey: string;
|
|
39
|
+
envelopes: unknown[];
|
|
40
|
+
}
|
|
41
|
+
/** The verdict for one {@link A2uiSurfaceRouter.route} call. */
|
|
42
|
+
export interface A2uiRouteResult {
|
|
43
|
+
/**
|
|
44
|
+
* The full envelope list this source keeps — assign it as the part's payload.
|
|
45
|
+
* Envelopes routed to an earlier source are absent; everything else stays,
|
|
46
|
+
* including malformed ones (the processor is what reports those).
|
|
47
|
+
*/
|
|
48
|
+
own: unknown[];
|
|
49
|
+
/** Envelopes to append to earlier sources' payloads, grouped by target. */
|
|
50
|
+
patches: A2uiRoutePatch[];
|
|
51
|
+
/**
|
|
52
|
+
* Envelopes to REMOVE from earlier payloads (by reference) because this
|
|
53
|
+
* source was rebuilt — a regenerated turn must not leave its old patches
|
|
54
|
+
* behind. Empty on an append-only call.
|
|
55
|
+
*/
|
|
56
|
+
revoked: A2uiRoutePatch[];
|
|
57
|
+
/**
|
|
58
|
+
* Surfaces that this call patched from a later source — i.e. surfaces that
|
|
59
|
+
* just proved they outlive their own message. This is the promotion signal
|
|
60
|
+
* for a client heuristic: a surface listed here is long-lived and deserves
|
|
61
|
+
* treatment beyond an inline transcript entry (a jump anchor, a pinned card,
|
|
62
|
+
* an artifact panel). Only fires on the transition, not on every patch.
|
|
63
|
+
*/
|
|
64
|
+
promoted: string[];
|
|
65
|
+
/** Router-level findings, shaped for the same channel as processor issues. */
|
|
66
|
+
issues: A2uiValidationIssue[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Tracks surface ownership across sources and routes envelopes to the payload
|
|
70
|
+
* that owns their surface.
|
|
71
|
+
*
|
|
72
|
+
* A "source" is one A2UI payload in the transcript — in a chat app, one `a2ui`
|
|
73
|
+
* part of one message. The key is the consumer's to choose; it only has to be
|
|
74
|
+
* stable for that payload's lifetime (e.g. `` `${messageId}#${partIndex}` ``).
|
|
75
|
+
*
|
|
76
|
+
* Ownership rules:
|
|
77
|
+
* - `createSurface` for an unknown id → the calling source owns it.
|
|
78
|
+
* - `createSurface` for an id owned by ANOTHER source → ownership transfers to
|
|
79
|
+
* the caller (last writer wins, so the new surface actually renders) and a
|
|
80
|
+
* `SURFACE_RECREATED` issue is raised for the agent.
|
|
81
|
+
* - Any operation on a surface owned by another source → routed to that owner.
|
|
82
|
+
* - Any operation on an UNKNOWN surface stays with the caller, so the
|
|
83
|
+
* processor's `NO_SURFACE` error surfaces instead of vanishing here.
|
|
84
|
+
* - `deleteSurface` routes to the owner and then frees the id for re-use.
|
|
85
|
+
*
|
|
86
|
+
* @example One assistant turn, wired into the transcript
|
|
87
|
+
* ```ts
|
|
88
|
+
* const result = router.route(`${messageId}#0`, part.payload);
|
|
89
|
+
* part.payload = result.own;
|
|
90
|
+
* for (const patch of result.patches) appendToPayload(patch.targetKey, patch.envelopes);
|
|
91
|
+
* for (const surfaceId of result.promoted) markLongLived(surfaceId);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare class A2uiSurfaceRouter {
|
|
95
|
+
#private;
|
|
96
|
+
/**
|
|
97
|
+
* Route one source's envelope list.
|
|
98
|
+
*
|
|
99
|
+
* Call it as often as you like — on every streamed token, if that is when the
|
|
100
|
+
* payload grows. Only envelopes appended since the last call are examined; an
|
|
101
|
+
* unchanged prefix is never re-routed, so patches are never duplicated. If
|
|
102
|
+
* the prefix DID change (a regenerated turn), the source is rebuilt from
|
|
103
|
+
* scratch and its previous patches come back in `revoked`.
|
|
104
|
+
*
|
|
105
|
+
* @param sourceKey Stable id of the payload being routed.
|
|
106
|
+
* @param envelopes The payload's accumulated envelopes, in stream order.
|
|
107
|
+
*/
|
|
108
|
+
route(sourceKey: string, envelopes: readonly unknown[]): A2uiRouteResult;
|
|
109
|
+
/**
|
|
110
|
+
* Forget a source entirely — call it when a message leaves the transcript
|
|
111
|
+
* (regenerate, delete, branch switch). The returned patches are the envelopes
|
|
112
|
+
* this source had pushed into other payloads; remove them by reference so no
|
|
113
|
+
* orphaned patch survives its author.
|
|
114
|
+
*/
|
|
115
|
+
revoke(sourceKey: string): A2uiRoutePatch[];
|
|
116
|
+
/** The source whose payload currently holds `surfaceId`, if any. */
|
|
117
|
+
ownerOf(surfaceId: string): string | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Whether a surface has been patched from a later source — the promotion
|
|
120
|
+
* predicate, for clients that need to ask after the fact rather than react to
|
|
121
|
+
* {@link A2uiRouteResult.promoted}.
|
|
122
|
+
*/
|
|
123
|
+
isLongLived(surfaceId: string): boolean;
|
|
124
|
+
/** Every surface currently owned by a source, in creation order. */
|
|
125
|
+
surfaceIds(): string[];
|
|
126
|
+
/**
|
|
127
|
+
* Every source key routed so far, in first-seen order.
|
|
128
|
+
*
|
|
129
|
+
* Needed to retire a source whose payload no longer shows it: a message whose
|
|
130
|
+
* envelopes ALL travelled to earlier surfaces keeps no a2ui part of its own,
|
|
131
|
+
* so the caller cannot reconstruct its keys by counting parts.
|
|
132
|
+
*/
|
|
133
|
+
sourceKeys(): string[];
|
|
134
|
+
/** Drop all bookkeeping (new conversation). */
|
|
135
|
+
reset(): void;
|
|
136
|
+
}
|