@adia-ai/a2ui-compose 0.7.26 → 0.7.27

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,4 +1,13 @@
1
1
  # Changelog — @adia-ai/a2ui-compose
2
+ ## [0.7.27] — 2026-07-12
3
+
4
+ ### Fixed
5
+ - **Transpiler — `[span]` is now universally preserved (TKT-0002).** `extractProps()` had a universal preserve for `[slot]` but not `[span]` — the same class of cross-cutting, layout-bearing attribute, consumed purely via `grid.css`'s `& > [span="N"]` child selectors and never declared in any component's own yaml `props:`. Every transpiled asymmetric grid collapsed to equal columns (the admin-dashboard's 3:2 Overview/Recent-Sales split was the operator-reported case). Regression tests cover `span="3"`, `span="full"`, and the absent case.
6
+ - **Transpiler — `Tag` is a leaf type; labels ride the `text` prop (TKT-0004 A).** `Tag` was missing from `LEAF_TYPES`, so `<tag-ui size="sm">label</tag-ui>` transpiled to `Tag > Text variant=body` — a full body-size paragraph inside the pill, inflating every converted chip (581 instances across 233 docs pages measured). Labels now ride tag.yaml's documented `attr(text)` contract, in both the pure-text and element-children extraction paths.
7
+ - **`strategies/zettel/issue-reporter.js` — issue storage repointed to `qa/findings/issues/`.** The previous path would have silently recreated the retired `.brain/` tree at runtime on the first `report_issue` call.
8
+
9
+ ### Maintenance
10
+ - Transpiler test suite: `extract-props.test.js` 19 → 24 cases (span preservation ×3, Tag text contract ×2).
2
11
 
3
12
  ## [0.7.26] — 2026-07-04
4
13
 
@@ -916,7 +925,7 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 ove
916
925
  - **`compose/strategies/monolithic/generate-pro.js` + companion `_shared/fragment-expander.js` — fragment machinery removed (§37).** Fragments retired; 85 compositions inlined with previously-referenced fragment content. Expander deleted; its caller's try/catch in `generator-adapter.js` handles the absence gracefully.
917
926
  - **MCP server `mcp/server.js` (§37).** Removed `get_fragment` tool, simplified `zettel_stats`, updated imports + boot logs + engine description to reflect the post-fragment world.
918
927
 
919
- See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutting arc narrative + [.claude/docs/journal/2026/05/2026-05-12.md](../../../.claude/docs/journal/2026/05/2026-05-12.md) §§ 37 / 38 / 41 / 47 for per-§ details.
928
+ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutting arc narrative + `.claude/docs/journal/2026/05/2026-05-12.md` §§ 37 / 38 / 41 / 47 for per-§ details.
920
929
  ## [0.4.3] - 2026-05-11
921
930
 
922
931
  ### Fixed
@@ -1200,7 +1209,7 @@ packages now share one version, governed by
1200
1209
  [`.claude/docs/specs/package-architecture.md` § 15 (Versioning Policy)](../../../.claude/docs/specs/package-architecture.md#15-versioning-policy).
1201
1210
  This release also lands the `engine/` ↔ `engines/` collision fix from
1202
1211
  T3 of the
1203
- [`.claude/docs/plans/packages-architecture-fixes-2026-05-02.md`](../../../.brain/archive/2026-Q2/PLAN-packages-architecture-fixes-2026-05-02.md)
1212
+ `.claude/docs/plans/packages-architecture-fixes-2026-05-02.md`
1204
1213
  plan.
1205
1214
 
1206
1215
  ### Changed
@@ -1255,8 +1264,8 @@ multi-turn surface, plus a first-class telemetry surface that auto-fires
1255
1264
  issue tickets on engine failure paths.
1256
1265
 
1257
1266
  Spec: [`.claude/docs/specs/genui-multiturn-architecture.md`](../../../.claude/docs/specs/genui-multiturn-architecture.md) (Active v0.1.0).
1258
- Plan: [`.claude/docs/plans/genui-multiturn-rollout-2026-04-28.md`](../../../.claude/docs/plans/genui-multiturn-rollout-2026-04-28.md) (Phase A scoped).
1259
- ADR: [`0008-multiturn-genui-architecture.md`](../../../.brain/adrs/0008-multiturn-genui-architecture.md).
1267
+ Plan: `.claude/docs/plans/genui-multiturn-rollout-2026-04-28.md` (Phase A scoped).
1268
+ ADR: `0008-multiturn-genui-architecture.md`.
1260
1269
 
1261
1270
  ### Added (engine modules)
1262
1271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-compose",
3
- "version": "0.7.26",
3
+ "version": "0.7.27",
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": {
@@ -7,7 +7,7 @@
7
7
  * 3. Engine auto-fire — internal failure paths trigger `autoReport(reason)`.
8
8
  * ctx.reporter = 'auto'. Suppressed when ctx.evalMode is true.
9
9
  *
10
- * Every issue lands as immutable JSON under `.brain/audit-history/issues/`.
10
+ * Every issue lands as immutable JSON under `qa/findings/issues/`.
11
11
  * Traces > 200KB spill to a sidecar `.trace.json` next to the issue.
12
12
  *
13
13
  * Spec: .claude/docs/specs/genui-multiturn-architecture.md §3.5 + §4.6 + §11.
@@ -23,7 +23,7 @@ import { fileURLToPath } from 'node:url';
23
23
 
24
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
25
  const REPO_ROOT = resolve(__dirname, '../../../../..');
26
- const DEFAULT_STORAGE_ROOT = resolve(REPO_ROOT, '.brain/audit-history/issues');
26
+ const DEFAULT_STORAGE_ROOT = resolve(REPO_ROOT, 'qa/findings/issues');
27
27
  const TRACE_INLINE_THRESHOLD_BYTES = 200 * 1024;
28
28
 
29
29
  const VALID_TYPES = new Set(['bug', 'training-gap', 'protocol-gap', 'ux-feedback']);
@@ -225,4 +225,39 @@ describe('§163 extractProps — catalog-driven prop fidelity', () => {
225
225
  expect(props.variant).toBe('outline');
226
226
  });
227
227
  });
