@adia-ai/a2ui-retrieval 0.7.19 → 0.7.20

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,16 @@
1
1
  # Changelog — @adia-ai/a2ui-retrieval
2
2
 
3
+ ## [0.7.20] — 2026-06-10
4
+
5
+ ### Fixed
6
+ - **Catalog loaders work on statically-served sites.** `component-catalog.js` + `catalog.js` gain the same static-fetch branch as the compose loaders: one consolidated `catalog-a2ui_0_9.json` request (181 component schemas) replaces the vite-only glob over 138 sidecars.
7
+
8
+ ### Added
9
+ - **3 anti-pattern rules from the gen-UI contact-form incident** (these feed BOTH the generation prompt and post-validation): `noCssPropAttrs` (`display=`/`visibility=` as markup attributes — components silently ignore them, so "hidden" content renders visibly), `inputTypeTextarea` (`input-ui[type="textarea"]` → `textarea-ui`), `fieldOwnsLabelHintError` (a sibling `text-ui` label / raw `<label>` / `<small>` error inside `field-ui` → use `label=`/`hint=`/`error=`).
10
+
11
+ ### Fixed
12
+ - **`noInventedComponents` known-tags list regenerated from the yaml `tag:` SoT (145 tags).** The hand-rolled list was stale — it was missing `field-ui` (a core primitive), so every canonical labeled form false-positived as an invented component.
13
+
3
14
  ## [0.7.19] — 2026-06-10
4
15
 
5
16
  ### Maintenance
