@adia-ai/a2ui-compose 0.4.4 → 0.4.6
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 +52 -0
- package/core/generator.js +0 -1
- package/core/reference.js +53 -18
- package/package.json +1 -1
- package/strategies/_shared/chunk-loader.js +5 -4
- package/strategies/monolithic/_shared.js +118 -3
- package/strategies/monolithic/generate-instant.js +1 -1
- package/strategies/monolithic/generate-pro.js +10 -2
- package/strategies/zettel/composition-library.js +14 -0
- package/core/pattern-export.js +0 -149
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,58 @@ generator graph.
|
|
|
12
12
|
|
|
13
13
|
_No pending changes._
|
|
14
14
|
|
|
15
|
+
## [0.4.6] - 2026-05-12
|
|
16
|
+
|
|
17
|
+
### Changed — `core/` retirement follow-through (§64, v0.4.6)
|
|
18
|
+
|
|
19
|
+
Companion to `@adia-ai/a2ui-retrieval@0.4.6` retiring `pattern-library.js`:
|
|
20
|
+
|
|
21
|
+
- **`compose/core/pattern-export.js`** retired — no consumers post-§64 (the export shape targeted the retired pattern-library surface).
|
|
22
|
+
- **`compose/core/reference.js`** — updated to delegate to `composition-library` instead of the retired `pattern-library`.
|
|
23
|
+
- **`compose/core/generator.js`** — dropped pattern-export wiring; system-prompt path consumes compositions directly via §66's `adaptV09Component()`.
|
|
24
|
+
|
|
25
|
+
### Changed — `buildSystemPrompt` reads canonical `.a2ui.json` sidecars (§66, v0.4.6)
|
|
26
|
+
|
|
27
|
+
Closes the catalog-drift surface that §56 explicitly deferred ("different schema shapes; restructuring `getComponentCatalog()` is its own arc"). The monolithic prompt builder now reads from the v0.9 `.a2ui.json` sidecars (generated from yamls by `npm run components`) instead of the legacy hand-maintained `_components.json` catalog.
|
|
28
|
+
|
|
29
|
+
**Root cause uncovered during audit:** `strategies/monolithic/_shared.js` (pre-§66) called `getComponentData(typeName)` returning the raw v0.9 sidecar — `{title, description, properties, x-adiaui}` (JSON Schema shape) — but the prompt builder code did `a2uiData.props ? Object.entries(a2uiData.props)`. The v0.9 shape uses `properties`, not `props`. **Every prompt was silently falling through to the legacy `_components.json` catalog** because every v0.9 lookup looked empty. The "canonical sidecar reads" path was wired but dead code.
|
|
30
|
+
|
|
31
|
+
**Fix:** new `adaptV09Component()` helper at the top of `_shared.js` (~90 LOC) normalizes the v0.9 sidecar shape to the legacy `{tag, category, description, props, events, slots, examples, aliases, keywords}` shape the rest of the prompt builder was written for. Properties map 1:1 (skipping `component` discriminator + `children` container + `const`-only fields). Type names map JSON-Schema lowercase → legacy Title-Case (`string`→`String`, `boolean`→`Boolean`, etc.).
|
|
32
|
+
|
|
33
|
+
**Empirical fidelity gain** (Button as canonical primitive):
|
|
34
|
+
|
|
35
|
+
| Source | Props surfaced |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `_components.json` (legacy, before §66) | 7: `text, variant, size, disabled, block, icon, type` (`block` is stale — removed from yaml in an earlier arc but never cleaned up in the hand-maintained catalog) |
|
|
38
|
+
| v0.9 sidecar (canonical, after §66) | 10: `type, aria-label, color, disabled, icon, size, stretch, text, textContent, variant` |
|
|
39
|
+
|
|
40
|
+
**§66 surfaces ~30% more catalog data** for the LLM, all traceable to a yaml SoT that `npm run components` regenerates on demand. This is exactly the catalog drift §56 flagged: when the team added `Select.size` (§50) and `inputmode`/`autocomplete` (§51) to the yamls, those props went into the v0.9 sidecars but never propagated to `_components.json`. The LLM was getting a stale catalog view that excluded recent additions. After §66, the LLM gets the actual ground truth.
|
|
41
|
+
|
|
42
|
+
**Pitfalls encoded in the adapter** (5 v0.9-shape gotchas):
|
|
43
|
+
1. `events` is an object `{eventName: {description}}`, not an array. Normalize to `string[]`.
|
|
44
|
+
2. `slots` is also object-shaped. Same fix.
|
|
45
|
+
3. `synonyms.tags` may be null/undefined. Wrap in `Array.isArray()` guard.
|
|
46
|
+
4. `component` and `children` properties on every v0.9 sidecar are discriminator/container fields, not user-facing props. Skip in the adapter loop.
|
|
47
|
+
5. `const` fields (e.g. `{const: 'Button'}`) are discriminator-only. Skip.
|
|
48
|
+
|
|
49
|
+
Source: `packages/a2ui/compose/strategies/monolithic/_shared.js` (+91, -2 lines, commit `afda98f5`).
|
|
50
|
+
|
|
51
|
+
## [0.4.5] - 2026-05-12
|
|
52
|
+
|
|
53
|
+
### Changed — GenUI overhaul prompt-engineering (§56, v0.4.5)
|
|
54
|
+
|
|
55
|
+
- **`strategies/monolithic/_shared.js` — CORPUS CONTEXT block added to `buildSystemPrompt`** (~30 new lines between role/output-format and CARD-N content model). Surgical insertion explaining the §36-§51 retrieval-augmented pipeline:
|
|
56
|
+
- Pipeline searches PATTERNS / COMPOSITIONS first (full-canvas A2UI templates: login-form, pricing-tiers, dashboard-admin-page)
|
|
57
|
+
- Pipeline also searches ANNOTATED CHUNKS (real production HTML with `metadata.{domain, keywords, description}`)
|
|
58
|
+
- When matched, `MATCHED PATTERN` / `STRUCTURAL REFERENCE` blocks carry the actual reference
|
|
59
|
+
- Only AVAILABLE COMPONENTS are usable — anything else is a hallucination
|
|
60
|
+
|
|
61
|
+
Cost: ~150 tokens per request. Benefit: LLM understands what the pipeline around it IS instead of treating the matched-pattern block as anonymous instructions. Existing rules unchanged; no removed lines.
|
|
62
|
+
|
|
63
|
+
- **`strategies/monolithic/generate-pro.js` — STRUCTURAL REFERENCE prose enriched.** Prior copy: "a real production block from the codebase matched this intent." New copy: "this chunk was retrieved from the AdiaUI training corpus (annotated production HTML, harvested from real app pages). It matched your intent on keyword/domain ranking." Threads chunk `metadata.domain`, `metadata.description`, `metadata.keywords` into the prompt when present. Reframes "do not copy the HTML" as "the chunk represents the SHAPE the user wants; instantiate it with their content" — more actionable framing.
|
|
64
|
+
|
|
65
|
+
See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 overhaul arc + apps/genui/CHANGELOG.md `[Unreleased]` for the per-§ rollup.
|
|
66
|
+
|
|
15
67
|
## [0.4.4] - 2026-05-12
|
|
16
68
|
|
|
17
69
|
### Changed
|
package/core/generator.js
CHANGED
|
@@ -17,7 +17,6 @@ import { store, engine } from './state.js';
|
|
|
17
17
|
import { checkIntentAlignment } from '../../retrieval/intent/intent-alignment.js';
|
|
18
18
|
import { decomposeIntent, composeSubtasks } from '../../retrieval/intent/decomposer.js';
|
|
19
19
|
import { getWiringCatalog } from '../../retrieval/wiring-catalog.js';
|
|
20
|
-
import { getComponentData } from '../../retrieval/pattern-library.js';
|
|
21
20
|
|
|
22
21
|
import { StubLLMAdapter } from '../../../llm/llm-stub.js';
|
|
23
22
|
import { createAdapter } from '../../../llm/llm-bridge.js';
|
package/core/reference.js
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reference Tools —
|
|
2
|
+
* Reference Tools — stable generative-system API surface.
|
|
3
3
|
*
|
|
4
|
-
* Thin wrappers
|
|
5
|
-
*
|
|
6
|
-
* to
|
|
4
|
+
* Thin wrappers used by the monolithic engines + generator pipeline.
|
|
5
|
+
* §64 migrated the "pattern" surface from `pattern-library.js` (retired)
|
|
6
|
+
* to the zettel `composition-library.js`. Function names kept
|
|
7
|
+
* (searchBlocks/lookupPattern/listPatterns/searchBlocksSemantic) for
|
|
8
|
+
* back-compat with engine call sites; internally they hit compositions.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { getCatalog, getComponent, getComponentsByCategory } from '../../retrieval/index.js';
|
|
10
12
|
import { classifyIntent, assembleContext } from '../../retrieval/index.js';
|
|
11
|
-
import { getAllPatterns, searchPatterns, getPattern, semanticSearchPatterns } from '../../retrieval/index.js';
|
|
12
13
|
import { checkAllAntiPatterns } from '../../retrieval/index.js';
|
|
13
14
|
import { serializeEntry } from '../../retrieval/index.js';
|
|
15
|
+
import {
|
|
16
|
+
searchAll as searchCompositions,
|
|
17
|
+
getComposition,
|
|
18
|
+
getAllCompositions,
|
|
19
|
+
} from '../strategies/zettel/composition-library.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compositions have `tags: { purpose: [...], complexity: "", layout: "" }`
|
|
23
|
+
* but the monolithic engines (instant/pro/thinking) all assume the old
|
|
24
|
+
* pattern shape `tags: [string]` and call `.map(...)` on it. Flatten on
|
|
25
|
+
* the way out so legacy call sites keep working without re-templating
|
|
26
|
+
* every engine. Annotated chunks expose flat-but-shallow `tags`; both
|
|
27
|
+
* are handled.
|
|
28
|
+
*/
|
|
29
|
+
function normalize(record) {
|
|
30
|
+
if (!record) return record;
|
|
31
|
+
let tags = record.tags;
|
|
32
|
+
if (Array.isArray(tags)) return record;
|
|
33
|
+
if (tags && typeof tags === 'object') {
|
|
34
|
+
tags = Object.values(tags)
|
|
35
|
+
.flat()
|
|
36
|
+
.filter((v) => typeof v === 'string');
|
|
37
|
+
} else {
|
|
38
|
+
tags = [];
|
|
39
|
+
}
|
|
40
|
+
return { ...record, tags };
|
|
41
|
+
}
|
|
14
42
|
|
|
15
43
|
/**
|
|
16
44
|
* Look up a single component by A2UI type name.
|
|
@@ -50,40 +78,47 @@ export async function getComponentsByGroup(category) {
|
|
|
50
78
|
}
|
|
51
79
|
|
|
52
80
|
/**
|
|
53
|
-
* Search the
|
|
81
|
+
* Search the composition library by query.
|
|
82
|
+
* Returns full composition records (rehydrated from name hits).
|
|
54
83
|
* @param {string} query — Natural language or keyword query
|
|
55
84
|
* @returns {object[]}
|
|
56
85
|
*/
|
|
57
86
|
export function searchBlocks(query, { domain } = {}) {
|
|
58
|
-
|
|
87
|
+
const hits = searchCompositions(query);
|
|
88
|
+
const records = hits.map((h) => getComposition(h.name)).filter(Boolean);
|
|
89
|
+
const filtered = domain ? records.filter((c) => c.domain === domain) : records;
|
|
90
|
+
return filtered.map(normalize);
|
|
59
91
|
}
|
|
60
92
|
|
|
61
93
|
/**
|
|
62
|
-
* Get a specific named
|
|
63
|
-
* @param {string} name —
|
|
94
|
+
* Get a specific named composition.
|
|
95
|
+
* @param {string} name — Composition name
|
|
64
96
|
* @returns {object|null}
|
|
65
97
|
*/
|
|
66
98
|
export function lookupPattern(name) {
|
|
67
|
-
return
|
|
99
|
+
return normalize(getComposition(name));
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
/**
|
|
71
|
-
* Get all available
|
|
103
|
+
* Get all available compositions.
|
|
72
104
|
* @returns {object[]}
|
|
73
105
|
*/
|
|
74
106
|
export function listPatterns() {
|
|
75
|
-
return
|
|
107
|
+
return getAllCompositions().map(normalize);
|
|
76
108
|
}
|
|
77
109
|
|
|
78
110
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
111
|
+
* Keyword search wrapped as the legacy `{ matches }` return shape so the
|
|
112
|
+
* monolithic-thinking engine's call site keeps working. The historical
|
|
113
|
+
* LLM-driven semantic path retired with `pattern-library.js` in §64;
|
|
114
|
+
* compositions don't have a semantic search backend yet.
|
|
115
|
+
*
|
|
81
116
|
* @param {string} query
|
|
82
|
-
* @param {object} [
|
|
83
|
-
* @returns {Promise<{ matches: object[]
|
|
117
|
+
* @param {object} [_options] — accepted for back-compat; llmAdapter/remix ignored
|
|
118
|
+
* @returns {Promise<{ matches: object[] }>}
|
|
84
119
|
*/
|
|
85
|
-
export async function searchBlocksSemantic(query,
|
|
86
|
-
return
|
|
120
|
+
export async function searchBlocksSemantic(query, _options = {}) {
|
|
121
|
+
return { matches: searchBlocks(query) };
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
/**
|
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.6",
|
|
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": {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dual-environment chunk loader — Node + browser.
|
|
3
3
|
*
|
|
4
|
-
* Mirrors the
|
|
5
|
-
* gen-UI training chunks at
|
|
6
|
-
* from BOTH server-side
|
|
7
|
-
*
|
|
4
|
+
* Mirrors the dual-environment loading approach historically used by
|
|
5
|
+
* pattern-library.js (retired §64) so the gen-UI training chunks at
|
|
6
|
+
* packages/a2ui/corpus/chunks/*.json are reachable from BOTH server-side
|
|
7
|
+
* engines (monolithic-pro via /api/generate's Node process) AND
|
|
8
|
+
* browser-side engines (monolithic-pro called directly from
|
|
8
9
|
* gen-ui playground's IIFE).
|
|
9
10
|
*
|
|
10
11
|
* The existing packages/a2ui/corpus/scripts/chunk-library.js is Node-only
|
|
@@ -12,7 +12,91 @@ import { store } from '../../core/state.js';
|
|
|
12
12
|
import { checkIntentAlignment } from '../../../retrieval/intent/intent-alignment.js';
|
|
13
13
|
import { composeSubtasks } from '../../../retrieval/intent/decomposer.js';
|
|
14
14
|
import { getWiringCatalog } from '../../../retrieval/wiring-catalog.js';
|
|
15
|
-
import { getComponentData } from '../../../retrieval/
|
|
15
|
+
import { getComponentData } from '../../../retrieval/component-catalog.js';
|
|
16
|
+
|
|
17
|
+
// ── v0.9 .a2ui.json sidecar → prompt-catalog adapter ─────────────
|
|
18
|
+
//
|
|
19
|
+
// §66 (v0.4.6): the `.a2ui.json` sidecars produced by `npm run components`
|
|
20
|
+
// are v0.9 JSON Schemas with `{title, description, properties, x-adiaui}`.
|
|
21
|
+
// The legacy `_components.json` catalog this prompt builder originally
|
|
22
|
+
// read had a flatter `{tag, category, description, props, events, slots,
|
|
23
|
+
// examples, aliases, keywords}` shape. This adapter normalizes the v0.9
|
|
24
|
+
// sidecar into the legacy shape so the rest of the prompt builder can stay
|
|
25
|
+
// unchanged.
|
|
26
|
+
//
|
|
27
|
+
// Before §66, this code did `a2uiData.props` — which silently returned 0
|
|
28
|
+
// props for every component (the v0.9 shape uses `properties`, not
|
|
29
|
+
// `props`), causing every prompt to fall through to the `_components.json`
|
|
30
|
+
// catalog. After §66, the canonical sidecar drives the prompt directly
|
|
31
|
+
// and `_components.json` is retired (§64/§65).
|
|
32
|
+
function adaptV09Component(a2uiData) {
|
|
33
|
+
if (!a2uiData || typeof a2uiData !== 'object') return null;
|
|
34
|
+
const ext = a2uiData['x-adiaui'] || {};
|
|
35
|
+
const properties = a2uiData.properties || {};
|
|
36
|
+
|
|
37
|
+
// Map v0.9 property → legacy prop shape. Skip the always-present
|
|
38
|
+
// `component` (const PascalCase name) + `children` (slot container)
|
|
39
|
+
// fields since they aren't user-facing props.
|
|
40
|
+
const props = {};
|
|
41
|
+
for (const [name, spec] of Object.entries(properties)) {
|
|
42
|
+
if (name === 'component' || name === 'children') continue;
|
|
43
|
+
if (spec?.const) continue; // discriminator-only fields
|
|
44
|
+
const out = {};
|
|
45
|
+
// JSON Schema type names are lowercase; legacy catalog used Title-Case.
|
|
46
|
+
if (spec.type) {
|
|
47
|
+
out.type = spec.type === 'string' ? 'String'
|
|
48
|
+
: spec.type === 'boolean' ? 'Boolean'
|
|
49
|
+
: spec.type === 'number' || spec.type === 'integer' ? 'Number'
|
|
50
|
+
: spec.type === 'array' ? 'Array'
|
|
51
|
+
: spec.type === 'object' ? 'Object'
|
|
52
|
+
: String(spec.type);
|
|
53
|
+
}
|
|
54
|
+
if (spec.enum) out.enum = spec.enum;
|
|
55
|
+
if (spec.default !== undefined) out.default = spec.default;
|
|
56
|
+
if (spec.description) out.description = spec.description;
|
|
57
|
+
props[name] = out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Events in the legacy catalog were a flat string[]. The v0.9 sidecar
|
|
61
|
+
// stores them as an object: `{eventName: {description}}` (verified
|
|
62
|
+
// across all 94 sidecars on 2026-05-12). Older code paths accepted
|
|
63
|
+
// either shape; normalize to a string[] of event names.
|
|
64
|
+
const eventsRaw = ext.events;
|
|
65
|
+
let events = [];
|
|
66
|
+
if (Array.isArray(eventsRaw)) {
|
|
67
|
+
events = eventsRaw.map((e) => (typeof e === 'string' ? e : e?.name)).filter(Boolean);
|
|
68
|
+
} else if (eventsRaw && typeof eventsRaw === 'object') {
|
|
69
|
+
events = Object.keys(eventsRaw);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Slots — also object-shaped (`{slotName: {description, type}}`) in v0.9.
|
|
73
|
+
// Legacy catalog stored a string[]. Normalize.
|
|
74
|
+
const slotsRaw = ext.slots;
|
|
75
|
+
let slots = [];
|
|
76
|
+
if (Array.isArray(slotsRaw)) {
|
|
77
|
+
slots = slotsRaw.map((s) => (typeof s === 'string' ? s : s?.name)).filter(Boolean);
|
|
78
|
+
} else if (slotsRaw && typeof slotsRaw === 'object') {
|
|
79
|
+
slots = Object.keys(slotsRaw);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Aliases live under `x-adiaui.synonyms.tags` in v0.9. The synonyms
|
|
83
|
+
// field is always an object (verified across all 94 sidecars). Surface
|
|
84
|
+
// the `tags` array — the prompt renders them as "Button (also: SubmitButton)".
|
|
85
|
+
const synonyms = (ext.synonyms && typeof ext.synonyms === 'object') ? ext.synonyms : null;
|
|
86
|
+
const aliases = Array.isArray(synonyms?.tags) ? synonyms.tags : null;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
tag: ext.tag || null,
|
|
90
|
+
category: ext.category || 'layout',
|
|
91
|
+
description: a2uiData.description || '',
|
|
92
|
+
props,
|
|
93
|
+
events,
|
|
94
|
+
slots,
|
|
95
|
+
examples: Array.isArray(ext.examples) ? ext.examples : [],
|
|
96
|
+
aliases,
|
|
97
|
+
keywords: Array.isArray(ext.keywords) ? ext.keywords : [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
16
100
|
|
|
17
101
|
// Component prop catalog — loaded lazily for prompt injection
|
|
18
102
|
let _componentCatalog = null;
|
|
@@ -51,6 +135,32 @@ Output format: [{ "type": "updateComponents", "surfaceId": "default", "component
|
|
|
51
135
|
Each component: { "id": "<unique>", "component": "<Type>", "children": ["<childId>", ...], ...props }
|
|
52
136
|
The root must have id "root". Use short, descriptive IDs (e.g., "hdr", "email-field", "submit-btn").`);
|
|
53
137
|
|
|
138
|
+
// ── Corpus context (§56, v0.4.5) ──
|
|
139
|
+
// Tells the LLM how the system surrounding it is wired so it doesn't
|
|
140
|
+
// hallucinate component names and so it understands what MATCHED PATTERN /
|
|
141
|
+
// STRUCTURAL REFERENCE blocks actually ARE. Keep this section tight — every
|
|
142
|
+
// token costs latency + money.
|
|
143
|
+
parts.push(`CORPUS CONTEXT (the system around you):
|
|
144
|
+
|
|
145
|
+
You are part of a retrieval-augmented pipeline. Before this prompt was built,
|
|
146
|
+
the pipeline searched two corpora for matches to the user's intent:
|
|
147
|
+
|
|
148
|
+
1. PATTERNS / COMPOSITIONS — full-canvas A2UI templates curated for the
|
|
149
|
+
AdiaUI design system. Examples: login-form, pricing-tiers,
|
|
150
|
+
dashboard-admin-page. When a strong match exists, you'll see a
|
|
151
|
+
"MATCHED PATTERN" block below with the canonical template.
|
|
152
|
+
|
|
153
|
+
2. ANNOTATED CHUNKS — real production HTML blocks harvested from the AdiaUI
|
|
154
|
+
apps (auth flows, dashboards, error pages, etc.) with metadata
|
|
155
|
+
(domain, keywords, description). When a strong chunk match exists,
|
|
156
|
+
you'll see a "STRUCTURAL REFERENCE" block with the production HTML.
|
|
157
|
+
Match its component palette + information density, but translate to
|
|
158
|
+
A2UI components — do not copy raw HTML.
|
|
159
|
+
|
|
160
|
+
Both corpora use ONLY components from the AVAILABLE COMPONENTS list further
|
|
161
|
+
down. If you would invent a component name not in that list, you're
|
|
162
|
+
hallucinating — use the closest canonical name instead.`);
|
|
163
|
+
|
|
54
164
|
// ── Card-N content model (critical for quality) ──
|
|
55
165
|
parts.push(`CARD-N CONTENT MODEL (mandatory for any card surface):
|
|
56
166
|
- Card > Header: for title/description. Use Text children with heading variants (h3, h4).
|
|
@@ -134,8 +244,13 @@ KEY RULES:
|
|
|
134
244
|
return Array.isArray(a) && a.length ? ` (also: ${a.join(', ')})` : '';
|
|
135
245
|
};
|
|
136
246
|
for (const typeName of relevantTypes) {
|
|
137
|
-
//
|
|
138
|
-
|
|
247
|
+
// §66 (v0.4.6): .a2ui.json sidecar is the canonical source of truth.
|
|
248
|
+
// The raw v0.9 shape uses `properties` (JSON Schema); adapt to the
|
|
249
|
+
// legacy `props` shape this prompt builder was originally written
|
|
250
|
+
// for. Falls back to the (soon-retired) `_components.json` only
|
|
251
|
+
// when no sidecar exists.
|
|
252
|
+
const v09Raw = getComponentData(typeName);
|
|
253
|
+
const a2uiData = v09Raw ? adaptV09Component(v09Raw) : null;
|
|
139
254
|
if (a2uiData) {
|
|
140
255
|
const props = a2uiData.props ? Object.entries(a2uiData.props).map(([k, v]) => {
|
|
141
256
|
let desc = k;
|
|
@@ -163,7 +163,7 @@ export async function generateInstant({ intent, executionId, storeId, analysis,
|
|
|
163
163
|
|
|
164
164
|
engine.submitStage(execId, 'generate', {
|
|
165
165
|
messages,
|
|
166
|
-
source: bestMatch ? '
|
|
166
|
+
source: bestMatch ? 'composition-library' : 'fallback',
|
|
167
167
|
confidence: bestMatch ? 0.95 : 0.4,
|
|
168
168
|
});
|
|
169
169
|
|
|
@@ -221,10 +221,18 @@ export async function generatePro({ intent, executionId, storeId, llmAdapter, an
|
|
|
221
221
|
// structural "look like this" hint that lets the LLM materialize the
|
|
222
222
|
// real chunk shape (e.g. auth-signin-card-email) instead of
|
|
223
223
|
// hallucinating a passable-but-wrong shape from the brief alone.
|
|
224
|
+
//
|
|
225
|
+
// §56 (v0.4.5): prompt language enriched with provenance + metadata so
|
|
226
|
+
// the LLM understands the chunk's domain/keywords context. Tested via
|
|
227
|
+
// factory-chat + gen-ui live submit; LLMs produce closer structural
|
|
228
|
+
// matches when told what the chunk is + where it came from.
|
|
224
229
|
const chunkReferenceBlock = chunkRefHtml
|
|
225
|
-
? `\nSTRUCTURAL REFERENCE —
|
|
230
|
+
? `\nSTRUCTURAL REFERENCE — this chunk was retrieved from the AdiaUI training corpus (annotated production HTML, harvested from real app pages). It matched your intent on keyword/domain ranking:
|
|
231
|
+
|
|
232
|
+
CHUNK: ${chunkMatch.name}
|
|
233
|
+
kind=${chunkMatch.kind}, primary=${chunkMatch.primary}, score=${chunkMatch.score}${chunkMatch.metadata?.domain ? `, domain=${chunkMatch.metadata.domain}` : ''}${chunkMatch.metadata?.description ? `\n description: ${chunkMatch.metadata.description}` : ''}${chunkMatch.metadata?.keywords ? `\n keywords: ${chunkMatch.metadata.keywords.join(', ')}` : ''}
|
|
226
234
|
|
|
227
|
-
|
|
235
|
+
Match this chunk's component palette, information density, and Card/Header/Section/Footer anatomy. Do NOT copy the HTML verbatim into A2UI — translate its semantic structure into the equivalent A2UI components. The chunk represents the SHAPE the user wants; your job is to instantiate that shape with their content:
|
|
228
236
|
---
|
|
229
237
|
${chunkRefHtml}
|
|
230
238
|
---
|
|
@@ -62,6 +62,13 @@ function chunkToComposition(chunkDoc, sourcePath) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Track whether the eager top-level load has run. Multiple `loadAll`
|
|
67
|
+
* call sites (mcp/server.js bootstrap + any other consumer) re-clear
|
|
68
|
+
* + re-build, which is wasted work but not incorrect.
|
|
69
|
+
*/
|
|
70
|
+
let _autoLoaded = false;
|
|
71
|
+
|
|
65
72
|
export function loadAll() {
|
|
66
73
|
compositions.clear();
|
|
67
74
|
|
|
@@ -99,6 +106,7 @@ export function loadAll() {
|
|
|
99
106
|
annotatedChunkCount++;
|
|
100
107
|
});
|
|
101
108
|
|
|
109
|
+
_autoLoaded = true;
|
|
102
110
|
return {
|
|
103
111
|
compositionCount: compositions.size,
|
|
104
112
|
handAuthoredCount: compositions.size - annotatedChunkCount,
|
|
@@ -106,6 +114,12 @@ export function loadAll() {
|
|
|
106
114
|
};
|
|
107
115
|
}
|
|
108
116
|
|
|
117
|
+
// Eager top-level load so synchronous getters (getComposition,
|
|
118
|
+
// getAllCompositions, searchAll) work for consumers that don't call
|
|
119
|
+
// loadAll themselves — test harnesses, smoke scripts, and the
|
|
120
|
+
// retrieval-side helpers migrated off pattern-library in §64.
|
|
121
|
+
if (!_autoLoaded) loadAll();
|
|
122
|
+
|
|
109
123
|
// Back-compat shims removed in §38 — fragment-library.js → composition-library.js
|
|
110
124
|
// rename. The retired `getFragment` / `getAllFragments` exports had zero
|
|
111
125
|
// external callers; drop them.
|
package/core/pattern-export.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Export — Save generated UI as reusable named patterns.
|
|
3
|
-
*
|
|
4
|
-
* Produces two downloadable files:
|
|
5
|
-
* {name}.json — A2UI flat adjacency pattern (importable into pattern-library)
|
|
6
|
-
* {name}.html — Rendered HTML snapshot from the canvas
|
|
7
|
-
*
|
|
8
|
-
* Also supports importing patterns back into the runtime library.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { registerPattern } from '../../retrieval/pattern-library.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Build a pattern-library-compatible JSON object from generation results.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} name — Pattern name (kebab-case, e.g. "my-pricing-page")
|
|
17
|
-
* @param {object} opts
|
|
18
|
-
* @param {object[]} opts.components — Flat adjacency array from messages[0].components
|
|
19
|
-
* @param {string} opts.intent — Original user intent
|
|
20
|
-
* @param {string} [opts.domain] — Domain classification (forms, data, layout, agent, navigation)
|
|
21
|
-
* @param {string} [opts.description] — Human description (falls back to intent)
|
|
22
|
-
* @returns {object} — Pattern object matching pattern-library format
|
|
23
|
-
*/
|
|
24
|
-
export function buildPatternJSON(name, { components, intent, domain, description }) {
|
|
25
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
26
|
-
|
|
27
|
-
// Extract unique component type names
|
|
28
|
-
const componentTypes = [...new Set(
|
|
29
|
-
components.map(c => c.component).filter(Boolean)
|
|
30
|
-
)];
|
|
31
|
-
|
|
32
|
-
// Clean components: strip internal fields
|
|
33
|
-
const template = components.map(c => {
|
|
34
|
-
const { _surfaceId, ...rest } = c;
|
|
35
|
-
return rest;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
name: slug,
|
|
40
|
-
description: description || intent || slug,
|
|
41
|
-
domain: domain || 'layout',
|
|
42
|
-
components: componentTypes,
|
|
43
|
-
template,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Save a generation as a named pattern. Downloads .json + .html files.
|
|
49
|
-
*
|
|
50
|
-
* @param {string} name — Pattern name
|
|
51
|
-
* @param {object} opts
|
|
52
|
-
* @param {object[]} opts.messages — A2UI messages array
|
|
53
|
-
* @param {object} opts.validation — Validation result (must have score >= 95)
|
|
54
|
-
* @param {string} opts.intent — Original user intent
|
|
55
|
-
* @param {string} [opts.domain] — Domain classification
|
|
56
|
-
* @param {string} [opts.canvasHTML] — Rendered HTML from canvas
|
|
57
|
-
* @param {number} [opts.minScore=95] — Minimum score to allow save
|
|
58
|
-
* @returns {{ json: object, html: string }} — The saved payloads
|
|
59
|
-
*/
|
|
60
|
-
export function savePattern(name, { messages, validation, intent, domain, canvasHTML, minScore = 95 }) {
|
|
61
|
-
if (!validation || validation.score < minScore) {
|
|
62
|
-
throw new Error(`Score ${validation?.score ?? 0} is below minimum ${minScore} for pattern save`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const components = messages?.[0]?.components;
|
|
66
|
-
if (!components || !components.length) {
|
|
67
|
-
throw new Error('No components to save');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const json = buildPatternJSON(name, { components, intent, domain });
|
|
71
|
-
|
|
72
|
-
// Download .json
|
|
73
|
-
downloadFile(`${json.name}.json`, JSON.stringify(json, null, 2), 'application/json');
|
|
74
|
-
|
|
75
|
-
// Download .html (if available)
|
|
76
|
-
const html = canvasHTML || '';
|
|
77
|
-
if (html) {
|
|
78
|
-
const htmlDoc = `<!DOCTYPE html>
|
|
79
|
-
<html lang="en">
|
|
80
|
-
<head>
|
|
81
|
-
<meta charset="UTF-8">
|
|
82
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
83
|
-
<title>${json.name} — A2UI Pattern</title>
|
|
84
|
-
<style>body { font-family: system-ui, sans-serif; padding: 2rem; }</style>
|
|
85
|
-
</head>
|
|
86
|
-
<body>
|
|
87
|
-
${html}
|
|
88
|
-
</body>
|
|
89
|
-
</html>`;
|
|
90
|
-
downloadFile(`${json.name}.html`, htmlDoc, 'text/html');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Also register in the runtime library
|
|
94
|
-
registerPattern(json);
|
|
95
|
-
|
|
96
|
-
return { json, html };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Import a pattern JSON object into the runtime pattern library.
|
|
101
|
-
*
|
|
102
|
-
* @param {object|string} patternJSON — Pattern object or JSON string
|
|
103
|
-
* @returns {{ success: boolean, name?: string, error?: string }}
|
|
104
|
-
*/
|
|
105
|
-
export function importPattern(patternJSON) {
|
|
106
|
-
let pattern = patternJSON;
|
|
107
|
-
if (typeof pattern === 'string') {
|
|
108
|
-
try { pattern = JSON.parse(pattern); }
|
|
109
|
-
catch { return { success: false, error: 'Invalid JSON' }; }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!pattern?.name) return { success: false, error: 'Missing pattern name' };
|
|
113
|
-
if (!pattern?.template || !Array.isArray(pattern.template)) {
|
|
114
|
-
return { success: false, error: 'Missing or invalid template array' };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const registered = registerPattern(pattern);
|
|
118
|
-
if (!registered) {
|
|
119
|
-
return { success: false, error: `Pattern "${pattern.name}" already exists` };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return { success: true, name: pattern.name };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Trigger a file download in the browser.
|
|
127
|
-
*
|
|
128
|
-
* @param {string} filename
|
|
129
|
-
* @param {string} content
|
|
130
|
-
* @param {string} [mimeType='application/json']
|
|
131
|
-
*/
|
|
132
|
-
export function downloadFile(filename, content, mimeType = 'application/json') {
|
|
133
|
-
if (typeof document === 'undefined') {
|
|
134
|
-
throw new Error('downloadFile requires a browser environment');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const blob = new Blob([content], { type: mimeType });
|
|
138
|
-
const url = URL.createObjectURL(blob);
|
|
139
|
-
const a = document.createElement('a');
|
|
140
|
-
a.href = url;
|
|
141
|
-
a.download = filename;
|
|
142
|
-
a.style.display = 'none';
|
|
143
|
-
// Prevent SPA router from intercepting the blob URL click
|
|
144
|
-
a.addEventListener('click', (e) => e.stopPropagation());
|
|
145
|
-
document.body.appendChild(a);
|
|
146
|
-
a.click();
|
|
147
|
-
document.body.removeChild(a);
|
|
148
|
-
URL.revokeObjectURL(url);
|
|
149
|
-
}
|