@adia-ai/a2ui-compose 0.6.42 → 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 +15 -0
- package/package.json +1 -1
- package/transpiler/transpiler-maps.js +89 -0
- package/transpiler/transpiler.js +57 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
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
|
+
|
|
9
|
+
## [0.6.43] — 2026-05-27
|
|
10
|
+
|
|
11
|
+
### Fixed — Cycle-18 transpiler substrate sweeps (RL + RL2)
|
|
12
|
+
|
|
13
|
+
- **`transpiler/transpiler.js`** — two substrate sweeps from gen-review cycle-18:
|
|
14
|
+
- **Ralph Loop**: 6 visual bugs fixed at SoT — header slot grammar, leaf children, button slot preservation.
|
|
15
|
+
- **RL2**: 10 visual bugs across header slot, leaf children, calendar widget.
|
|
16
|
+
- **`transpiler/transpiler-maps.js`** — preserve `slot=` annotations through transpilation so card-ui transpiled `text-ui` chunks match the substrate CSS selector.
|
|
17
|
+
|
|
3
18
|
## [0.6.42] — 2026-05-26
|
|
4
19
|
|
|
5
20
|
### Maintenance
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui-compose",
|
|
3
|
-
"version": "0.6.
|
|
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": {
|
|
@@ -310,6 +310,13 @@ export function extractProps(el, a2uiType) {
|
|
|
310
310
|
const props = {};
|
|
311
311
|
const attr = (name) => el.getAttribute?.(name) ?? el.attributes?.get?.(name) ?? null;
|
|
312
312
|
|
|
313
|
+
// Universal: preserve [slot] for every element. Slot is a layout-bearing
|
|
314
|
+
// HTML attribute that card-ui / drawer-ui / etc. depend on via :has(> [slot])
|
|
315
|
+
// grid activation. Yamls don't declare slot as a prop (it's an HTML standard
|
|
316
|
+
// attribute, not a component prop), so the catalog pass below won't pick it up.
|
|
317
|
+
const slotAttr = attr('slot');
|
|
318
|
+
if (slotAttr) props.slot = slotAttr;
|
|
319
|
+
|
|
313
320
|
// §163 (v0.5.4) — Catalog-driven generic extraction FIRST. Fills in
|
|
314
321
|
// every prop the v0.9 catalog declares for this type (label, icon,
|
|
315
322
|
// variant=, etc.). Legacy per-type blocks below run AFTER and can
|
|
@@ -428,6 +435,25 @@ export function extractProps(el, a2uiType) {
|
|
|
428
435
|
if (ariaLabel) props.label = ariaLabel;
|
|
429
436
|
}
|
|
430
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
|
+
|
|
431
457
|
// ── Data attributes pass-through ──
|
|
432
458
|
if (el.attributes) {
|
|
433
459
|
const attrs = el.attributes instanceof Map ? el.attributes : el.attributes;
|
|
@@ -445,6 +471,69 @@ export function extractProps(el, a2uiType) {
|
|
|
445
471
|
return props;
|
|
446
472
|
}
|
|
447
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
|
+
|
|
448
537
|
// ── Gap Inference ────────────────────────────────────────────────────────
|
|
449
538
|
|
|
450
539
|
/**
|
package/transpiler/transpiler.js
CHANGED
|
@@ -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',
|
|
@@ -200,7 +209,54 @@ function walkNode(el, context) {
|
|
|
200
209
|
]);
|
|
201
210
|
|
|
202
211
|
if (LEAF_TYPES.has(a2uiType)) {
|
|
203
|
-
|
|
212
|
+
// Leaf types still need to carry slotted or container children in two
|
|
213
|
+
// shapes:
|
|
214
|
+
//
|
|
215
|
+
// (1) Trailing icons / kbd shortcuts inside Button:
|
|
216
|
+
// `<button-ui text="Next"><icon-ui slot="trailing" name="caret-right">`
|
|
217
|
+
// (2) Slotted container wrappers that themselves transpile to a leaf
|
|
218
|
+
// type but hold inline child elements:
|
|
219
|
+
// `<span slot="heading"><text-ui strong>Title</text-ui> <badge-ui>...`
|
|
220
|
+
// The span maps to Text via HTML_TAG_MAP, but it functions as a
|
|
221
|
+
// flex container (per card.css `> [slot="heading"]`) and needs
|
|
222
|
+
// to keep its children.
|
|
223
|
+
//
|
|
224
|
+
// Rule: if the element has ANY element children, recurse all of them.
|
|
225
|
+
// Pure text leaves (no element children) keep the text-prop path.
|
|
226
|
+
const elementChildren = (el.children || []).filter(c => c.tagName);
|
|
227
|
+
if (elementChildren.length === 0) {
|
|
228
|
+
context.components.push({ id, component: a2uiType, ...props });
|
|
229
|
+
return id;
|
|
230
|
+
}
|
|
231
|
+
// When a leaf has element children, the auto-extracted textContent prop
|
|
232
|
+
// would be the CONCATENATED descendant text (e.g.
|
|
233
|
+
// <text-ui>$49<text-ui>/month</text-ui></text-ui>
|
|
234
|
+
// has textContent "$49/month"), causing every label to render twice
|
|
235
|
+
// alongside the child components. Replace it with the element's DIRECT
|
|
236
|
+
// text nodes only — text the element OWNS, not what its children own.
|
|
237
|
+
// The minimal HTML parser represents text nodes as pseudo-elements
|
|
238
|
+
// with tagName === '' inside the children array.
|
|
239
|
+
const directText = (el.children || [])
|
|
240
|
+
.filter(n => !n.tagName)
|
|
241
|
+
.map(n => n.textContent || '')
|
|
242
|
+
.join(' ').replace(/\s+/g, ' ').trim();
|
|
243
|
+
const containerProps = { ...props };
|
|
244
|
+
if (directText) {
|
|
245
|
+
// Preserve the direct text on whichever prop the type uses
|
|
246
|
+
if (a2uiType === 'Text' || a2uiType === 'Kbd' || a2uiType === 'Code') containerProps.textContent = directText;
|
|
247
|
+
else if (a2uiType === 'Button' || a2uiType === 'Badge') {
|
|
248
|
+
if (containerProps.text === undefined) containerProps.text = directText;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
delete containerProps.textContent;
|
|
252
|
+
delete containerProps.text;
|
|
253
|
+
}
|
|
254
|
+
const childIds = [];
|
|
255
|
+
for (const child of elementChildren) {
|
|
256
|
+
const childId = walkNode(child, context);
|
|
257
|
+
if (childId) childIds.push(childId);
|
|
258
|
+
}
|
|
259
|
+
context.components.push({ id, component: a2uiType, ...containerProps, children: childIds });
|
|
204
260
|
return id;
|
|
205
261
|
}
|
|
206
262
|
|