package/anti-patterns.js CHANGED
@@ -67,21 +67,36 @@ const antiPatterns = [
67
67
  check: (html) => {
68
68
  const tags = html.match(/<([a-z]+-(?:n|ui))\b/gi) || [];
69
69
  const known = new Set([
70
- 'row-ui', 'col-ui', 'list-ui', 'grid-ui', 'text-ui', 'image-ui',
71
- 'icon-ui', 'divider-ui', 'badge-ui', 'avatar-ui', 'progress-ui',
72
- 'skeleton-ui', 'input-ui', 'input-ui', 'check-ui', 'switch-ui',
73
- 'slider-ui', 'select-ui', 'datetime-ui', 'search-ui', 'upload-ui',
74
- 'button-ui', 'card-ui', 'tabs-ui', 'tab-ui', 'pane-ui', 'modal-ui',
75
- 'drawer-ui', 'toast-ui', 'popover-ui', 'stream-ui', 'form-container-ui',
76
- 'table-ui', 'chart-ui', 'embed-ui', 'stack-ui', 'block-ui', 'code-ui',
77
- 'textarea-ui', 'radio-ui', 'radio-ui', 'tag-ui', 'accordion-ui',
78
- 'modal-ui', 'alert-ui', 'tooltip-ui', 'menu-ui', 'breadcrumb-ui',
79
- 'nav-ui', 'pagination-ui', 'avatar-group-ui', 'segmented-ui',
80
- 'segment-ui', 'command-ui', 'command-item-ui', 'command-group-ui',
81
- 'calendar-picker-ui', 'color-picker-ui', 'kbd-ui', 'toolbar-ui',
82
- 'otp-input-ui',
83
- 'theme-ui', 'router-ui', 'adia-root', 'noodles-ui',
84
- 'listbox-ui', 'icon-provider',
70
+ // Regenerated 2026-06-10 from the per-component yaml `tag:` SoT
71
+ // (node: scan packages/web-components/components/*/*.yaml) + the
72
+ // runtime/infra extras below. The previous hand-rolled list was
73
+ // stale — it was MISSING field-ui (a core primitive), so every
74
+ // canonical labeled form flagged noInventedComponents.
75
+ 'accordion-item-ui', 'accordion-ui', 'action-item-ui', 'action-list-ui', 'adia-root', 'agent-artifact-ui',
76
+ 'agent-feedback-bar-ui', 'agent-questions-ui', 'agent-reasoning-ui', 'agent-suggestions-ui', 'agent-trace-ui', 'alert-ui',
77
+ 'aside-ui', 'avatar-group-ui', 'avatar-ui', 'badge-ui', 'block-ui', 'blockquote-ui',
78
+ 'breadcrumb-ui', 'button-ui', 'calendar-grid-ui', 'calendar-picker-ui', 'canvas-ui', 'card-ui',
79
+ 'chart-legend-ui', 'chart-ui', 'chat-input-ui', 'chat-thread-ui', 'check-ui', 'code-ui',
80
+ 'col-ui', 'color-input-ui', 'color-picker-ui', 'combobox-ui', 'command-ui', 'context-menu-ui',
81
+ 'date-range-picker-ui', 'datetime-picker-ui', 'datetime-ui', 'demo-toggle-ui', 'description-list-ui', 'divider-ui',
82
+ 'drawer-ui', 'embed-ui', 'empty-state-ui', 'feed-item-ui', 'feed-ui', 'field-ui',
83
+ 'fields-ui', 'footer-ui', 'form-container-ui', 'frame-ui', 'grid-ui', 'header-ui',
84
+ 'heatmap-ui', 'icon-provider', 'icon-ui', 'image-ui', 'inline-edit-ui', 'inline-message-ui',
85
+ 'input-ui', 'inspector-ui', 'integration-card-ui', 'kbd-ui', 'link-ui', 'list-item-ui',
86
+ 'list-ui', 'list-window-ui', 'listbox-ui', 'loading-overlay-ui', 'mark-ui', 'menu-divider-ui',
87
+ 'menu-item-ui', 'menu-label-ui', 'menu-ui', 'modal-ui', 'nav-group-ui', 'nav-item-ui',
88
+ 'nav-ui', 'noodles-ui', 'number-format-ui', 'option-card-ui', 'otp-input-ui', 'page-ui',
89
+ 'pagination-ui', 'pane-ui', 'password-strength-ui', 'pipeline-status-ui', 'popover-ui', 'preview-ui',
90
+ 'progress-row-ui', 'progress-ui', 'qr-code-ui', 'radio-ui', 'range-ui', 'rating-ui',
91
+ 'relative-time-ui', 'richtext-ui', 'router-ui', 'row-ui', 'search-ui', 'section-ui',
92
+ 'segment-ui', 'segmented-ui', 'select-ui', 'skeleton-ui', 'skip-nav-ui', 'slider-ui',
93
+ 'spinner-ui', 'stack-ui', 'stat-ui', 'step-progress-ui', 'stepper-item-ui', 'stepper-ui',
94
+ 'stream-ui', 'swatch-ui', 'swiper-ui', 'switch-ui', 'tab-ui', 'table-toolbar-ui',
95
+ 'table-ui', 'tabs-ui', 'tag-ui', 'tags-input-ui', 'text-ui', 'textarea-ui',
96
+ 'theme-provider', 'theme-ui', 'time-picker-ui', 'timeline-item-ui', 'timeline-ui', 'toast-ui',
97
+ 'toc-ui', 'toggle-group-ui', 'toggle-option-ui', 'toggle-scheme-ui', 'toolbar-group-ui', 'toolbar-ui',
98
+ 'tooltip-ui', 'tour-step-ui', 'tour-ui', 'tree-item-ui', 'tree-ui', 'upload-ui',
99
+ 'visually-hidden-ui',
85
100
  ]);
86
101
  for (const match of tags) {
87
102
  const tag = match.slice(1).toLowerCase();
@@ -91,6 +106,32 @@ const antiPatterns = [
91
106
  },
92
107
  fix: 'Only use registered AdiaUI types. Check the registry for available components.',
93
108
  },
109
+ {
110
+ name: 'noCssPropAttrs',
111
+ description: 'CSS properties (display, visibility) are not markup attributes. Components silently ignore unknown attributes, so display="none" content renders VISIBLY — overlapping its siblings.',
112
+ check: /\s(?:display|visibility)\s*=\s*"/i,
113
+ fix: 'Remove the attribute. To hide content conditionally, omit the element from the tree (or use the component\'s documented state attribute, e.g. [hidden] on plain containers).',
114
+ },
115
+ {
116
+ name: 'inputTypeTextarea',
117
+ description: 'input-ui has no type="textarea" — multi-line input is its own component.',
118
+ check: /<input-ui[^>]*\btype\s*=\s*"textarea"/i,
119
+ fix: 'Use <textarea-ui rows="…"> for multi-line text input.',
120
+ },
121
+ {
122
+ name: 'fieldOwnsLabelHintError',
123
+ description: 'field-ui OWNS the label / hint / error chrome via its label= / hint= / error= attributes. Emitting a sibling text-ui "label", a raw <label>, or a <small> error inside field-ui double-renders and breaks the label-to-control binding (field-ui binds the first slot-less child as the control — a text-ui label steals that binding).',
124
+ check: (html) => {
125
+ // LLM-emitted label-ish children inside field-ui. The stamped internal
126
+ // label carries data-field-label — exclude it so post-render HTML
127
+ // doesn't false-positive.
128
+ if (/<field-ui[^>]*>\s*(?:<text-ui[^>]*variant\s*=\s*"label"|<label(?![^>]*data-field-label))/i.test(html)) return true;
129
+ // <small> is not an AdiaUI component anywhere; inside field-ui it is
130
+ // always a hand-rolled error line.
131
+ return /<field-ui[^>]*>[\s\S]*?<small[\s>]/i.test(html);
132
+ },
133
+ fix: 'Use <field-ui label="Full Name" hint="…" error="…"><input-ui …/></field-ui> — exactly ONE slot-less child (the control); field-ui renders the label, hint, and error itself.',
134
+ },
94
135
  {
95
136
  name: 'slotOnContainer',
96
137
  description: 'Slot attributes (slot="heading", slot="description", slot="action") must go on the content children inside Header/Footer, not on Header/Footer elements themselves.',
package/catalog.js CHANGED
@@ -39,8 +39,19 @@ async function loadSchemas() {
39
39
  }
40
40
  } catch { /* components dir missing */ }
41
41
  } else {
42
+ // Browser. Vite rewrites the glob CALL at transform time; on the static
43
+ // deploy that call throws (no Vite — `import.meta.glob` is a compile-time
44
+ // macro, undefined at runtime) → we fall through to fetching the
45
+ // CONSOLIDATED catalog. `typeof import.meta.glob` is undefined in BOTH
46
+ // contexts, so the only reliable discriminator is whether the CALL
47
+ // produced loaders. See composition-library.js for the full rationale.
48
+ let modules = null;
42
49
  try {
43
- const modules = import.meta.glob('../../web-components/components/*/*.a2ui.json', { query: '?raw', import: 'default' });
50
+ modules = import.meta.glob('../../web-components/components/*/*.a2ui.json', { query: '?raw', import: 'default' });
51
+ } catch { /* no Vite transform — static deploy */ }
52
+
53
+ if (modules && Object.keys(modules).length > 0) {
54
+ // Vite dev — iterate the glob loaders.
44
55
  for (const [, loader] of Object.entries(modules)) {
45
56
  try {
46
57
  const raw = await loader();
@@ -49,7 +60,26 @@ async function loadSchemas() {
49
60
  if (tag) schemaByTag.set(tag, schema);
50
61
  } catch { /* skip invalid */ }
51
62
  }
52
- } catch { /* glob not available */ }
63
+ } else {
64
+ // Static deploy — fetch the consolidated catalog once. Its `components`
65
+ // values carry the same `x-adiaui.tag` shape as the per-component
66
+ // sidecars (2026-06-10). Relative-to-module so it resolves under any
67
+ // mount point + the dist symlink layout.
68
+ try {
69
+ const url = new URL('../corpus/catalog-a2ui_0_9.json', import.meta.url);
70
+ const res = await fetch(url);
71
+ if (res.ok) {
72
+ const catalog = await res.json();
73
+ const comps = catalog?.components;
74
+ if (comps && typeof comps === 'object') {
75
+ for (const schema of Object.values(comps)) {
76
+ const tag = tagFromSchema(schema);
77
+ if (tag) schemaByTag.set(tag, schema);
78
+ }
79
+ }
80
+ }
81
+ } catch { /* consolidated catalog not present */ }
82
+ }
53
83
  }
54
84
  }
