@urbicon-ui/blocks 6.40.4 → 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.
Files changed (72) hide show
  1. package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
  2. package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
  3. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
  4. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
  5. package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
  6. package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
  7. package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
  8. package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
  9. package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
  10. package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
  11. package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
  12. package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
  13. package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
  14. package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
  15. package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
  16. package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
  17. package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
  18. package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
  19. package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
  20. package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
  21. package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
  22. package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
  23. package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
  24. package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
  25. package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
  26. package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
  27. package/dist/components/Chat/A2UIView/index.d.ts +35 -2
  28. package/dist/components/Chat/A2UIView/index.js +13 -0
  29. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
  30. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
  31. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
  32. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
  33. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
  34. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
  35. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
  36. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
  37. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
  38. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
  39. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +19 -19
  40. package/dist/components/FileUpload/fileUpload.variants.js +15 -4
  41. package/dist/components/PinInput/PinInput.svelte +1 -0
  42. package/dist/components/PinInput/pin-input.variants.js +14 -1
  43. package/dist/components/TimeInput/time-input.variants.js +16 -1
  44. package/dist/internal/field-chrome.d.ts +18 -1
  45. package/dist/internal/field-chrome.js +18 -1
  46. package/dist/primitives/Avatar/avatar.variants.js +4 -1
  47. package/dist/primitives/Badge/badge.variants.js +11 -2
  48. package/dist/primitives/Button/button.variants.js +4 -1
  49. package/dist/primitives/Card/card.variants.js +4 -1
  50. package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
  51. package/dist/primitives/Combobox/Combobox.svelte +9 -1
  52. package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
  53. package/dist/primitives/Combobox/combobox.variants.js +32 -3
  54. package/dist/primitives/Combobox/index.d.ts +8 -2
  55. package/dist/primitives/Dialog/dialog.variants.js +4 -1
  56. package/dist/primitives/Drawer/drawer.variants.js +4 -1
  57. package/dist/primitives/Input/input.variants.js +17 -1
  58. package/dist/primitives/Menu/menu.variants.js +5 -1
  59. package/dist/primitives/Progress/progress.variants.js +5 -2
  60. package/dist/primitives/RadioGroup/radioGroup.variants.js +68 -9
  61. package/dist/primitives/Select/select.variants.js +13 -4
  62. package/dist/primitives/Slider/Slider.svelte +4 -1
  63. package/dist/primitives/Slider/slider.variants.js +9 -2
  64. package/dist/primitives/Textarea/textarea.variants.js +15 -1
  65. package/dist/primitives/Toast/toast.variants.js +5 -1
  66. package/dist/primitives/Toggle/toggle.variants.js +92 -18
  67. package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
  68. package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
  69. package/dist/utils/persistent-state.svelte.d.ts +25 -1
  70. package/dist/utils/persistent-state.svelte.js +98 -10
  71. package/docs/MIGRATION-v5.md +29 -0
  72. package/package.json +3 -3
@@ -21,6 +21,7 @@
21
21
  import { checkImageUrl, checkLinkUrl } from '../markdown/url-policy.js';
22
22
  import type { A2uiActionEvent } from './a2ui.types';
23
23
  import { A2UI_REGISTRY, A2UI_SVG_PATH_RE } from './a2ui-registry';
24
+ import { dedupeOptions, splitDateTime } from './a2ui-node-common';
24
25
  import type { A2uiRenderContext, A2uiRenderNode } from './a2ui-render';
25
26
  import { bindingPointer, toInputString, toStringArray } from './a2ui-render';
26
27
 
@@ -246,7 +247,11 @@
246
247
  ).value;
247
248
  }
248
249
  }
250
+ // `sendDataModel` surfaces ship the whole model alongside the context, so a
251
+ // context the agent under-specified still tells it what the user entered.
252
+ const dataModel = context.actionDataModel?.();
249
253
  const payload: A2uiActionEvent = {
254
+ ...(dataModel === undefined ? {} : { dataModel }),
250
255
  name: event.name as string,
251
256
  surfaceId: context.surfaceId,
252
257
  sourceComponentId: instance.id,
@@ -306,27 +311,18 @@
306
311
  if (pointer) return toStringArray(resolved('value'));
307
312
  return localList ?? toStringArray(raw('value'));
308
313
  });
