@adia-ai/a2ui-compose 0.6.43 → 0.6.44

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog — @adia-ai/a2ui-compose
2
2
 
3
+ ## [0.6.44] — 2026-05-28
4
+
5
+ ### Fixed — transpiler emits `<select-ui>` options as a prop, not Text children (FEEDBACK-79)
6
+
7
+ - **`transpiler/transpiler-maps.js` + `transpiler/transpiler.js`** — `<select-ui>` / `<select>` (A2UI `ChoicePicker`) had no `<option>` handling: each `<option>` fell through the generic text-leaf path and became a `Text` child of the picker. Since `ChoicePicker` → `select-ui`, the renderer appended those as `<p>` / `<text-ui>` children, which `select-ui` rejects (`ignoring unrecognized child element` warning + empty options). `extractProps()` now builds the `options` model — `[{value, label, disabled?}]`, grouped `{label, options:[…]}` for `<optgroup>` — and mirrors `<option selected>` into `value` (comma-joined for `[multiple]`); `walkNode()` treats `ChoicePicker` as self-contained so options are never re-emitted as children. Matches `select.yaml`'s `options` contract. Affects every `<select-ui>`-bearing chunk, not just the 3 in the ticket. Re-harvest required (see `@adia-ai/a2ui-corpus`).
8
+
3
9
  ## [0.6.43] — 2026-05-27
4
10
 
5
11
  ### Fixed — Cycle-18 transpiler substrate sweeps (RL + RL2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-compose",
3
- "version": "0.6.43",
3
+ "version": "0.6.44",
4
4
  "description": "AdiaUI A2UI compose engine \u2014 framework-agnostic. Takes natural-language intents + a catalog and produces A2UI protocol messages. Pairs with `@adia-ai/a2ui-retrieval` (intent classification, catalog lookup) and `@adia-ai/a2ui-validator` (schema + semantic checks).",
5
5
  "type": "module",
6
6
  "exports": {
@@ -435,6 +435,25 @@ export function extractProps(el, a2uiType) {
435
435
  if (ariaLabel) props.label = ariaLabel;
436
436
  }
437
437
 
438
+ // ── ChoicePicker / Select — build the options[] model from declarative
439
+ // <option> / <optgroup> children (FB-79). <select-ui> renders natively
440
+ // from `el.options = [{value, label, disabled?}]` (select.yaml `options`),
441
+ // so the options must travel as a PROP. Without this, the tree walker
442
+ // emits each <option> as a Text child, which <select-ui> rejects
443
+ // (renders empty + "ignoring unrecognized child element" warning).
444
+ if (a2uiType === 'ChoicePicker') {
445
+ const options = extractSelectOptions(el);
446
+ if (options.length) {
447
+ props.options = options;
448
+ // Mirror <option selected> into the select value when not set via attr,
449
+ // matching select-ui #parseOptions (single value, comma-joined for [multiple]).
450
+ if (props.value === undefined || props.value === '') {
451
+ const selected = collectSelectedOptionValues(el);
452
+ if (selected.length) props.value = selected.join(',');
453
+ }
454
+ }
455
+ }
456
+
438
457
  // ── Data attributes pass-through ──
439
458
  if (el.attributes) {
440
459
  const attrs = el.attributes instanceof Map ? el.attributes : el.attributes;
@@ -452,6 +471,69 @@ export function extractProps(el, a2uiType) {
452
471
  return props;
453
472
  }
454
473
 
474
+ // ── Select option model (FB-79) ──────────────────────────────────────────
475
+
476
+ /**
477
+ * Build the <select-ui> options[] model from an element's declarative
478
+ * <option> / <optgroup> children. Mirrors select-ui #parseOptions:
479
+ * <option value disabled>Label</option> → { value, label, disabled? }
480
+ * <optgroup label>…</optgroup> → { label, options: [...] }
481
+ *
482
+ * @param {object} el — DOM element or MinimalElement
483
+ * @returns {Array<object>}
484
+ */
485
+ function extractSelectOptions(el) {
486
+ const out = [];
487
+ for (const child of (el.children || [])) {
488
+ const ctag = (child.tagName || '').toLowerCase();
489
+ if (ctag === 'option') {
490
+ out.push(optionToModel(child));
491
+ } else if (ctag === 'optgroup') {
492
+ const label = optionAttr(child, 'label') || '';
493
+ const groupOptions = [];
494
+ for (const opt of (child.children || [])) {
495
+ if ((opt.tagName || '').toLowerCase() === 'option') groupOptions.push(optionToModel(opt));
496
+ }
497
+ out.push({ label, options: groupOptions });
498
+ }
499
+ }
500
+ return out;
501
+ }
502
+
503
+ /** @returns {{value: string, label: string, disabled?: boolean}} */
504
+ function optionToModel(opt) {
505
+ const value = optionAttr(opt, 'value');
506
+ const label = (opt.textContent || '').trim().replace(/\s+/g, ' ');
507
+ const model = { value: value != null ? value : label, label };
508
+ if (optionAttr(opt, 'disabled') !== null) model.disabled = true;
509
+ return model;
510
+ }
511
+
512
+ /** Collect values of <option selected> children (recursing <optgroup>). */
513
+ function collectSelectedOptionValues(el) {
514
+ const values = [];
515
+ const walk = (nodes) => {
516
+ for (const child of (nodes || [])) {
517
+ const ctag = (child.tagName || '').toLowerCase();
518
+ if (ctag === 'option') {
519
+ if (optionAttr(child, 'selected') !== null) {
520
+ const v = optionAttr(child, 'value');
521
+ values.push(v != null ? v : (child.textContent || '').trim().replace(/\s+/g, ' '));
522
+ }
523
+ } else if (ctag === 'optgroup') {
524
+ walk(child.children);
525
+ }
526
+ }
527
+ };
528
+ walk(el.children);
529
+ return values;
530
+ }
531
+
532
+ /** Read an attribute from either a DOM element or a MinimalElement. */
533
+ function optionAttr(el, name) {
534
+ return el.getAttribute?.(name) ?? el.attributes?.get?.(name) ?? null;
535
+ }
536
+
455
537
  // ── Gap Inference ────────────────────────────────────────────────────────
456
538
 
457
539
  /**
@@ -192,6 +192,15 @@ function walkNode(el, context) {
192
192
 
193
193
  // ── Handle children ──
194
194
 
195
+ // ChoicePicker / Select carry their <option>/<optgroup> children as an
196
+ // `options` prop (built in extractProps), not as child components. Recursing
197
+ // the <option>s would emit Text nodes that <select-ui> rejects — renders empty
198
+ // + "ignoring unrecognized child" warning (FB-79). Treat as a self-contained leaf.
199
+ if (a2uiType === 'ChoicePicker') {
200
+ context.components.push({ id, component: a2uiType, ...props });
201
+ return id;
202
+ }
203
+
195
204
  // Leaf types: these render their own text, don't recurse into children
196
205
  const LEAF_TYPES = new Set([
197
206
  'Text', 'Button', 'Kbd', 'Code', 'Badge', 'Image', 'Divider',