55
85
 
@@ -24,7 +24,12 @@ const _componentData = new Map();
24
24
  let _loaded = false;
25
25
  let _loadPromise = null;
26
26
 
27
- // Vite resolves this at build time; at runtime in Node the variable is unused.
27
+ // Vite rewrites this CALL at transform time. On the static deploy the call
28
+ // throws (no Vite) → `_globA2UIModules` null → fetch the CONSOLIDATED
29
+ // catalog (one request) instead of 138 per-component sidecars.
30
+ // `import.meta.glob` is a compile-time macro (undefined at runtime in both
31
+ // Vite and static) — the only reliable discriminator is whether the CALL
32
+ // produced loaders. See composition-library.js for the full rationale.
28
33
  let _globA2UIModules = null;
29
34
  if (!IS_NODE) {
30
35
  try {
@@ -33,9 +38,20 @@ if (!IS_NODE) {
33
38
  import: 'default',
34
39
  });
35
40
  } catch {
36
- // Not in a Vite contextno component data
41
+ // No Vite transform here (static deploy) fall through to the fetch branch.
37
42
  }
38
43
  }
44
+ const HAS_VITE_GLOB =
45
+ !IS_NODE && !!_globA2UIModules && Object.keys(_globA2UIModules).length > 0;
46
+
47
+ // Static-deploy source: the consolidated catalog co-located in the corpus.
48
+ // Its `components` map values are byte-shape-identical to the standalone
49
+ // `.a2ui.json` sidecars (verified 2026-06-10), so one fetch replaces 138.
50
+ // Relative-to-module from retrieval/ → ../a2ui-corpus is `../corpus/`.
51
+ const _CONSOLIDATED_CATALOG_URL =
52
+ !IS_NODE && !HAS_VITE_GLOB
53
+ ? new URL('../corpus/catalog-a2ui_0_9.json', import.meta.url)
54
+ : null;
39
55
 