309
- const choiceOptions = $derived.by(() => {
310
- const options = raw('options');
311
- if (!Array.isArray(options)) return [] as Array<{ value: string; label: string }>;
312
- // Dedupe by value (first wins): the validator warns on duplicates, but the
313
- // keyed `{#each}` below is on option.value — two equal keys throw Svelte's
314
- // `each_key_duplicate` (a hard crash that would break the "never throw"
315
- // contract for an untrusted payload).
316
- const seen = new Set<string>();
317
- const out: Array<{ value: string; label: string }> = [];
318
- for (const option of options) {
319
- if (option === null || typeof option !== 'object' || typeof option.value !== 'string')
320
- continue;
321
- if (seen.has(option.value)) continue;
322
- seen.add(option.value);
323
- out.push({
324
- value: option.value,
325
- label: text(context.resolve(option.label, node.scopePrefix).value)
326
- });
327
- }
328
- return out;
329
- });
314
+ // Dedupe by value (first wins): the validator warns on duplicates, but the
315
+ // keyed `{#each}` below is on option.value — two equal keys throw Svelte's
316
+ // `each_key_duplicate` (a hard crash that would break the "never throw"
317
+ // contract for an untrusted payload). Shared with the Urbicon dispatcher.
318
+ const choiceOptions = $derived(
319
+ // `options` may itself be a { path } binding — that is how an agent shows a
320
+ // list it fetched mid-conversation (free slots, search hits) without
321
+ // rewriting the component. Resolve it first, then the per-option labels.
322
+ dedupeOptions(resolved('options'), (label) =>
323
+ text(context.resolve(label, node.scopePrefix).value)
324
+ )
325
+ );
330
326
  const choiceLabel = $derived(text(resolved('label')));
331
327
  function writeChoice(next: string[]) {
332
328
  const pointer = valuePointer();
@@ -359,24 +355,6 @@
359
355
  return 'date';
360
356
  });
361
357
 
362
- function normalizeTimePart(value: string): string {
363
- const match = /^(\d{2}:\d{2}(?::\d{2})?)/.exec(value);
364
- return match ? match[1] : '';
365
- }
366
- function splitDateTime(value: string): { date: string; time: string } {
367
- const trimmed = value.trim();
368
- if (trimmed === '') return { date: '', time: '' };
369
- const tIndex = trimmed.indexOf('T');
370
- if (tIndex !== -1) {
371
- return {
372
- date: trimmed.slice(0, tIndex),
373
- time: normalizeTimePart(trimmed.slice(tIndex + 1))
374
- };
375
- }
376
- if (trimmed.includes(':')) return { date: '', time: normalizeTimePart(trimmed) };
377
- return { date: trimmed, time: '' };
378
- }
379
-
380
358
  const dtParts = $derived(splitDateTime(textValue));
381
359
  const dtMinParts = $derived(splitDateTime(text(resolved('min'))));
382
360
  const dtMaxParts = $derived(splitDateTime(text(resolved('max'))));
@@ -638,11 +616,14 @@
638
616
  </RadioGroup>
639
617
  {/if}
640
618
  {:else if component === 'Slider'}
619
+ <!-- showValue is forced on: a generated surface has no other place to state
620
+ the current number, and the agent cannot ask for it (not a catalog prop). -->
641
621
  <Slider
642
622
  label={text(resolved('label')) || undefined}
643
623
  value={sliderValue}
644
624
  min={sliderMin}
645
625
  max={sliderMax}
626
+ showValue
646
627
  onValueChange={onSliderChange}
647
628
  style={weightStyle}
648
629
  aria-label={ariaLabel}
@@ -2,26 +2,9 @@
2
2
  import { Alert } from '../../../primitives';
3
3
  import { untrack } from 'svelte';
4
4
  import { getBlocksConfig, resolveSlotClasses } from '../../../provider';
5
- import { type IconComponent, resolveIcon } from '../../../icons';
6
- import PlusIconDefault from '../../../icons/PlusIcon.svelte';
7
- import ArrowLeftIconDefault from '../../../icons/ArrowLeftIcon.svelte';
8
- import CheckIconDefault from '../../../icons/CheckIcon.svelte';
9
- import CloseIconDefault from '../../../icons/CloseIcon.svelte';
10
- import TrashIconDefault from '../../../icons/TrashIcon.svelte';
11
- import EditIconDefault from '../../../icons/EditIcon.svelte';
12
- import DangerCircleIconDefault from '../../../icons/DangerCircleIcon.svelte';
13
- import HomeIconDefault from '../../../icons/HomeIcon.svelte';
14
- import InfoCircleIconDefault from '../../../icons/InfoCircleIcon.svelte';
15
- import MailIconDefault from '../../../icons/MailIcon.svelte';
16
- import MenuIconDefault from '../../../icons/MenuIcon.svelte';
17
- import SearchIconDefault from '../../../icons/SearchIcon.svelte';
18
- import SendIconDefault from '../../../icons/SendIcon.svelte';
19
- import SettingsIconDefault from '../../../icons/SettingsIcon.svelte';
20
- import StarIconDefault from '../../../icons/StarIcon.svelte';
21
- import WarningTriangleIconDefault from '../../../icons/WarningTriangleIcon.svelte';
22
- import CircleHelpIconDefault from '../../../icons/CircleHelpIcon.svelte';
23
- import A2UINode from './A2UINode.svelte';
24
5
  import type { A2uiValidationIssue } from './a2ui.types';