228
+
229
+ describe('TKT-0004 — Tag label via text prop (attr(text) contract)', () => {
230
+ it('falls back to light-DOM textContent when no text= attribute', () => {
231
+ const el = elem({ size: 'sm', variant: 'success' }, { text: '~1 min · no tools' });
232
+ const props = extractProps(el, 'Tag');
233
+ expect(props.text).toBe('~1 min · no tools');
234
+ expect(props.size).toBe('sm');
235
+ });
236
+
237
+ it('an explicit text= attribute wins over textContent', () => {
238
+ const el = elem({ text: 'explicit' }, { text: 'ignored light-dom' });
239
+ const props = extractProps(el, 'Tag');
240
+ expect(props.text).toBe('explicit');
241
+ });
242
+ });
243
+
244
+ describe('TKT-0002 — [span] universal preservation', () => {
245
+ it('preserves span on a Card (grid-ui child), same as slot', () => {
246
+ const el = elem({ span: '3' });
247
+ const props = extractProps(el, 'Card');
248
+ expect(props.span).toBe('3');
249
+ });
250
+
251
+ it('preserves span="full" verbatim (not coerced to a number)', () => {
252
+ const el = elem({ span: 'full' });
253
+ const props = extractProps(el, 'Card');
254
+ expect(props.span).toBe('full');
255
+ });
256
+
257
+ it('omits span when absent — no stray empty prop', () => {
258
+ const el = elem({});
259
+ const props = extractProps(el, 'Card');
260
+ expect(props.span).toBeUndefined();
261
+ });
262
+ });
228
263
  });
@@ -317,6 +317,17 @@ export function extractProps(el, a2uiType) {
317
317
  const slotAttr = attr('slot');
318
318
  if (slotAttr) props.slot = slotAttr;
319
319
 
320
+ // Universal: preserve [span] for every element. Span is a layout-bearing
321
+ // attribute grid-ui's child reads via a pure CSS attribute selector
322
+ // (grid.css: `& > [span="3"] { grid-column: span 3; }`) — it belongs to
323
+ // "whatever this grid child happens to be," never to a specific
324
+ // component's own prop schema, so no .yaml declares it and the
325
+ // catalog-driven pass below silently drops it (TKT-0002: collapsed the
326
+ // admin-dashboard Overview/Recent Sales 3:2 split to two equal, unspanned
327
+ // columns).
328
+ const spanAttr = attr('span');
329
+ if (spanAttr) props.span = spanAttr;
330
+
320
331
  // §163 (v0.5.4) — Catalog-driven generic extraction FIRST. Fills in
321
332
  // every prop the v0.9 catalog declares for this type (label, icon,
322
333
  // variant=, etc.). Legacy per-type blocks below run AFTER and can
@@ -351,6 +362,15 @@ export function extractProps(el, a2uiType) {
351
362
  }
352
363
  }
353
364
 
365
+ // ── Tag ── (TKT-0004: tag-ui renders its label via CSS attr(text) —
366
+ // tag.yaml documents textContent as routing to the text attribute. The
367
+ // authored form is light-DOM content (<tag-ui size="sm">label</tag-ui>),
368
+ // so fall back to textContent when no text= attribute is set.)
369
+ if (a2uiType === 'Tag' && props.text === undefined) {
370
+ const text = (el.textContent || '').trim().replace(/\s+/g, ' ');
371
+ if (text) props.text = text;
372
+ }
373
+
354
374
  // ── Link ── (§47 — <a> tags map to Link, not Button; carries href, target, rel)
355
375
  if (a2uiType === 'Link') {
356
376
  const text = (el.textContent || '').trim().replace(/\s+/g, ' ');
@@ -201,9 +201,13 @@ function walkNode(el, context) {
201
201
  return id;
202
202
  }
203
203
 
204
- // Leaf types: these render their own text, don't recurse into children
204
+ // Leaf types: these render their own text, don't recurse into children.
205
+ // Tag joined 2026-07-12 (TKT-0004): tag-ui renders its label via CSS
206
+ // attr(text) — as a container, its light-DOM label became a child Text
207
+ // at body size, inflating every converted chip site-wide (581 instances
208
+ // across 233 pages measured).
205
209
  const LEAF_TYPES = new Set([
206
- 'Text', 'Button', 'Kbd', 'Code', 'Badge', 'Image', 'Divider',
210
+ 'Text', 'Button', 'Kbd', 'Code', 'Badge', 'Tag', 'Image', 'Divider',
207
211
  'TextField', 'CheckBox', 'Toggle', 'Radio', 'Slider', 'Search',
208
212
  'Upload', 'ColorPicker', 'DateTimeInput', 'TextArea', 'OtpInput',
209
213
  ]);
@@ -244,7 +248,7 @@ function walkNode(el, context) {
244
248
  if (directText) {
245
249
  // Preserve the direct text on whichever prop the type uses
246
250
  if (a2uiType === 'Text' || a2uiType === 'Kbd' || a2uiType === 'Code') containerProps.textContent = directText;
247
- else if (a2uiType === 'Button' || a2uiType === 'Badge') {
251
+ else if (a2uiType === 'Button' || a2uiType === 'Badge' || a2uiType === 'Tag') {
248
252
  if (containerProps.text === undefined) containerProps.text = directText;
249
253
  }
250
254
  } else {