40
56
  async function _loadNode() {
41
57
  const fs = await import(/* @vite-ignore */ 'node:fs/promises');
@@ -103,12 +119,29 @@ async function _loadBrowser() {
103
119
  }
104
120
  }
105
121
 
122
+ async function _loadStatic() {
123
+ if (!_CONSOLIDATED_CATALOG_URL) return;
124
+ try {
125
+ const res = await fetch(_CONSOLIDATED_CATALOG_URL);
126
+ if (!res.ok) return;
127
+ const catalog = await res.json();
128
+ const comps = catalog?.components;
129
+ if (!comps || typeof comps !== 'object') return;
130
+ for (const data of Object.values(comps)) {
131
+ if (data?.title) _componentData.set(data.title, data);
132
+ }
133
+ } catch {
134
+ // Consolidated catalog not present / unparseable — leave empty.
135
+ }
136
+ }
137
+
106
138
  export async function loadCatalog() {
107
139
  if (_loaded) return;
108
140
  if (_loadPromise) return _loadPromise;
109
141
  _loadPromise = (async () => {
110
142
  if (IS_NODE) await _loadNode();
111
- else await _loadBrowser();
143
+ else if (HAS_VITE_GLOB) await _loadBrowser();
144
+ else await _loadStatic();
112
145
  })();
113
146
  await _loadPromise;
114
147
  _loaded = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-retrieval",
3
- "version": "0.7.19",
3
+ "version": "0.7.20",
4
4
  "description": "AdiaUI A2UI retrieval layer \u2014 catalog lookup, intent classification, domain routing, pattern + anti-pattern matching, clarity + context assembly. Consumed by the compose engine and any A2UI-protocol tooling that needs to reason about user intent against the catalog.",
5
5
  "type": "module",
6
6
  "main": "./index.js",