6
+ import { basicA2uiCatalog } from './a2ui-basic-catalog';
7
+ import type { A2uiCatalog } from './a2ui-catalog';
25
8
  import { deleteAtPointer, resolveDynamic, setAtPointer } from './a2ui-data';
26
9
  import {
27
10
  type A2uiProcessor,
@@ -43,6 +26,8 @@
43
26
  unsupportedLabel = 'Unsupported component',
44
27
  blockedImageLabel = 'Image blocked',
45
28
  pendingLabel = 'Loading UI',
29
+ catalogs: catalogsProp,
30
+ dataSchema: dataSchemaProp,
46
31
  class: className,
47
32
  unstyled: unstyledProp = false,
48
33
  slotClasses: slotClassesProp = {},
@@ -53,29 +38,30 @@
53
38
  const blocksConfig = getBlocksConfig();
54
39
  const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
55
40
 
56
- // A2UI icon-enum name resolved Urbicon icon. Resolved ONCE via direct
57
- // imports (tree-shakeable never getIcon()); an IconProvider override still
58
- // wins, the direct import is the fallback. Built here (not per node) and
59
- // threaded through the render context.
60
- const icons: Readonly<Record<string, IconComponent>> = {
61
- add: resolveIcon('plus', PlusIconDefault),
62
- arrowBack: resolveIcon('arrowLeft', ArrowLeftIconDefault),
63
- check: resolveIcon('check', CheckIconDefault),
64
- close: resolveIcon('close', CloseIconDefault),
65
- delete: resolveIcon('trash', TrashIconDefault),
66
- edit: resolveIcon('edit', EditIconDefault),
67
- error: resolveIcon('danger', DangerCircleIconDefault),
68
- home: resolveIcon('home', HomeIconDefault),
69
- info: resolveIcon('info', InfoCircleIconDefault),
70
- mail: resolveIcon('mail', MailIconDefault),
71
- menu: resolveIcon('menu', MenuIconDefault),
72
- search: resolveIcon('search', SearchIconDefault),
73
- send: resolveIcon('send', SendIconDefault),
74
- settings: resolveIcon('settings', SettingsIconDefault),
75
- star: resolveIcon('star', StarIconDefault),
76
- warning: resolveIcon('warning', WarningTriangleIconDefault)
77
- };
78
- const fallbackIcon = resolveIcon('circleHelp', CircleHelpIconDefault);
41
+ // Effective catalogs: the Basic catalog is always available and FIRST (the
42
+ // default/fallback), consumer catalogs follow. Resolved ONCE at init
43
+ // `createIcons()` reads the IconProvider context, so it must run during
44
+ // component init and cannot be reactive. Keep `catalogs` referentially stable.
45
+ // `untrack` captures the initial prop value without a reactive subscription
46
+ // (the init-only capture is deliberate — silences `state_referenced_locally`).
47
+ const catalogs: readonly A2uiCatalog[] = [
48
+ basicA2uiCatalog,
49
+ ...(untrack(() => catalogsProp) ?? [])
50
+ ];
51
+ const catalogById = new Map<string, A2uiCatalog>();
52
+ const iconsByCatalog = new Map<string, ReturnType<A2uiCatalog['createIcons']>>();
53
+ for (const catalog of catalogs) {
54
+ if (catalogById.has(catalog.catalogId)) continue;
55
+ catalogById.set(catalog.catalogId, catalog);
56
+ for (const alias of catalog.catalogIdAliases ?? []) {
57
+ if (!catalogById.has(alias)) catalogById.set(alias, catalog);
58
+ }
59
+ iconsByCatalog.set(catalog.catalogId, catalog.createIcons());
60
+ }
61
+ const defaultCatalog = catalogs[0];
62
+ const defaultIcons = iconsByCatalog.get(defaultCatalog.catalogId) ?? defaultCatalog.createIcons();
63
+ // Init-fixed like the catalogs (validation config, not reactive UI state).
64
+ const dataSchema = untrack(() => dataSchemaProp);
79
65
 
80
66
  // ── Processor (plain, non-reactive) + version counter ────────────────────
81
67
  // The processor mutates plain Maps in place (the streaming-markdown engine
@@ -83,7 +69,7 @@
83
69
  // render tree derives from it, so reading it is a real subscription (unlike a
84
70
  // `void messages` proxy read). `processor`/`consumed` are plain lets: their
85
71
  // reassignment must NOT itself trigger reactivity — the bump does.
86
- let processor: A2uiProcessor = createA2uiProcessor();
72
+ let processor: A2uiProcessor = createA2uiProcessor({ catalogs, dataSchema });
87
73
  let consumed: unknown[] = [];
88
74
  let normalizeIssue = $state<A2uiValidationIssue | undefined>(undefined);
89
75
  let version = $state(0);
@@ -110,7 +96,7 @@
110
96
  const prefixMatches =
111
97
  envelopes.length >= consumed.length && consumed.every((env, i) => env === envelopes[i]);
112
98
  if (!prefixMatches) {
113
- processor = createA2uiProcessor();
99
+ processor = createA2uiProcessor({ catalogs, dataSchema });
114
100
  consumed = [];
115
101
  generation++;
116
102
  }
@@ -195,6 +181,11 @@
195
181
  allIssues.push(graphIssue);
196
182
  }
