@urbicon-ui/blocks 6.38.0 → 6.39.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/Chat/A2UIView/A2UINode.svelte +582 -0
- package/dist/components/Chat/A2UIView/A2UINode.svelte.d.ts +10 -0
- package/dist/components/Chat/A2UIView/A2UIView.svelte +272 -0
- package/dist/components/Chat/A2UIView/A2UIView.svelte.d.ts +4 -0
- package/dist/components/Chat/A2UIView/a2ui-data.d.ts +65 -0
- package/dist/components/Chat/A2UIView/a2ui-data.js +283 -0
- package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +18 -0
- package/dist/components/Chat/A2UIView/a2ui-prompt.js +141 -0
- package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +64 -0
- package/dist/components/Chat/A2UIView/a2ui-registry.js +348 -0
- package/dist/components/Chat/A2UIView/a2ui-render.d.ts +96 -0
- package/dist/components/Chat/A2UIView/a2ui-render.js +140 -0
- package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +75 -0
- package/dist/components/Chat/A2UIView/a2ui-validate.js +762 -0
- package/dist/components/Chat/A2UIView/a2ui-view.variants.d.ts +74 -0
- package/dist/components/Chat/A2UIView/a2ui-view.variants.js +58 -0
- package/dist/components/Chat/A2UIView/a2ui.types.d.ts +117 -0
- package/dist/components/Chat/A2UIView/a2ui.types.js +83 -0
- package/dist/components/Chat/A2UIView/index.d.ts +102 -0
- package/dist/components/Chat/A2UIView/index.js +7 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte +4 -1
- package/dist/components/Chat/index.d.ts +2 -0
- package/dist/components/Chat/index.js +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-prompt generator for the A2UI subset. Renders the agent-facing catalog
|
|
3
|
+
* description straight from `A2UI_REGISTRY`, so the prompt and the validator can
|
|
4
|
+
* never drift (a drift-guard test asserts every component and enum value is
|
|
5
|
+
* covered). Pure TS, no Svelte — a server building the prompt has no DOM.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately excludes any TRANSPORT section (how envelopes reach the client):
|
|
8
|
+
* that is app-specific and each app appends it (the chat-demo appends its fenced
|
|
9
|
+
* `a2ui` JSONL protocol).
|
|
10
|
+
*/
|
|
11
|
+
import { A2UI_CATALOG_ID, A2UI_ICON_NAMES, A2UI_REGISTRY, UNSUPPORTED_A2UI_COMPONENTS } from './a2ui-registry.js';
|
|
12
|
+
/** Props documented once in the shared section rather than per component. */
|
|
13
|
+
const COMMON_PROP_KEYS = new Set(['accessibility', 'weight']);
|
|
14
|
+
function renderKind(spec) {
|
|
15
|
+
switch (spec.kind) {
|
|
16
|
+
case 'enum':
|
|
17
|
+
case 'icon':
|
|
18
|
+
return `one of: ${(spec.values ?? []).join(' | ')}`;
|
|
19
|
+
case 'string':
|
|
20
|
+
return spec.dynamic ? 'string literal or { path } binding' : 'string';
|
|
21
|
+
case 'number':
|
|
22
|
+
return spec.dynamic ? 'number literal or { path } binding' : 'number';
|
|
23
|
+
case 'boolean':
|
|
24
|
+
return spec.dynamic ? 'boolean literal or { path } binding' : 'boolean';
|
|
25
|
+
case 'stringList':
|
|
26
|
+
return 'string array or { path } binding';
|
|
27
|
+
case 'childId':
|
|
28
|
+
return 'component id (string)';
|
|
29
|
+
case 'childList':
|
|
30
|
+
return 'array of ids OR template { componentId, path }';
|
|
31
|
+
case 'action':
|
|
32
|
+
return 'action { event: { name, context } }';
|
|
33
|
+
case 'options':
|
|
34
|
+
return 'array of { label, value }';
|
|
35
|
+
case 'accessibility':
|
|
36
|
+
return 'object { label?, description? }';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function renderProp(key, spec) {
|
|
40
|
+
const flags = [];
|
|
41
|
+
flags.push(spec.required ? 'required' : 'optional');
|
|
42
|
+
flags.push(renderKind(spec));
|
|
43
|
+
if (spec.default !== undefined)
|
|
44
|
+
flags.push(`default ${JSON.stringify(spec.default)}`);
|
|
45
|
+
return ` - ${key} (${flags.join('; ')}): ${spec.description}`;
|
|
46
|
+
}
|
|
47
|
+
function renderComponent(name) {
|
|
48
|
+
const spec = A2UI_REGISTRY[name];
|
|
49
|
+
const lines = [`### ${name}`, spec.description, 'Props:'];
|
|
50
|
+
for (const [key, propSpec] of Object.entries(spec.props)) {
|
|
51
|
+
if (COMMON_PROP_KEYS.has(key) || propSpec.promptHidden)
|
|
52
|
+
continue;
|
|
53
|
+
lines.push(renderProp(key, propSpec));
|
|
54
|
+
}
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the A2UI system prompt for this subset. Pass `catalogId` to override the
|
|
59
|
+
* default `createSurface` catalog identifier (the value must round-trip
|
|
60
|
+
* unchanged in every envelope).
|
|
61
|
+
*/
|
|
62
|
+
export function a2uiSystemPrompt(options) {
|
|
63
|
+
const catalogId = options?.catalogId ?? A2UI_CATALOG_ID;
|
|
64
|
+
const componentNames = Object.keys(A2UI_REGISTRY);
|
|
65
|
+
const sections = [];
|
|
66
|
+
sections.push([
|
|
67
|
+
'# A2UI rendering',
|
|
68
|
+
'',
|
|
69
|
+
'You can render live, interactive UI by emitting A2UI (Agent-to-UI) envelopes',
|
|
70
|
+
'against the catalog below. Emit UI when a structured surface (form, list, card,',
|
|
71
|
+
'chooser) serves the user better than prose; otherwise answer in plain text.',
|
|
72
|
+
'',
|
|
73
|
+
`Protocol version: v0.9.1. Catalog id: ${catalogId}`
|
|
74
|
+
].join('\n'));
|
|
75
|
+
sections.push([
|
|
76
|
+
'## Envelopes',
|
|
77
|
+
'',
|
|
78
|
+
'Emit one JSON envelope per line (JSONL). Every envelope is an object with a',
|
|
79
|
+
'"version" of "v0.9.1" and EXACTLY ONE of these operations:',
|
|
80
|
+
'',
|
|
81
|
+
`- createSurface: { surfaceId, catalogId: "${catalogId}" } — send this FIRST.`,
|
|
82
|
+
'- updateComponents: { surfaceId, components: [ ... ] } — the component tree.',
|
|
83
|
+
'- updateDataModel: { surfaceId, path?, value? } — the surface state.',
|
|
84
|
+
'- deleteSurface: { surfaceId } — remove a surface.',
|
|
85
|
+
'',
|
|
86
|
+
'Rules:',
|
|
87
|
+
'- components is a FLAT list; each entry is { id, component, ...props }.',
|
|
88
|
+
'- EXACTLY ONE component MUST have id "root" — it is the tree root.',
|
|
89
|
+
'- Children are referenced by ID only — NEVER define a component inline.',
|
|
90
|
+
'- A single-child container (Card, Button) uses "child" (one id).',
|
|
91
|
+
'- A multi-child container (Row, Column, List) uses "children": either an',
|
|
92
|
+
' array of ids, or a template { componentId, path } that repeats one',
|
|
93
|
+
' component over a data-model array.',
|
|
94
|
+
'- You may stream components incrementally; buffer until "root" exists.'
|
|
95
|
+
].join('\n'));
|
|
96
|
+
sections.push([
|
|
97
|
+
'## Data bindings',
|
|
98
|
+
'',
|
|
99
|
+
'Any value prop may be a literal OR a data binding { "path": "/json/pointer" }',
|
|
100
|
+
'(RFC 6901). Inside a template, use a RELATIVE path (no leading slash) to address',
|
|
101
|
+
'the current array item, e.g. { "path": "name" }.',
|
|
102
|
+
'',
|
|
103
|
+
'Inputs are two-way: typing/checking/selecting writes back into the data model',
|
|
104
|
+
'immediately; bound text updates live. Update state with updateDataModel — omit',
|
|
105
|
+
'"value" to DELETE the key at "path".',
|
|
106
|
+
'',
|
|
107
|
+
'Function-call bindings ({ "call": ..., "args": ... }) are NOT supported and',
|
|
108
|
+
'resolve to nothing — do NOT use them. Use literals and { path } only.'
|
|
109
|
+
].join('\n'));
|
|
110
|
+
sections.push([
|
|
111
|
+
'## Actions',
|
|
112
|
+
'',
|
|
113
|
+
'A Button action MUST be a server event: { "event": { "name": string,',
|
|
114
|
+
'"context": { ... } } }. Context values may be literals or { path } bindings and',
|
|
115
|
+
'are resolved before dispatch. Do NOT use local function-call actions.'
|
|
116
|
+
].join('\n'));
|
|
117
|
+
sections.push(['## Components', '', ...componentNames.map(renderComponent)].join('\n\n'));
|
|
118
|
+
sections.push([
|
|
119
|
+
'## Common props (every component)',
|
|
120
|
+
'',
|
|
121
|
+
'- accessibility (optional; object { label?, description? }): ARIA label/description.',
|
|
122
|
+
'- weight (optional; number): flex-grow, honored ONLY on a direct Row/Column child.'
|
|
123
|
+
].join('\n'));
|
|
124
|
+
sections.push([
|
|
125
|
+
'## Icons',
|
|
126
|
+
'',
|
|
127
|
+
`Icon.name must be one of: ${A2UI_ICON_NAMES.join(', ')}. Unknown names render a`,
|
|
128
|
+
'fallback glyph.'
|
|
129
|
+
].join('\n'));
|
|
130
|
+
sections.push([
|
|
131
|
+
"## Don'ts",
|
|
132
|
+
'',
|
|
133
|
+
'- Do NOT define children inline; reference them by id.',
|
|
134
|
+
'- Do NOT use more than one "root".',
|
|
135
|
+
'- Do NOT use function-call bindings or local function-call actions.',
|
|
136
|
+
`- Do NOT use unsupported components: ${[...UNSUPPORTED_A2UI_COMPONENTS].join(', ')}.`,
|
|
137
|
+
'- Do NOT put HTML, images or links in Text markdown (inline bold/italic/code only).',
|
|
138
|
+
'- Images are blocked by default — always give Image a meaningful description.'
|
|
139
|
+
].join('\n'));
|
|
140
|
+
return sections.join('\n\n');
|
|
141
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The A2UI catalog subset this engine renders — the single source of truth for
|
|
3
|
+
* BOTH validation (`a2ui-validate.ts`) and the agent-facing system prompt
|
|
4
|
+
* (`a2ui-prompt.ts`). No Svelte imports: this module must be importable
|
|
5
|
+
* standalone (a server building the system prompt has no DOM).
|
|
6
|
+
*
|
|
7
|
+
* The subset is 12 of the 18 v0.9.1 `basic` components. The six excluded
|
|
8
|
+
* (Modal, Tabs, Video, AudioPlayer, DateTimeInput — plus any future addition)
|
|
9
|
+
* validate to a visible error chip; see `UNSUPPORTED_A2UI_COMPONENTS`.
|
|
10
|
+
*
|
|
11
|
+
* Prop names, kinds, `required`, defaults and enum sets mirror
|
|
12
|
+
* `catalogs/basic/catalog.json` **case-sensitively**. Every `description` here
|
|
13
|
+
* is written to be read by an agent — it is emitted verbatim into the prompt.
|
|
14
|
+
*/
|
|
15
|
+
/** How a prop value is shaped and validated. */
|
|
16
|
+
export type A2uiPropKind = 'string' | 'number' | 'boolean' | 'stringList' | 'enum' | 'childId' | 'childList' | 'action' | 'options' | 'icon' | 'accessibility';
|
|
17
|
+
export interface A2uiPropSpec {
|
|
18
|
+
kind: A2uiPropKind;
|
|
19
|
+
/** Required on the component. @default false */
|
|
20
|
+
required?: boolean;
|
|
21
|
+
/** Accepts the Dynamic union (literal | `{ path }` | function call). @default false */
|
|
22
|
+
dynamic?: boolean;
|
|
23
|
+
/** Allowed values for `enum`/`icon` kinds. */
|
|
24
|
+
values?: readonly string[];
|
|
25
|
+
/** Documented default (rendered in the prompt). */
|
|
26
|
+
default?: string | number | boolean;
|
|
27
|
+
/** Agent-facing description; emitted into the system prompt verbatim. */
|
|
28
|
+
description: string;
|
|
29
|
+
/**
|
|
30
|
+
* Accepted for spec-compatibility but omitted from the prompt and rendered
|
|
31
|
+
* with a degraded fallback (e.g. ChoicePicker `displayStyle`/`filterable`).
|
|
32
|
+
*/
|
|
33
|
+
promptHidden?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface A2uiComponentSpec {
|
|
36
|
+
/** Agent-facing description; emitted into the system prompt verbatim. */
|
|
37
|
+
description: string;
|
|
38
|
+
props: Record<string, A2uiPropSpec>;
|
|
39
|
+
}
|
|
40
|
+
/** Opaque catalog identifier this subset advertises in `createSurface`. */
|
|
41
|
+
export declare const A2UI_CATALOG_ID = "urbicon-ui/a2ui-basic-subset/v0.9.1";
|
|
42
|
+
/** Envelope `version` strings this engine accepts. */
|
|
43
|
+
export declare const A2UI_SUPPORTED_VERSIONS: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Real v0.9.1 `basic` components the engine deliberately does not render.
|
|
46
|
+
* Distinguished from wholly-unknown component names so the error message can
|
|
47
|
+
* say "not part of the rendered subset" rather than "unknown".
|
|
48
|
+
*/
|
|
49
|
+
export declare const UNSUPPORTED_A2UI_COMPONENTS: ReadonlySet<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Mapped `Icon.name` values. A name outside this set degrades to a fallback
|
|
52
|
+
* glyph (a warning, not an error). Kept in sync with the direct icon imports in
|
|
53
|
+
* `A2UINode.svelte` (WP-B).
|
|
54
|
+
*/
|
|
55
|
+
export declare const A2UI_ICON_NAMES: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Grammar guard for inline `Icon.name.svgPath`. Only SVG path commands, digits,
|
|
58
|
+
* whitespace and separators — no `<`, no attributes, no entities. WP-B MUST
|
|
59
|
+
* re-apply this guard before inlining a `<path d>` (defense in depth).
|
|
60
|
+
*/
|
|
61
|
+
export declare const A2UI_SVG_PATH_RE: RegExp;
|
|
62
|
+
export declare const A2UI_REGISTRY: Readonly<Record<string, A2uiComponentSpec>>;
|
|
63
|
+
/** Props recognized anywhere but intentionally ignored (validation only, dropped before render). */
|
|
64
|
+
export declare const A2UI_IGNORED_PROPS: ReadonlySet<string>;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The A2UI catalog subset this engine renders — the single source of truth for
|
|
3
|
+
* BOTH validation (`a2ui-validate.ts`) and the agent-facing system prompt
|
|
4
|
+
* (`a2ui-prompt.ts`). No Svelte imports: this module must be importable
|
|
5
|
+
* standalone (a server building the system prompt has no DOM).
|
|
6
|
+
*
|
|
7
|
+
* The subset is 12 of the 18 v0.9.1 `basic` components. The six excluded
|
|
8
|
+
* (Modal, Tabs, Video, AudioPlayer, DateTimeInput — plus any future addition)
|
|
9
|
+
* validate to a visible error chip; see `UNSUPPORTED_A2UI_COMPONENTS`.
|
|
10
|
+
*
|
|
11
|
+
* Prop names, kinds, `required`, defaults and enum sets mirror
|
|
12
|
+
* `catalogs/basic/catalog.json` **case-sensitively**. Every `description` here
|
|
13
|
+
* is written to be read by an agent — it is emitted verbatim into the prompt.
|
|
14
|
+
*/
|
|
15
|
+
/** Opaque catalog identifier this subset advertises in `createSurface`. */
|
|
16
|
+
export const A2UI_CATALOG_ID = 'urbicon-ui/a2ui-basic-subset/v0.9.1';
|
|
17
|
+
/** Envelope `version` strings this engine accepts. */
|
|
18
|
+
export const A2UI_SUPPORTED_VERSIONS = ['v0.9', 'v0.9.1'];
|
|
19
|
+
/**
|
|
20
|
+
* Real v0.9.1 `basic` components the engine deliberately does not render.
|
|
21
|
+
* Distinguished from wholly-unknown component names so the error message can
|
|
22
|
+
* say "not part of the rendered subset" rather than "unknown".
|
|
23
|
+
*/
|
|
24
|
+
export const UNSUPPORTED_A2UI_COMPONENTS = new Set([
|
|
25
|
+
'Modal',
|
|
26
|
+
'Tabs',
|
|
27
|
+
'Video',
|
|
28
|
+
'AudioPlayer',
|
|
29
|
+
'DateTimeInput'
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Mapped `Icon.name` values. A name outside this set degrades to a fallback
|
|
33
|
+
* glyph (a warning, not an error). Kept in sync with the direct icon imports in
|
|
34
|
+
* `A2UINode.svelte` (WP-B).
|
|
35
|
+
*/
|
|
36
|
+
export const A2UI_ICON_NAMES = [
|
|
37
|
+
'add',
|
|
38
|
+
'arrowBack',
|
|
39
|
+
'check',
|
|
40
|
+
'close',
|
|
41
|
+
'delete',
|
|
42
|
+
'edit',
|
|
43
|
+
'error',
|
|
44
|
+
'home',
|
|
45
|
+
'info',
|
|
46
|
+
'mail',
|
|
47
|
+
'menu',
|
|
48
|
+
'search',
|
|
49
|
+
'send',
|
|
50
|
+
'settings',
|
|
51
|
+
'star',
|
|
52
|
+
'warning'
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Grammar guard for inline `Icon.name.svgPath`. Only SVG path commands, digits,
|
|
56
|
+
* whitespace and separators — no `<`, no attributes, no entities. WP-B MUST
|
|
57
|
+
* re-apply this guard before inlining a `<path d>` (defense in depth).
|
|
58
|
+
*/
|
|
59
|
+
export const A2UI_SVG_PATH_RE = /^[MmLlHhVvCcSsQqTtAaZz0-9\s,.+\-eE]+$/;
|
|
60
|
+
/**
|
|
61
|
+
* Props every component accepts (spread into each entry). `accessibility` maps
|
|
62
|
+
* to ARIA; `weight` is flex-grow, honored only on a direct Row/Column child.
|
|
63
|
+
*/
|
|
64
|
+
const COMMON_PROPS = {
|
|
65
|
+
accessibility: {
|
|
66
|
+
kind: 'accessibility',
|
|
67
|
+
description: 'Accessibility attributes { label, description } for assistive technologies. label is a short (1–3 word) purpose string; description adds instructions or format requirements.'
|
|
68
|
+
},
|
|
69
|
+
weight: {
|
|
70
|
+
kind: 'number',
|
|
71
|
+
description: 'Relative flex-grow weight within a Row or Column (like CSS flex-grow). ONLY honored when this component is a direct child of a Row or Column.'
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function withCommon(props) {
|
|
75
|
+
return { ...props, ...COMMON_PROPS };
|
|
76
|
+
}
|
|
77
|
+
export const A2UI_REGISTRY = Object.freeze({
|
|
78
|
+
Column: {
|
|
79
|
+
description: 'A layout container that arranges its children vertically. Nest Rows inside a Column to build a grid.',
|
|
80
|
+
props: withCommon({
|
|
81
|
+
children: {
|
|
82
|
+
kind: 'childList',
|
|
83
|
+
required: true,
|
|
84
|
+
description: 'The children: either an array of component IDs, or a template { componentId, path } that repeats one component over a data-model array. Children are referenced by ID only — never defined inline.'
|
|
85
|
+
},
|
|
86
|
+
justify: {
|
|
87
|
+
kind: 'enum',
|
|
88
|
+
values: ['start', 'center', 'end', 'spaceBetween', 'spaceAround', 'spaceEvenly', 'stretch'],
|
|
89
|
+
default: 'start',
|
|
90
|
+
description: 'Arrangement along the main (vertical) axis. Use spaceBetween to push items to the top and bottom edges.'
|
|
91
|
+
},
|
|
92
|
+
align: {
|
|
93
|
+
kind: 'enum',
|
|
94
|
+
values: ['start', 'center', 'end', 'stretch'],
|
|
95
|
+
default: 'stretch',
|
|
96
|
+
description: 'Alignment along the cross (horizontal) axis, like CSS align-items.'
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
Row: {
|
|
101
|
+
description: 'A layout container that arranges its children horizontally. Nest Columns inside a Row to build a grid.',
|
|
102
|
+
props: withCommon({
|
|
103
|
+
children: {
|
|
104
|
+
kind: 'childList',
|
|
105
|
+
required: true,
|
|
106
|
+
description: 'The children: either an array of component IDs, or a template { componentId, path } that repeats one component over a data-model array. Children are referenced by ID only — never defined inline.'
|
|
107
|
+
},
|
|
108
|
+
justify: {
|
|
109
|
+
kind: 'enum',
|
|
110
|
+
values: ['start', 'center', 'end', 'spaceBetween', 'spaceAround', 'spaceEvenly', 'stretch'],
|
|
111
|
+
default: 'start',
|
|
112
|
+
description: 'Arrangement along the main (horizontal) axis. Use spaceBetween to push items to the left and right edges.'
|
|
113
|
+
},
|
|
114
|
+
align: {
|
|
115
|
+
kind: 'enum',
|
|
116
|
+
values: ['start', 'center', 'end', 'stretch'],
|
|
117
|
+
default: 'stretch',
|
|
118
|
+
description: 'Alignment along the cross (vertical) axis, like CSS align-items.'
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
List: {
|
|
123
|
+
description: 'A list container. Prefer a template { componentId, path } to render one row per data-model array item.',
|
|
124
|
+
props: withCommon({
|
|
125
|
+
children: {
|
|
126
|
+
kind: 'childList',
|
|
127
|
+
required: true,
|
|
128
|
+
description: 'The children: an array of component IDs, or a template { componentId, path }. Children are referenced by ID only.'
|
|
129
|
+
},
|
|
130
|
+
direction: {
|
|
131
|
+
kind: 'enum',
|
|
132
|
+
values: ['vertical', 'horizontal'],
|
|
133
|
+
default: 'vertical',
|
|
134
|
+
description: 'The direction list items flow in.'
|
|
135
|
+
},
|
|
136
|
+
align: {
|
|
137
|
+
kind: 'enum',
|
|
138
|
+
values: ['start', 'center', 'end', 'stretch'],
|
|
139
|
+
default: 'stretch',
|
|
140
|
+
description: 'Alignment of items along the cross axis.'
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
},
|
|
144
|
+
Card: {
|
|
145
|
+
description: 'A visually grouped container with exactly one child. To place several elements in a Card, wrap them in a Column or Row and pass that container ID as the child.',
|
|
146
|
+
props: withCommon({
|
|
147
|
+
child: {
|
|
148
|
+
kind: 'childId',
|
|
149
|
+
required: true,
|
|
150
|
+
description: 'The ID of the single child component to render inside the card. Pass a layout container ID for multiple elements. Never inline the child.'
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
Divider: {
|
|
155
|
+
description: 'A thin rule that visually separates content.',
|
|
156
|
+
props: withCommon({
|
|
157
|
+
axis: {
|
|
158
|
+
kind: 'enum',
|
|
159
|
+
values: ['horizontal', 'vertical'],
|
|
160
|
+
default: 'horizontal',
|
|
161
|
+
description: 'Orientation of the divider.'
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
},
|
|
165
|
+
Text: {
|
|
166
|
+
description: 'Displays text. `text` supports simple inline Markdown (bold, italic, code) — no HTML, images or links. Prefer dedicated components over Markdown for structured content.',
|
|
167
|
+
props: withCommon({
|
|
168
|
+
text: {
|
|
169
|
+
kind: 'string',
|
|
170
|
+
required: true,
|
|
171
|
+
dynamic: true,
|
|
172
|
+
description: 'The text to display. Bind to the data model with { path } for live values.'
|
|
173
|
+
},
|
|
174
|
+
variant: {
|
|
175
|
+
kind: 'enum',
|
|
176
|
+
values: ['h1', 'h2', 'h3', 'h4', 'h5', 'caption', 'body'],
|
|
177
|
+
default: 'body',
|
|
178
|
+
description: 'Text style hint. h1–h5 are headings; caption is small secondary text; body is default.'
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
},
|
|
182
|
+
Image: {
|
|
183
|
+
description: 'Displays an image from a URL. Images are blocked by default unless the host allows the source — provide a meaningful `description` so a blocked image still conveys meaning.',
|
|
184
|
+
props: withCommon({
|
|
185
|
+
url: {
|
|
186
|
+
kind: 'string',
|
|
187
|
+
required: true,
|
|
188
|
+
dynamic: true,
|
|
189
|
+
description: 'The image URL. Subject to the host URL policy (blocked by default).'
|
|
190
|
+
},
|
|
191
|
+
description: {
|
|
192
|
+
kind: 'string',
|
|
193
|
+
dynamic: true,
|
|
194
|
+
description: 'Alt text describing the image for accessibility and when the image is blocked.'
|
|
195
|
+
},
|
|
196
|
+
fit: {
|
|
197
|
+
kind: 'enum',
|
|
198
|
+
values: ['contain', 'cover', 'fill', 'none', 'scaleDown'],
|
|
199
|
+
default: 'fill',
|
|
200
|
+
description: 'How the image resizes to fit its box (CSS object-fit).'
|
|
201
|
+
},
|
|
202
|
+
variant: {
|
|
203
|
+
kind: 'enum',
|
|
204
|
+
values: ['icon', 'avatar', 'smallFeature', 'mediumFeature', 'largeFeature', 'header'],
|
|
205
|
+
default: 'mediumFeature',
|
|
206
|
+
description: 'Size/style preset for the image.'
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
},
|
|
210
|
+
Icon: {
|
|
211
|
+
description: 'Displays a named icon from the mapped set. Use `name` with one of the supported names; unknown names fall back to a generic glyph.',
|
|
212
|
+
props: withCommon({
|
|
213
|
+
name: {
|
|
214
|
+
kind: 'icon',
|
|
215
|
+
required: true,
|
|
216
|
+
values: A2UI_ICON_NAMES,
|
|
217
|
+
description: 'The icon name (one of the supported names). May also bind to the data model with { path }. Custom vector art via { svgPath } is accepted but discouraged.'
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
},
|
|
221
|
+
Button: {
|
|
222
|
+
description: 'A clickable button. Its `child` is the label (use a Text component). `action` MUST be a server event. Both `child` and `action` are required.',
|
|
223
|
+
props: withCommon({
|
|
224
|
+
child: {
|
|
225
|
+
kind: 'childId',
|
|
226
|
+
required: true,
|
|
227
|
+
description: 'The ID of the label component (usually a Text). For an icon-only button pass an Icon ID. Never inline the child.'
|
|
228
|
+
},
|
|
229
|
+
action: {
|
|
230
|
+
kind: 'action',
|
|
231
|
+
required: true,
|
|
232
|
+
description: 'The interaction handler. Use the server-event form { event: { name, context } }. Resolve dynamic context values with { path } bindings; use literals for static values.'
|
|
233
|
+
},
|
|
234
|
+
variant: {
|
|
235
|
+
kind: 'enum',
|
|
236
|
+
values: ['default', 'primary', 'borderless'],
|
|
237
|
+
default: 'default',
|
|
238
|
+
description: 'Button style. primary marks the main call to action; borderless renders as a link-like button.'
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
},
|
|
242
|
+
TextField: {
|
|
243
|
+
description: 'A single- or multi-line text input. `label` is required. Bind `value` to a data-model path for two-way binding (typing writes to the model immediately).',
|
|
244
|
+
props: withCommon({
|
|
245
|
+
label: {
|
|
246
|
+
kind: 'string',
|
|
247
|
+
required: true,
|
|
248
|
+
dynamic: true,
|
|
249
|
+
description: 'The field label.'
|
|
250
|
+
},
|
|
251
|
+
value: {
|
|
252
|
+
kind: 'string',
|
|
253
|
+
dynamic: true,
|
|
254
|
+
description: 'The field value. Bind with { path } for two-way binding.'
|
|
255
|
+
},
|
|
256
|
+
variant: {
|
|
257
|
+
kind: 'enum',
|
|
258
|
+
values: ['shortText', 'longText', 'number', 'obscured'],
|
|
259
|
+
default: 'shortText',
|
|
260
|
+
description: 'Input type: shortText (one line), longText (multi-line), number (numeric), obscured (password).'
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
CheckBox: {
|
|
265
|
+
description: 'A single boolean checkbox with a label. Bind `value` to a data-model boolean for two-way binding.',
|
|
266
|
+
props: withCommon({
|
|
267
|
+
label: {
|
|
268
|
+
kind: 'string',
|
|
269
|
+
required: true,
|
|
270
|
+
dynamic: true,
|
|
271
|
+
description: 'The text shown next to the checkbox.'
|
|
272
|
+
},
|
|
273
|
+
value: {
|
|
274
|
+
kind: 'boolean',
|
|
275
|
+
required: true,
|
|
276
|
+
dynamic: true,
|
|
277
|
+
description: 'The checked state. Bind with { path } for two-way binding.'
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
},
|
|
281
|
+
ChoicePicker: {
|
|
282
|
+
description: 'Select one or more options from a list. Bind `value` to a data-model string array. Use variant mutuallyExclusive for single-select, multipleSelection for multi-select.',
|
|
283
|
+
props: withCommon({
|
|
284
|
+
options: {
|
|
285
|
+
kind: 'options',
|
|
286
|
+
required: true,
|
|
287
|
+
description: 'The available options as an array of { label, value } objects. `value` is a stable string.'
|
|
288
|
+
},
|
|
289
|
+
value: {
|
|
290
|
+
kind: 'stringList',
|
|
291
|
+
required: true,
|
|
292
|
+
dynamic: true,
|
|
293
|
+
description: 'The selected values as a string array in the data model. Single-select writes a one-element array.'
|
|
294
|
+
},
|
|
295
|
+
label: {
|
|
296
|
+
kind: 'string',
|
|
297
|
+
dynamic: true,
|
|
298
|
+
description: 'An optional label for the whole group.'
|
|
299
|
+
},
|
|
300
|
+
variant: {
|
|
301
|
+
kind: 'enum',
|
|
302
|
+
values: ['mutuallyExclusive', 'multipleSelection'],
|
|
303
|
+
default: 'mutuallyExclusive',
|
|
304
|
+
description: 'mutuallyExclusive is single-select (radios); multipleSelection allows several.'
|
|
305
|
+
},
|
|
306
|
+
displayStyle: {
|
|
307
|
+
kind: 'enum',
|
|
308
|
+
values: ['checkbox', 'chips'],
|
|
309
|
+
default: 'checkbox',
|
|
310
|
+
promptHidden: true,
|
|
311
|
+
description: 'Display hint. chips is rendered with a checkbox/radio fallback.'
|
|
312
|
+
},
|
|
313
|
+
filterable: {
|
|
314
|
+
kind: 'boolean',
|
|
315
|
+
promptHidden: true,
|
|
316
|
+
description: 'If true, requests a filter input; rendered without one (fallback).'
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
},
|
|
320
|
+
Slider: {
|
|
321
|
+
description: 'A numeric slider. `value` and `max` are required (`min` defaults to 0). Bind `value` for two-way binding.',
|
|
322
|
+
props: withCommon({
|
|
323
|
+
value: {
|
|
324
|
+
kind: 'number',
|
|
325
|
+
required: true,
|
|
326
|
+
dynamic: true,
|
|
327
|
+
description: 'The current value. Bind with { path } for two-way binding.'
|
|
328
|
+
},
|
|
329
|
+
max: {
|
|
330
|
+
kind: 'number',
|
|
331
|
+
required: true,
|
|
332
|
+
description: 'The maximum value.'
|
|
333
|
+
},
|
|
334
|
+
min: {
|
|
335
|
+
kind: 'number',
|
|
336
|
+
default: 0,
|
|
337
|
+
description: 'The minimum value (default 0).'
|
|
338
|
+
},
|
|
339
|
+
label: {
|
|
340
|
+
kind: 'string',
|
|
341
|
+
dynamic: true,
|
|
342
|
+
description: 'An optional label for the slider.'
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
/** Props recognized anywhere but intentionally ignored (validation only, dropped before render). */
|
|
348
|
+
export const A2UI_IGNORED_PROPS = new Set(['checks', 'validationRegexp']);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-tree assembly for the A2UI renderer — the bridge between the pure-TS
|
|
3
|
+
* processor (`a2ui-validate.ts`, plain Maps) and the recursive Svelte dispatcher
|
|
4
|
+
* (`A2UINode.svelte`). Pure TS, no Svelte, so `A2UINode` and `A2UIView` share
|
|
5
|
+
* one typed contract (the house pattern of `md-context.ts`, deliberately not a
|
|
6
|
+
* Svelte context — the tree is private and explicit prop flow keeps it
|
|
7
|
+
* inspectable).
|
|
8
|
+
*
|
|
9
|
+
* `buildRenderTree` walks the component graph from `root` into a fully-expanded,
|
|
10
|
+
* BOUNDED node tree: child/children references resolved, list templates expanded
|
|
11
|
+
* over the data model, dangling references marked, and the depth/node/cycle
|
|
12
|
+
* limits enforced in ONE place so the Svelte layer can never recurse without
|
|
13
|
+
* bound regardless of payload. Graph-level ISSUES (the reportable faults) are
|
|
14
|
+
* still produced by `collectGraphIssues` in `a2ui-validate.ts`; this walk only
|
|
15
|
+
* shapes what renders.
|
|
16
|
+
*/
|
|
17
|
+
import type { IconComponent } from '../../../icons/index.js';
|
|
18
|
+
import type { MarkdownUrlPolicy } from '../markdown/types.js';
|
|
19
|
+
import type { A2uiActionEvent, A2uiValidationIssue } from './a2ui.types.js';
|
|
20
|
+
import type { A2uiComponentInstance, A2uiSurfaceState } from './a2ui-validate.js';
|
|
21
|
+
/** Default traversal bounds — mirror `collectGraphIssues` so the tree and the issue list agree. */
|
|
22
|
+
export declare const A2UI_MAX_DEPTH = 32;
|
|
23
|
+
export declare const A2UI_MAX_NODES = 512;
|
|
24
|
+
/**
|
|
25
|
+
* One node of the assembled render tree. `instance === null` marks a dangling
|
|
26
|
+
* reference (a child id that was never defined) — rendered as a streaming
|
|
27
|
+
* placeholder or, once settled, a fault chip. `children` are pre-expanded and
|
|
28
|
+
* ordered; `key` is stable across incremental rebuilds so keyed `{#each}` keeps
|
|
29
|
+
* component identity (and input focus) through a keystroke-triggered rebuild.
|
|
30
|
+
*/
|
|
31
|
+
export interface A2uiRenderNode {
|
|
32
|
+
key: string;
|
|
33
|
+
id: string;
|
|
34
|
+
instance: A2uiComponentInstance | null;
|
|
35
|
+
/** Template-item scope for relative-path resolution and two-way write-back. */
|
|
36
|
+
scopePrefix: string | undefined;
|
|
37
|
+
children: A2uiRenderNode[];
|
|
38
|
+
/** flex-grow, present only when this node is a direct Row/Column child with a finite `weight`. */
|
|
39
|
+
weight?: number;
|
|
40
|
+
}
|
|
41
|
+
/** Static text labels threaded to the node dispatcher (i18n comes from A2UIView props). */
|
|
42
|
+
export interface A2uiRenderLabels {
|
|
43
|
+
unsupported: string;
|
|
44
|
+
blockedImage: string;
|
|
45
|
+
pending: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The single context object each `A2UINode` receives. Carries resolved slot
|
|
49
|
+
* classes, the icon map, the data-binding resolver, the two-way write-back
|
|
50
|
+
* callbacks, the action sink and rendering flags. Rebuilt per version bump so a
|
|
51
|
+
* live data-model edit propagates fresh resolved values without remounting.
|
|
52
|
+
*/
|
|
53
|
+
export interface A2uiRenderContext {
|
|
54
|
+
/** Resolved class string per slot (tv() + slotClasses + unstyled already applied). */
|
|
55
|
+
classes: Readonly<Record<string, string>>;
|
|
56
|
+
urlPolicy: MarkdownUrlPolicy | undefined;
|
|
57
|
+
/** Dangling refs render placeholders while true; fault chips once false. */
|
|
58
|
+
streaming: boolean;
|
|
59
|
+
/** True inside a Button label (Text renders as an inline plain span). */
|
|
60
|
+
inline: boolean;
|
|
61
|
+
surfaceId: string;
|
|
62
|
+
onAction: ((event: A2uiActionEvent) => void) | undefined;
|
|
63
|
+
/** Resolve a Dynamic value (literal | { path } | function-call) against the model in `scope`. */
|
|
64
|
+
resolve: (value: unknown, scopePrefix: string | undefined) => {
|
|
65
|
+
value: unknown;
|
|
66
|
+
issue?: A2uiValidationIssue;
|
|
67
|
+
};
|
|
68
|
+
/** Write a value at an absolute JSON Pointer into the surface model, then bump. */
|
|
69
|
+
write: (pointer: string, value: unknown) => void;
|
|
70
|
+
/** Delete the key at an absolute JSON Pointer from the surface model, then bump. */
|
|
71
|
+
remove: (pointer: string) => void;
|
|
72
|
+
/** A2UI icon-name → resolved icon component (IconProvider override honoured, direct import fallback). */
|
|
73
|
+
icons: Readonly<Record<string, IconComponent>>;
|
|
74
|
+
/** Fallback glyph for an unmapped icon name. */
|
|
75
|
+
fallbackIcon: IconComponent;
|
|
76
|
+
labels: A2uiRenderLabels;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a `{ path }` data binding to the ABSOLUTE JSON Pointer it addresses,
|
|
80
|
+
* honouring the template scope for relative paths. Returns `undefined` for
|
|
81
|
+
* literals and function-call bindings (nothing to write back into).
|
|
82
|
+
*/
|
|
83
|
+
export declare function bindingPointer(value: unknown, scopePrefix: string | undefined): string | undefined;
|
|
84
|
+
/** Coerce an unknown model value to a string array of stable option values (for ChoicePicker). */
|
|
85
|
+
export declare function toStringArray(value: unknown): string[];
|
|
86
|
+
/** Coerce a scalar model value to the string an `<input>` displays. */
|
|
87
|
+
export declare function toInputString(value: unknown): string;
|
|
88
|
+
/**
|
|
89
|
+
* Assemble the bounded render tree for a surface, starting at `root`. Returns
|
|
90
|
+
* `null` when no `root` component exists yet (the caller renders a placeholder
|
|
91
|
+
* while streaming, or a fault chip once settled).
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildRenderTree(surface: A2uiSurfaceState, options?: {
|
|
94
|
+
maxDepth?: number;
|
|
95
|
+
maxNodes?: number;
|
|
96
|
+
}): A2uiRenderNode | null;
|