@adia-ai/a2ui-compose 0.4.6 → 0.4.7
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
|
@@ -12,6 +12,14 @@ generator graph.
|
|
|
12
12
|
|
|
13
13
|
_No pending changes._
|
|
14
14
|
|
|
15
|
+
## [0.4.7] - 2026-05-12
|
|
16
|
+
|
|
17
|
+
### Changed — `strategies/monolithic/_shared.js` `getComponentCatalog()` reads canonical catalog (§72)
|
|
18
|
+
|
|
19
|
+
The legacy reader of `@adia-ai/a2ui-corpus/patterns/_components.json` has been migrated to read `@adia-ai/a2ui-corpus/catalog-a2ui_0_9.json` (the canonical v0.9 catalog, already the package root export). Per-component aliases are now lifted from `components[name].x-adiaui.synonyms.tags`. Same legacy output shape (`{ <Name>: { aliases: string[] } }`); zero behavior change for downstream callers.
|
|
20
|
+
|
|
21
|
+
Closes the §65 carry-over from v0.4.6 — `@adia-ai/a2ui-corpus` `patterns/` + `compositions/` are now deleted from disk + tarball.
|
|
22
|
+
|
|
15
23
|
## [0.4.6] - 2026-05-12
|
|
16
24
|
|
|
17
25
|
### Changed — `core/` retirement follow-through (§64, v0.4.6)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui-compose",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "AdiaUI A2UI compose engine — 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": {
|
|
@@ -98,24 +98,43 @@ function adaptV09Component(a2uiData) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
// Component prop catalog — loaded lazily for prompt injection
|
|
101
|
+
// Component prop catalog — loaded lazily for prompt injection.
|
|
102
|
+
//
|
|
103
|
+
// Since §65 (v0.4.7), reads from the canonical v0.9 catalog at
|
|
104
|
+
// `corpus/catalog-a2ui_0_9.json` (assembled from yamls by `npm run
|
|
105
|
+
// components`). Previously read the hand-maintained
|
|
106
|
+
// `corpus/patterns/_components.json` which was retired in §65.
|
|
107
|
+
// Aliases come from `x-adiaui.synonyms.tags` per the v0.9 sidecar shape;
|
|
108
|
+
// migrated in §65 step 1 — 17 yamls populated with synonyms.tags from
|
|
109
|
+
// the prior `_components.json` aliases field.
|
|
102
110
|
let _componentCatalog = null;
|
|
103
111
|
async function getComponentCatalog() {
|
|
104
112
|
if (_componentCatalog) return _componentCatalog;
|
|
105
113
|
try {
|
|
106
114
|
const IS_NODE = typeof process !== 'undefined' && process.versions?.node;
|
|
115
|
+
let catalogJson;
|
|
107
116
|
if (IS_NODE) {
|
|
108
117
|
const fs = await import(/* @vite-ignore */ 'node:fs/promises');
|
|
109
118
|
const path = await import(/* @vite-ignore */ 'node:path');
|
|
110
119
|
const url = await import(/* @vite-ignore */ 'node:url');
|
|
111
120
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
112
|
-
const raw = await fs.readFile(path.join(__dirname, '../../../corpus/
|
|
113
|
-
|
|
121
|
+
const raw = await fs.readFile(path.join(__dirname, '../../../corpus/catalog-a2ui_0_9.json'), 'utf8');
|
|
122
|
+
catalogJson = JSON.parse(raw);
|
|
114
123
|
} else {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
const resp = await fetch(new URL('../../../corpus/catalog-a2ui_0_9.json', import.meta.url));
|
|
125
|
+
catalogJson = resp.ok ? await resp.json() : {};
|
|
126
|
+
}
|
|
127
|
+
// Adapt the v0.9 catalog (JSON Schema with `components: {Name: {x-adiaui: {...}}}`)
|
|
128
|
+
// into the legacy {Name: {aliases, ...}} shape getComponentCatalog consumers
|
|
129
|
+
// expect. Aliases live at `x-adiaui.synonyms.tags`.
|
|
130
|
+
const comps = catalogJson?.components || {};
|
|
131
|
+
_componentCatalog = {};
|
|
132
|
+
for (const [name, def] of Object.entries(comps)) {
|
|
133
|
+
const ext = def?.['x-adiaui'] || {};
|
|
134
|
+
const syns = (ext.synonyms && typeof ext.synonyms === 'object') ? ext.synonyms : null;
|
|
135
|
+
_componentCatalog[name] = {
|
|
136
|
+
aliases: Array.isArray(syns?.tags) ? syns.tags : [],
|
|
137
|
+
};
|
|
119
138
|
}
|
|
120
139
|
} catch {
|
|
121
140
|
_componentCatalog = {};
|
|
@@ -1,43 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Composition Library — zettel-style loader for A2UI
|
|
2
|
+
* Composition Library — zettel-style loader for harvested A2UI chunks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Reads `corpus/chunks/<name>.json` whenever the chunk carries both
|
|
5
|
+
* `metadata` (from `data-chunk-*` source-HTML attrs per §40) AND
|
|
6
|
+
* `template` (from the harvester's transpileHTML pass per §41). Chunks
|
|
7
|
+
* are normalized to composition shape by hoisting `metadata.*` to
|
|
8
|
+
* top level so consumers can search them uniformly.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
10
|
+
* The legacy `corpus/compositions/<domain>/<name>.json` glob was
|
|
11
|
+
* retired in v0.4.7 (§65). The harvested-chunks substrate (~30
|
|
12
|
+
* annotated chunks at landing time) is the canonical retrieval
|
|
13
|
+
* surface; per the project's "source-of-truth = shipped /site/"
|
|
14
|
+
* principle, anything not in a shipped surface should fall through
|
|
15
|
+
* to LLM generation rather than be retrieval-matched from a curated
|
|
16
|
+
* JSON. The 199 MODE-C-MAYBE + 9 KEEP-AS-CHUNK + 3 DELETE
|
|
17
|
+
* composition/pattern files all retired alongside the dir.
|
|
8
18
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* source-HTML attrs per §40) AND `template` (from the harvester's
|
|
12
|
-
* transpileHTML pass per §41). Such chunks are normalized to
|
|
13
|
-
* composition shape by hoisting `metadata.*` to top level, so they
|
|
14
|
-
* compete with hand-authored compositions in the same search.
|
|
15
|
-
*
|
|
16
|
-
* Records from chunks carry `_kind: 'annotated-chunk'` and the
|
|
17
|
-
* source-of-truth path so consumers can distinguish if they care; most
|
|
18
|
-
* shouldn't.
|
|
19
|
+
* Records carry `_kind: 'annotated-chunk'` and the source-of-truth
|
|
20
|
+
* path so consumers can distinguish if they care; most shouldn't.
|
|
19
21
|
*
|
|
20
22
|
* Renamed from fragment-library.js in §38. Fragments retired §37.
|
|
21
|
-
* Annotated-chunk loading added §41.
|
|
23
|
+
* Annotated-chunk loading added §41. Compositions-glob retired §65.
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
// §72 follow-up (v0.4.7): dual-mode loader so this module is safe to
|
|
27
|
+
// statically import from browser entrypoints. Previously the top-level
|
|
28
|
+
// `import 'node:fs'` poisoned the browser bundle the moment any
|
|
29
|
+
// app/playground reached `core/reference.js` → composition-library.
|
|
30
|
+
// Pattern mirrors `retrieval/component-catalog.js`.
|
|
31
|
+
const IS_NODE =
|
|
32
|
+
typeof process !== 'undefined' &&
|
|
33
|
+
typeof process.versions?.node === 'string';
|
|
31
34
|
|
|
32
35
|
/** @type {Map<string, object>} */
|
|
33
36
|
const compositions = new Map();
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
// Vite resolves this at build time; at runtime in Node the variable is unused.
|
|
39
|
+
let _globChunkModules = null;
|
|
40
|
+
if (!IS_NODE) {
|
|
41
|
+
try {
|
|
42
|
+
_globChunkModules = import.meta.glob('../../../corpus/chunks/*.json', {
|
|
43
|
+
query: '?raw',
|
|
44
|
+
import: 'default',
|
|
45
|
+
eager: false,
|
|
46
|
+
});
|
|
47
|
+
} catch {
|
|
48
|
+
// Not in a Vite context — no chunk data in this realm.
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -68,57 +76,94 @@ function chunkToComposition(chunkDoc, sourcePath) {
|
|
|
68
76
|
* + re-build, which is wasted work but not incorrect.
|
|
69
77
|
*/
|
|
70
78
|
let _autoLoaded = false;
|
|
79
|
+
let _loadStats = { compositionCount: 0, handAuthoredCount: 0, annotatedChunkCount: 0 };
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Filter + register an annotated chunk. Returns 1 if it qualified
|
|
83
|
+
* (had `metadata` + non-empty `template` + domain/keywords), else 0.
|
|
84
|
+
*/
|
|
85
|
+
function registerChunk(doc, sourcePath) {
|
|
86
|
+
if (!doc?.metadata) return 0;
|
|
87
|
+
if (!doc.template || !Array.isArray(doc.template) || doc.template.length === 0) return 0;
|
|
88
|
+
const meta = doc.metadata;
|
|
89
|
+
if (!meta.domain && !(meta.keywords && meta.keywords.length > 0)) return 0;
|
|
90
|
+
compositions.set(doc.name, chunkToComposition(doc, sourcePath));
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
async function _loadAllNode() {
|
|
95
|
+
const fs = await import(/* @vite-ignore */ 'node:fs');
|
|
96
|
+
const path = await import(/* @vite-ignore */ 'node:path');
|
|
97
|
+
const url = await import(/* @vite-ignore */ 'node:url');
|
|
98
|
+
|
|
99
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
100
|
+
const CHUNKS_ROOT = path.resolve(__dirname, '../../../corpus/chunks');
|
|
101
|
+
|
|
102
|
+
function walk(dir, cb) {
|
|
103
|
+
if (!fs.existsSync(dir)) return;
|
|
104
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
105
|
+
const p = path.join(dir, entry.name);
|
|
106
|
+
if (entry.isDirectory()) walk(p, cb);
|
|
107
|
+
else if (entry.name.endsWith('.json') && !entry.name.startsWith('_')) cb(p);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
82
110
|
|
|
83
|
-
// 2. Annotated chunks (§41). Filter: must have metadata.domain (or
|
|
84
|
-
// metadata.keywords) AND a template field — otherwise the harvester
|
|
85
|
-
// doesn't have enough to treat the chunk as a retrieval candidate.
|
|
86
111
|
let annotatedChunkCount = 0;
|
|
87
112
|
walk(CHUNKS_ROOT, (p) => {
|
|
88
113
|
const doc = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
89
|
-
|
|
90
|
-
if (!doc.template || !Array.isArray(doc.template) || doc.template.length === 0) return;
|
|
91
|
-
const meta = doc.metadata;
|
|
92
|
-
if (!meta.domain && !(meta.keywords && meta.keywords.length > 0)) return;
|
|
93
|
-
|
|
94
|
-
// Chunk name MUST NOT collide with a hand-authored composition.
|
|
95
|
-
// Hand-authored wins; warn so authors can reconcile (rename one or
|
|
96
|
-
// delete the composition once the chunk supersedes it).
|
|
97
|
-
if (compositions.has(doc.name)) {
|
|
98
|
-
console.warn(
|
|
99
|
-
`[composition-library] name collision: annotated chunk "${doc.name}" ` +
|
|
100
|
-
`shadowed by hand-authored composition. Annotated chunk ignored — ` +
|
|
101
|
-
`rename one or retire the composition.`
|
|
102
|
-
);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
compositions.set(doc.name, chunkToComposition(doc, p));
|
|
106
|
-
annotatedChunkCount++;
|
|
114
|
+
annotatedChunkCount += registerChunk(doc, p);
|
|
107
115
|
});
|
|
116
|
+
return annotatedChunkCount;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function _loadAllBrowser() {
|
|
120
|
+
if (!_globChunkModules) return 0;
|
|
121
|
+
let annotatedChunkCount = 0;
|
|
122
|
+
for (const [globPath, loader] of Object.entries(_globChunkModules)) {
|
|
123
|
+
try {
|
|
124
|
+
const raw = await loader();
|
|
125
|
+
const doc = JSON.parse(raw);
|
|
126
|
+
annotatedChunkCount += registerChunk(doc, globPath);
|
|
127
|
+
} catch {
|
|
128
|
+
// skip malformed chunk JSON
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return annotatedChunkCount;
|
|
132
|
+
}
|
|
108
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Load all annotated chunks into the in-memory map. Returns a stats
|
|
136
|
+
* snapshot. In Node, runs synchronously via `fs.readFileSync`; in the
|
|
137
|
+
* browser, dispatches the Vite glob loaders in parallel.
|
|
138
|
+
*/
|
|
139
|
+
export async function loadAll() {
|
|
140
|
+
compositions.clear();
|
|
141
|
+
const annotatedChunkCount = IS_NODE ? await _loadAllNode() : await _loadAllBrowser();
|
|
109
142
|
_autoLoaded = true;
|
|
110
|
-
|
|
143
|
+
_loadStats = {
|
|
111
144
|
compositionCount: compositions.size,
|
|
112
|
-
handAuthoredCount:
|
|
145
|
+
handAuthoredCount: 0,
|
|
113
146
|
annotatedChunkCount,
|
|
114
147
|
};
|
|
148
|
+
return _loadStats;
|
|
115
149
|
}
|
|
116
150
|
|
|
117
151
|
// Eager top-level load so synchronous getters (getComposition,
|
|
118
152
|
// getAllCompositions, searchAll) work for consumers that don't call
|
|
119
153
|
// loadAll themselves — test harnesses, smoke scripts, and the
|
|
120
154
|
// retrieval-side helpers migrated off pattern-library in §64.
|
|
121
|
-
|
|
155
|
+
//
|
|
156
|
+
// In Node we top-level-await the load so the map is populated before
|
|
157
|
+
// any importer reaches the synchronous getters. In the browser the
|
|
158
|
+
// auto-load is fire-and-forget (the map fills as chunk JSONs come back
|
|
159
|
+
// from Vite's glob loaders); callers that need the map populated MUST
|
|
160
|
+
// `await loadAll()` themselves. Documented in §72-follow-up.
|
|
161
|
+
if (IS_NODE && !_autoLoaded) {
|
|
162
|
+
await loadAll();
|
|
163
|
+
} else if (!IS_NODE && !_autoLoaded) {
|
|
164
|
+
// Fire-and-forget; do not block module import on the network round-trips.
|
|
165
|
+
loadAll().catch(() => { /* swallow; browsers that don't need this path won't trip */ });
|
|
166
|
+
}
|
|
122
167
|
|
|
123
168
|
// Back-compat shims removed in §38 — fragment-library.js → composition-library.js
|
|
124
169
|
// rename. The retired `getFragment` / `getAllFragments` exports had zero
|