197
183
  const root = buildRenderTree(surface);
184
+ // Resolve the surface's catalog to its Svelte wiring: which dispatcher
185
+ // renders it and which icon set it draws from. surface.catalog is one of
186
+ // `catalogs`, so both lookups always hit; the `??` are belt-and-braces.
187
+ const surfaceCatalog = catalogById.get(surface.catalog.catalogId) ?? defaultCatalog;
188
+ const surfaceIcons = iconsByCatalog.get(surfaceCatalog.catalogId) ?? defaultIcons;
198
189
  const context: A2uiRenderContext = {
199
190
  classes: currentClasses,
200
191
  urlPolicy,
@@ -202,11 +193,14 @@
202
193
  inline: false,
203
194
  surfaceId: surface.surfaceId,
204
195
  onAction,
196
+ // Read lazily: the click, not the render, decides what the agent sees.
197
+ actionDataModel: surface.sendDataModel ? () => surface.dataModel : undefined,
205
198
  resolve: (value, scope) => resolveDynamic(value, surface.dataModel, scope),
206
199
  write: (pointer, value) => writeBinding(surface.surfaceId, pointer, value),
207
200
  remove: (pointer) => removeBinding(surface.surfaceId, pointer),
208
- icons,
209
- fallbackIcon,
201
+ icons: surfaceIcons.icons,
202
+ fallbackIcon: surfaceIcons.fallbackIcon,
203
+ nodeComponent: surfaceCatalog.Node,
210
204
  labels: {
211
205
  unsupported: unsupportedLabel,
212
206
  blockedImage: blockedImageLabel,
@@ -241,12 +235,15 @@
241
235
 
242
236
  <!--
243
237
  The render tree recurses through this self-referencing snippet rather than a
244
- self-import inside A2UINode: a snippet that renders A2UINode and passes ITSELF
245
- down as `renderChild` gives clean recursion without the circular component-type
246
- resolution that self-imports trip in svelte-check.
238
+ self-import inside the node dispatcher: a snippet that renders the dispatcher
239
+ and passes ITSELF down as `renderChild` gives clean recursion without the
240
+ circular component-type resolution that self-imports trip in svelte-check. The
241
+ dispatcher is the surface catalog's own node component (Basic → A2UINode; a
242
+ custom catalog → its own), read off the render context per surface.
247
243
  -->
248
244
  {#snippet renderNode(node: A2uiRenderNode, context: A2uiRenderContext)}
249
- <A2UINode {node} {context} renderChild={renderNode} />
245
+ {@const Node = context.nodeComponent}
246
+ <Node {node} {context} renderChild={renderNode} />
250
247
  {/snippet}
251
248
 
252
249
  <!--
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The v0.9.1 `basic`-subset catalog, fully wired: the pure `basicA2uiCatalogSpec`
3
+ * plus its Svelte half — the `A2UINode` dispatcher and the `createIcons` factory.
4
+ *
5
+ * The icon map lives here (not at A2UIView module scope) so it is built ONCE per
6
+ * catalog and threaded through the render context, and so a custom catalog can
7
+ * ship its own icon set from its own module without dragging Basic's icons into
8
+ * every bundle. `createIcons` is a FACTORY, not a constant: `resolveIcon` reads
9
+ * the IconProvider context, so it must run during component initialisation —
10
+ * A2UIView calls `catalog.createIcons()` from its script top level.
11
+ *
12
+ * This module imports `.svelte` components, so — unlike `a2ui-catalog.ts` — it is
13
+ * NOT server-importable. Keep the Svelte-free spec in `a2ui-catalog.ts`.
14
+ */
15
+ import { type A2uiCatalog } from './a2ui-catalog.js';
16
+ /** The renderable Basic catalog: spec + `A2UINode` dispatcher + icon factory. */
17
+ export declare const basicA2uiCatalog: A2uiCatalog;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The v0.9.1 `basic`-subset catalog, fully wired: the pure `basicA2uiCatalogSpec`
3
+ * plus its Svelte half — the `A2UINode` dispatcher and the `createIcons` factory.
4
+ *
5
+ * The icon map lives here (not at A2UIView module scope) so it is built ONCE per
6
+ * catalog and threaded through the render context, and so a custom catalog can
7
+ * ship its own icon set from its own module without dragging Basic's icons into
8
+ * every bundle. `createIcons` is a FACTORY, not a constant: `resolveIcon` reads
9
+ * the IconProvider context, so it must run during component initialisation —
10
+ * A2UIView calls `catalog.createIcons()` from its script top level.
11
+ *
12
+ * This module imports `.svelte` components, so — unlike `a2ui-catalog.ts` — it is
13
+ * NOT server-importable. Keep the Svelte-free spec in `a2ui-catalog.ts`.
14
+ */
15
+ import { resolveIcon } from '../../../icons/index.js';
16
+ import ArrowLeftIconDefault from '../../../icons/ArrowLeftIcon.svelte';
17
+ import CheckIconDefault from '../../../icons/CheckIcon.svelte';
18
+ import CircleHelpIconDefault from '../../../icons/CircleHelpIcon.svelte';
19
+ import CloseIconDefault from '../../../icons/CloseIcon.svelte';
20
+ import DangerCircleIconDefault from '../../../icons/DangerCircleIcon.svelte';
21
+ import EditIconDefault from '../../../icons/EditIcon.svelte';
22
+ import HomeIconDefault from '../../../icons/HomeIcon.svelte';
23
+ import InfoCircleIconDefault from '../../../icons/InfoCircleIcon.svelte';
24
+ import MailIconDefault from '../../../icons/MailIcon.svelte';
25
+ import MenuIconDefault from '../../../icons/MenuIcon.svelte';
26
+ import PlusIconDefault from '../../../icons/PlusIcon.svelte';
27
+ import SearchIconDefault from '../../../icons/SearchIcon.svelte';
28
+ import SendIconDefault from '../../../icons/SendIcon.svelte';
29
+ import SettingsIconDefault from '../../../icons/SettingsIcon.svelte';
30
+ import StarIconDefault from '../../../icons/StarIcon.svelte';
31
+ import TrashIconDefault from '../../../icons/TrashIcon.svelte';
32
+ import WarningTriangleIconDefault from '../../../icons/WarningTriangleIcon.svelte';
33
+ import A2UINode from './A2UINode.svelte';
34
+ import { basicA2uiCatalogSpec } from './a2ui-catalog.js';
35
+ /**
36
+ * Build the Basic catalog's A2UI icon-enum name → resolved Urbicon icon map.
37
+ * Resolved via DIRECT imports (tree-shakeable — never `getIcon()`); an
38
+ * IconProvider override still wins, the direct import is the fallback. Must run
39
+ * during component init (`resolveIcon` reads context).
40
+ */
41
+ function createBasicIcons() {
42
+ return {
43
+ icons: {
44
+ add: resolveIcon('plus', PlusIconDefault),
45
+ arrowBack: resolveIcon('arrowLeft', ArrowLeftIconDefault),
46
+ check: resolveIcon('check', CheckIconDefault),
47
+ close: resolveIcon('close', CloseIconDefault),
48
+ delete: resolveIcon('trash', TrashIconDefault),
49
+ edit: resolveIcon('edit', EditIconDefault),
50
+ error: resolveIcon('danger', DangerCircleIconDefault),
51
+ home: resolveIcon('home', HomeIconDefault),
52
+ info: resolveIcon('info', InfoCircleIconDefault),
53
+ mail: resolveIcon('mail', MailIconDefault),
54
+ menu: resolveIcon('menu', MenuIconDefault),
55
+ search: resolveIcon('search', SearchIconDefault),
56
+ send: resolveIcon('send', SendIconDefault),
57
+ settings: resolveIcon('settings', SettingsIconDefault),
58
+ star: resolveIcon('star', StarIconDefault),
59
+ warning: resolveIcon('warning', WarningTriangleIconDefault)
60
+ },
61
+ fallbackIcon: resolveIcon('circleHelp', CircleHelpIconDefault)
62
+ };
63
+ }
64
+ /** The renderable Basic catalog: spec + `A2UINode` dispatcher + icon factory. */
65
+ export const basicA2uiCatalog = {
66
+ ...basicA2uiCatalogSpec,
67
+ Node: A2UINode,
68
+ createIcons: createBasicIcons
69
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Catalog abstraction for the A2UI engine. A *catalog* bundles everything the
3
+ * pure-TS engine (validate/render/prompt) needs to be catalog-agnostic: the
4
+ * component registry, the icon-name set, the unsupported/ignored lists, the
5
+ * flex-container set (which parents honour a child `weight`), and per-component
6
+ * post-validation checks. This is the seam that lets a second, Urbicon-native
7
+ * catalog (`urbicon/…`) sit alongside the v0.9.1 `basic` catalog without
8
+ * touching the engine.
9
+ *
10
+ * Two layers:
11
+ * - `A2uiCatalogSpec` — pure TS, Svelte-free, server-importable (a server
12
+ * building the system prompt or validating a payload has no DOM). This is
13
+ * what the engine threads.
14
+ * - `A2uiCatalog` — adds the Svelte wiring (`Node` dispatcher, `createIcons`
15
+ * factory) via TYPE-ONLY imports, so importing this module still pulls no
16
+ * Svelte runtime. The `.svelte`-bearing catalog VALUE is assembled in a
17
+ * separate module (`a2ui-basic-catalog.ts`) so the pure spec stays isolated.
18
+ *
19
+ * The spec-level prop/component types live in `a2ui-registry.ts` (the Basic
20
+ * data); they are re-exported here so a downstream catalog imports one place.
21
+ */
22
+ import type { Component } from 'svelte';
23
+ import type { IconComponent } from '../../../icons/index.js';
24
+ import { type A2uiValidationIssue } from './a2ui.types.js';
25
+ import { type A2uiComponentSpec, type A2uiPropKind, type A2uiPropSpec } from './a2ui-registry.js';
26
+ import type { A2uiNodeProps } from './a2ui-render.js';
27
+ export type { A2uiComponentSpec, A2uiPropKind, A2uiPropSpec };
28
+ /**
29
+ * Context passed to a per-component post-validation check. `props` is the
30
+ * already-validated (registry-declared, stored) prop map for the component;
31
+ * `base` is its payload path (`/messages/<i>/updateComponents/components/<j>`).
32
+ */
33
+ export interface A2uiComponentCheckContext {
34
+ id: string;
35
+ props: ReadonlyMap<string, unknown>;
36
+ surfaceId: string;
37
+ base: string;
38
+ }
39
+ /**
40
+ * A per-component check the engine runs after generic prop validation, keyed by
41
+ * component name. Returns any extra issues (e.g. ChoicePicker chips-fallback,
42
+ * DateTimeInput missing-mode). Kept as catalog DATA rather than hardcoded
43
+ * branches so each catalog owns its own semantic warnings.
44
+ */
45
+ export type A2uiComponentCheck = (ctx: A2uiComponentCheckContext) => A2uiValidationIssue[];
46
+ /**
47
+ * The Svelte-free half of a catalog — everything the engine (validate / render
48
+ * graph / prompt) needs. Structurally satisfied by the richer `A2uiCatalog`.
49
+ */
50
+ export interface A2uiCatalogSpec {
51
+ /** Catalog id advertised in `createSurface` and matched against incoming surfaces. */
52
+ catalogId: string;
53
+ /** Additional ids that resolve to this catalog (version/back-compat aliases). */
54
+ catalogIdAliases?: readonly string[];
55
+ /** Component name → spec (the single source of truth for validation AND the prompt). */
56
+ registry: Readonly<Record<string, A2uiComponentSpec>>;
57
+ /** Mapped `Icon.name` values; an unmapped name degrades to a fallback glyph. */
58
+ iconNames: readonly string[];
59
+ /** Real catalog components deliberately not rendered (error chip, not "unknown"). */
60
+ unsupportedComponents: ReadonlySet<string>;
61
+ /** Props recognised anywhere but dropped before render (validation warning only). */
62
+ ignoredProps: ReadonlySet<string>;
63
+ /** Component names whose DIRECT children may carry a `weight` (flex-grow). */
64
+ flexContainers: ReadonlySet<string>;
65
+ /** Optional per-component post-validation checks, keyed by component name. */
66
+ componentChecks?: Readonly<Record<string, A2uiComponentCheck>>;
67
+ }
68
+ /**
69
+ * A full, renderable catalog — the spec plus its Svelte wiring. `Node` is the
70
+ * recursive dispatcher A2UIView renders per surface; `createIcons` builds the
71
+ * icon map (must run during component init — `resolveIcon` reads the
72
+ * IconProvider context). The `Component`/`IconComponent` references are
73
+ * TYPE-ONLY, so this module stays runtime-Svelte-free.
74
+ */
75
+ export interface A2uiCatalog extends A2uiCatalogSpec {
76
+ Node: Component<A2uiNodeProps>;
77
+ createIcons: () => {
78
+ icons: Readonly<Record<string, IconComponent>>;
79
+ fallbackIcon: IconComponent;
80
+ };
81
+ }
82
+ /**
83
+ * Resolve a `createSurface.catalogId` to a configured catalog: exact id match
84
+ * first, then an alias match. No prefix/semver matching (an id must round-trip
85
+ * unchanged). Returns `undefined` when nothing matches — the caller falls back
86
+ * to the default catalog.
87
+ */
88
+ export declare function resolveCatalog<T extends A2uiCatalogSpec>(catalogs: readonly T[], catalogId: string): T | undefined;
89
+ /**
90
+ * The v0.9.1 `basic`-subset catalog, spec half. The Basic-specific ChoicePicker
91
+ * and DateTimeInput warnings live here as `componentChecks` (previously
92
+ * hardcoded in the validator) so the engine carries no catalog-specific
93
+ * branches. Message strings are byte-identical to the pre-refactor validator.
94
+ */
95
+ export declare const basicA2uiCatalogSpec: A2uiCatalogSpec;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Catalog abstraction for the A2UI engine. A *catalog* bundles everything the
3
+ * pure-TS engine (validate/render/prompt) needs to be catalog-agnostic: the
4
+ * component registry, the icon-name set, the unsupported/ignored lists, the
5
+ * flex-container set (which parents honour a child `weight`), and per-component
6
+ * post-validation checks. This is the seam that lets a second, Urbicon-native
7
+ * catalog (`urbicon/…`) sit alongside the v0.9.1 `basic` catalog without
8
+ * touching the engine.
9
+ *
10
+ * Two layers:
11
+ * - `A2uiCatalogSpec` — pure TS, Svelte-free, server-importable (a server
12
+ * building the system prompt or validating a payload has no DOM). This is
13
+ * what the engine threads.
14
+ * - `A2uiCatalog` — adds the Svelte wiring (`Node` dispatcher, `createIcons`
15
+ * factory) via TYPE-ONLY imports, so importing this module still pulls no
16
+ * Svelte runtime. The `.svelte`-bearing catalog VALUE is assembled in a
17
+ * separate module (`a2ui-basic-catalog.ts`) so the pure spec stays isolated.
18
+ *
19
+ * The spec-level prop/component types live in `a2ui-registry.ts` (the Basic
20
+ * data); they are re-exported here so a downstream catalog imports one place.
21
+ */
22
+ import { A2UI_ISSUE_CODES } from './a2ui.types.js';
23
+ import { A2UI_CATALOG_ID, A2UI_ICON_NAMES, A2UI_IGNORED_PROPS, A2UI_REGISTRY, UNSUPPORTED_A2UI_COMPONENTS } from './a2ui-registry.js';
24
+ /**
25
+ * Resolve a `createSurface.catalogId` to a configured catalog: exact id match
26
+ * first, then an alias match. No prefix/semver matching (an id must round-trip
27
+ * unchanged). Returns `undefined` when nothing matches — the caller falls back
28
+ * to the default catalog.
29
+ */
30
+ export function resolveCatalog(catalogs, catalogId) {
31
+ for (const catalog of catalogs) {
32
+ if (catalog.catalogId === catalogId)
33
+ return catalog;
34
+ }
35
+ for (const catalog of catalogs) {
36
+ if (catalog.catalogIdAliases?.includes(catalogId))
37
+ return catalog;
38
+ }
39
+ return undefined;
40
+ }
41
+ /**
42
+ * The v0.9.1 `basic`-subset catalog, spec half. The Basic-specific ChoicePicker
43
+ * and DateTimeInput warnings live here as `componentChecks` (previously
44
+ * hardcoded in the validator) so the engine carries no catalog-specific
45
+ * branches. Message strings are byte-identical to the pre-refactor validator.
46
+ */
47
+ export const basicA2uiCatalogSpec = {
48
+ catalogId: A2UI_CATALOG_ID,
49
+ registry: A2UI_REGISTRY,
50
+ iconNames: A2UI_ICON_NAMES,
51
+ unsupportedComponents: UNSUPPORTED_A2UI_COMPONENTS,
52
+ ignoredProps: A2UI_IGNORED_PROPS,
53
+ flexContainers: new Set(['Row', 'Column']),
54
+ componentChecks: {
55
+ ChoicePicker: ({ id, props, surfaceId, base }) => {
56
+ if (props.get('displayStyle') === 'chips' || props.get('filterable') === true) {
57
+ return [
58
+ {
59
+ severity: 'warning',
60
+ code: A2UI_ISSUE_CODES.CHOICEPICKER_FALLBACK,
61
+ message: `ChoicePicker "${id}" chips/filterable are rendered with a fallback`,
62
+ surfaceId,
63
+ path: base
64
+ }
65
+ ];
66
+ }
67
+ return [];
68
+ },
69
+ DateTimeInput: ({ id, props, surfaceId, base }) => {
70
+ // Both flags default to false in the spec — a DateTimeInput without either
71
+ // would have no input UI at all. We read tolerantly (render a date input)
72
+ // and report loudly so the agent fixes the payload.
73
+ if (props.get('enableDate') !== true && props.get('enableTime') !== true) {
74
+ return [
75
+ {
76
+ severity: 'warning',
77
+ code: A2UI_ISSUE_CODES.DATETIME_NO_MODE,
78
+ message: `DateTimeInput "${id}" sets neither enableDate nor enableTime; rendering a date input`,
79
+ surfaceId,
80
+ path: base
81
+ }
82
+ ];
83
+ }
84
+ return [];
85
+ }
86
+ }
87
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pure helpers shared by the catalog node dispatchers (`A2UINode`,
3
+ * `urbicon/UrbiconA2UINode`). No Svelte — just the payload-shaping logic both
4
+ * renderers need identically. The reactive plumbing (the `$derived`/`$state`
5
+ * two-way-binding wiring) is deliberately NOT extracted: it is duplicated per
6
+ * dispatcher so each stays a self-contained, readable component.
7
+ *
8
+ * `dedupeOptions` is load-bearing for the "never throw" contract: EVERY
9
+ * payload-driven keyed `{#each}` over option values must dedupe first, or two
10
+ * equal keys throw Svelte's `each_key_duplicate` — a hard render crash on an
11
+ * untrusted payload. The validator only WARNS on duplicates; the renderer must
12
+ * drop them.
13
+ */
14
+ /**
15
+ * Coerce an unknown resolved value to display text: strings pass through, finite
16
+ * numbers stringify, everything else (null/undefined/object/NaN) becomes `''`.
17
+ */
18
+ export declare function coerceText(value: unknown): string;
19
+ /**
20
+ * Normalise a raw options array into a deduped `{ value, label }` list (first
21
+ * occurrence of each value wins). `resolveLabel` turns a raw label value (a
22
+ * literal or a `{ path }` binding) into display text — the caller injects its
23
+ * data-binding resolver. Malformed options (non-object, non-string value) are
24
+ * skipped. Dedup is mandatory: the rendered list is keyed on `value`.
25
+ */
26
+ export declare function dedupeOptions(rawOptions: unknown, resolveLabel: (rawLabel: unknown) => string): Array<{
27
+ value: string;
28
+ label: string;
29
+ }>;
30
+ /** Keep only the leading `HH:MM(:SS)?` of a time string; `''` if there is none. */
31
+ export declare function normalizeTimePart(value: string): string;
32
+ /**
33
+ * Split one ISO 8601 date/time literal into its date and time parts, timezone-
34
+ * naively (a trailing offset/`Z` is dropped, never re-attached — timezone
35
+ * semantics belong to the agent, not a UI renderer without timezone context).
36
+ */
37
+ export declare function splitDateTime(value: string): {
38
+ date: string;
39
+ time: string;
40
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pure helpers shared by the catalog node dispatchers (`A2UINode`,
3
+ * `urbicon/UrbiconA2UINode`). No Svelte — just the payload-shaping logic both
4
+ * renderers need identically. The reactive plumbing (the `$derived`/`$state`
5
+ * two-way-binding wiring) is deliberately NOT extracted: it is duplicated per
6
+ * dispatcher so each stays a self-contained, readable component.
7
+ *
8
+ * `dedupeOptions` is load-bearing for the "never throw" contract: EVERY
9
+ * payload-driven keyed `{#each}` over option values must dedupe first, or two
10
+ * equal keys throw Svelte's `each_key_duplicate` — a hard render crash on an
11
+ * untrusted payload. The validator only WARNS on duplicates; the renderer must
12
+ * drop them.
13
+ */
14
+ /**
15
+ * Coerce an unknown resolved value to display text: strings pass through, finite
16
+ * numbers stringify, everything else (null/undefined/object/NaN) becomes `''`.
17
+ */
18
+ export function coerceText(value) {
19
+ if (typeof value === 'string')
20
+ return value;
21
+ if (typeof value === 'number' && Number.isFinite(value))
22
+ return String(value);
23
+ return '';
24
+ }
25
+ /**
26
+ * Normalise a raw options array into a deduped `{ value, label }` list (first
27
+ * occurrence of each value wins). `resolveLabel` turns a raw label value (a
28
+ * literal or a `{ path }` binding) into display text — the caller injects its
29
+ * data-binding resolver. Malformed options (non-object, non-string value) are
30
+ * skipped. Dedup is mandatory: the rendered list is keyed on `value`.
31
+ */
32
+ export function dedupeOptions(rawOptions, resolveLabel) {
33
+ if (!Array.isArray(rawOptions))
34
+ return [];
35
+ const seen = new Set();
36
+ const out = [];
37
+ for (const option of rawOptions) {
38
+ if (option === null || typeof option !== 'object')
39
+ continue;
40
+ const value = option.value;
41
+ if (typeof value !== 'string' || seen.has(value))
42
+ continue;
43
+ seen.add(value);
44
+ out.push({ value, label: resolveLabel(option.label) });
45
+ }
46
+ return out;
47
+ }
48
+ /** Keep only the leading `HH:MM(:SS)?` of a time string; `''` if there is none. */
49
+ export function normalizeTimePart(value) {
50
+ const match = /^(\d{2}:\d{2}(?::\d{2})?)/.exec(value);
51
+ return match ? match[1] : '';
52
+ }
53
+ /**
54
+ * Split one ISO 8601 date/time literal into its date and time parts, timezone-
55
+ * naively (a trailing offset/`Z` is dropped, never re-attached — timezone
56
+ * semantics belong to the agent, not a UI renderer without timezone context).
57
+ */
58
+ export function splitDateTime(value) {
59
+ const trimmed = value.trim();
60
+ if (trimmed === '')
61
+ return { date: '', time: '' };
62
+ const tIndex = trimmed.indexOf('T');
63
+ if (tIndex !== -1) {
64
+ return {
65
+ date: trimmed.slice(0, tIndex),
66
+ time: normalizeTimePart(trimmed.slice(tIndex + 1))
67
+ };
68
+ }
69
+ if (trimmed.includes(':'))
70
+ return { date: '', time: normalizeTimePart(trimmed) };
71
+ return { date: trimmed, time: '' };
72
+ }