@adia-ai/web-components 0.0.1 → 0.0.3
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/a2ui/index.js +23 -17
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.a2ui.json +14 -8
- package/components/agent-reasoning/agent-reasoning.css +1 -1
- package/components/agent-reasoning/agent-reasoning.js +9 -9
- package/components/agent-reasoning/agent-reasoning.yaml +9 -6
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/agent-trace/agent-trace.js +1 -1
- package/components/alert/alert.js +1 -1
- package/components/avatar/avatar.a2ui.json +0 -10
- package/components/avatar/avatar.css +1 -1
- package/components/avatar/avatar.js +1 -1
- package/components/avatar/avatar.yaml +0 -8
- package/components/badge/badge.a2ui.json +6 -6
- package/components/badge/badge.js +22 -1
- package/components/badge/badge.yaml +8 -5
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.a2ui.json +0 -11
- package/components/button/button.css +1 -1
- package/components/button/button.js +3 -3
- package/components/button/button.yaml +0 -11
- package/components/calendar-picker/calendar-picker.js +25 -19
- package/components/canvas/canvas.js +3 -3
- package/components/card/card.a2ui.json +0 -21
- package/components/card/card.js +2 -2
- package/components/card/card.yaml +0 -14
- package/components/chart/chart.a2ui.json +9 -8
- package/components/chart/chart.js +1 -1
- package/components/chart/chart.yaml +7 -5
- package/components/chat/chat-input.js +1 -1
- package/components/chat/chat.js +2 -2
- package/components/check/check.css +1 -1
- package/components/check/check.js +2 -2
- package/components/code/code.a2ui.json +0 -5
- package/components/code/code.js +1 -1
- package/components/code/code.yaml +0 -4
- package/components/col/col.a2ui.json +0 -20
- package/components/col/col.js +1 -1
- package/components/col/col.yaml +0 -20
- package/components/color-picker/color-picker.a2ui.json +1 -1
- package/components/color-picker/color-picker.js +5 -2
- package/components/color-picker/color-picker.yaml +1 -1
- package/components/command/command.js +22 -11
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.a2ui.json +0 -19
- package/components/divider/divider.js +1 -1
- package/components/divider/divider.yaml +0 -12
- package/components/drawer/drawer.a2ui.json +2 -2
- package/components/drawer/drawer.css +1 -1
- package/components/drawer/drawer.js +2 -2
- package/components/drawer/drawer.yaml +3 -3
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.a2ui.json +0 -13
- package/components/empty-state/empty-state.js +1 -1
- package/components/empty-state/empty-state.yaml +0 -5
- package/components/footer/footer.a2ui.json +1 -1
- package/components/footer/footer.yaml +1 -1
- package/components/grid/grid.a2ui.json +0 -5
- package/components/grid/grid.js +1 -1
- package/components/grid/grid.yaml +0 -4
- package/components/header/header.a2ui.json +1 -1
- package/components/header/header.yaml +1 -1
- package/components/heatmap/heatmap.a2ui.json +4 -4
- package/components/heatmap/heatmap.js +4 -4
- package/components/heatmap/heatmap.yaml +4 -4
- package/components/icon/icon.a2ui.json +13 -0
- package/components/icon/icon.js +2 -2
- package/components/icon/icon.yaml +6 -0
- package/components/image/image.a2ui.json +10 -21
- package/components/image/image.js +1 -1
- package/components/image/image.yaml +8 -16
- package/components/input/input.a2ui.json +0 -15
- package/components/input/input.js +2 -2
- package/components/input/input.yaml +0 -12
- package/components/inspector/inspector.a2ui.json +0 -5
- package/components/inspector/inspector.js +2 -2
- package/components/inspector/inspector.yaml +0 -4
- package/components/kbd/kbd.js +1 -1
- package/components/list/list.a2ui.json +0 -33
- package/components/list/list.js +1 -1
- package/components/list/list.yaml +0 -27
- package/components/menu/menu.a2ui.json +0 -10
- package/components/menu/menu.js +9 -3
- package/components/menu/menu.yaml +0 -8
- package/components/modal/modal.a2ui.json +1 -25
- package/components/modal/modal.js +1 -1
- package/components/modal/modal.yaml +1 -22
- package/components/noodles/noodles.a2ui.json +0 -5
- package/components/noodles/noodles.js +1 -1
- package/components/noodles/noodles.yaml +0 -5
- package/components/otp-input/otp-input.a2ui.json +0 -5
- package/components/otp-input/otp-input.js +1 -1
- package/components/otp-input/otp-input.yaml +0 -4
- package/components/pagination/pagination.a2ui.json +3 -13
- package/components/pagination/pagination.css +1 -1
- package/components/pagination/pagination.js +1 -1
- package/components/pagination/pagination.yaml +5 -14
- package/components/pane/pane.a2ui.json +0 -10
- package/components/pane/pane.js +1 -1
- package/components/pane/pane.yaml +0 -9
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.a2ui.json +0 -14
- package/components/popover/popover.js +9 -3
- package/components/popover/popover.yaml +0 -11
- package/components/progress/progress.a2ui.json +2 -12
- package/components/progress/progress.js +1 -1
- package/components/progress/progress.yaml +2 -11
- package/components/progress-row/progress-row.a2ui.json +3 -13
- package/components/progress-row/progress-row.js +1 -1
- package/components/progress-row/progress-row.yaml +3 -11
- package/components/radio/radio.css +1 -1
- package/components/radio/radio.js +2 -2
- package/components/range/range.js +1 -1
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.js +2 -2
- package/components/row/row.a2ui.json +0 -5
- package/components/row/row.js +2 -2
- package/components/row/row.yaml +0 -4
- package/components/search/search.a2ui.json +0 -5
- package/components/search/search.js +1 -1
- package/components/search/search.yaml +0 -4
- package/components/section/section.a2ui.json +1 -1
- package/components/section/section.yaml +1 -1
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.css +1 -1
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.a2ui.json +5 -0
- package/components/select/select.js +13 -3
- package/components/select/select.yaml +5 -0
- package/components/skeleton/skeleton.a2ui.json +0 -11
- package/components/skeleton/skeleton.js +1 -1
- package/components/skeleton/skeleton.yaml +0 -5
- package/components/slider/slider.a2ui.json +0 -5
- package/components/slider/slider.js +1 -1
- package/components/slider/slider.yaml +0 -5
- package/components/stack/stack.js +1 -1
- package/components/stat/stat.js +1 -1
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.js +1 -1
- package/components/swiper/swiper.a2ui.json +7 -7
- package/components/swiper/swiper.js +2 -2
- package/components/swiper/swiper.yaml +8 -6
- package/components/switch/switch.css +1 -1
- package/components/switch/switch.js +2 -2
- package/components/table/table.js +1 -1
- package/components/tabs/tab.js +1 -1
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.a2ui.json +0 -5
- package/components/tag/tag.js +1 -1
- package/components/tag/tag.yaml +0 -4
- package/components/text/text.a2ui.json +5 -0
- package/components/text/text.css +2 -2
- package/components/text/text.js +1 -1
- package/components/text/text.yaml +5 -0
- package/components/textarea/textarea.js +1 -1
- package/components/timeline/timeline.a2ui.json +8 -13
- package/components/timeline/timeline.js +1 -1
- package/components/timeline/timeline.yaml +6 -11
- package/components/toast/toast.a2ui.json +0 -15
- package/components/toast/toast.js +1 -1
- package/components/toast/toast.yaml +0 -13
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toolbar/toolbar.a2ui.json +0 -23
- package/components/toolbar/toolbar.js +13 -3
- package/components/toolbar/toolbar.yaml +0 -19
- package/components/tooltip/tooltip.a2ui.json +0 -10
- package/components/tooltip/tooltip.js +2 -2
- package/components/tooltip/tooltip.yaml +0 -8
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.js +1 -1
- package/core/markdown.js +1 -1
- package/core/provider.js +2 -2
- package/package.json +7 -3
- package/patterns/a2ui-root/a2ui-root.a2ui.json +118 -0
- package/{a2ui/root.js → patterns/a2ui-root/a2ui-root.js} +9 -4
- package/patterns/a2ui-root/a2ui-root.yaml +76 -0
- package/patterns/adia-chat/adia-chat.js +3 -3
- package/patterns/adia-chat/css/adia-chat.streaming.css +2 -2
- package/patterns/adia-editor/adia-editor.js +1 -1
- package/patterns/app-nav/app-nav.js +1 -1
- package/patterns/app-nav-group/app-nav-group.js +2 -2
- package/patterns/app-nav-item/app-nav-item.js +1 -1
- package/patterns/app-shell/app-shell.js +1 -1
- package/patterns/gen-ui/gen-ui.css +3 -3
- package/patterns/gen-ui/gen-ui.js +1 -1
- package/patterns/index.js +1 -0
- package/patterns/section-nav/section-nav.js +1 -1
- package/patterns/section-nav-group/section-nav-group.js +1 -1
- package/patterns/section-nav-item/section-nav-item.js +1 -1
- package/traits/define.js +1 -1
- package/a2ui/dockables/action.js +0 -152
- package/a2ui/dockables/base.js +0 -30
- package/a2ui/dockables/controller.js +0 -97
- package/a2ui/dockables/data-source.js +0 -103
- package/a2ui/dockables/index.js +0 -6
- package/a2ui/dockables/lifecycle.js +0 -84
- package/a2ui/dockables/provider.js +0 -59
- package/a2ui/manifest-runtime.js +0 -226
- package/a2ui/registry.js +0 -200
- package/a2ui/renderer.js +0 -361
- package/a2ui/stream.js +0 -243
- package/a2ui/surface-manifest.js +0 -294
- package/a2ui/surface.js +0 -222
- package/a2ui/wire-factory.js +0 -134
- package/a2ui/wiring-engine.js +0 -209
- package/a2ui/wiring-registry.js +0 -342
package/a2ui/surface-manifest.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Surface Manifest — Multi-surface relationship document (A008).
|
|
3
|
-
*
|
|
4
|
-
* Manages a graph of surfaces and their associations:
|
|
5
|
-
* routes-to, feeds, shares-context, depends-on, triggers, contains, slots-into
|
|
6
|
-
*
|
|
7
|
-
* The manifest is the design-time document that describes application topology.
|
|
8
|
-
* The runtime reads it to set up navigation, pre-fetch data, and manage
|
|
9
|
-
* cross-surface context.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {object} SurfaceDescriptor
|
|
14
|
-
* @property {string} name — Human-readable name
|
|
15
|
-
* @property {string} [route] — URL pattern with :param placeholders
|
|
16
|
-
* @property {boolean} [entryPoint] — Can user navigate here directly?
|
|
17
|
-
* @property {string[]} [requiredParams] — Params needed to render
|
|
18
|
-
* @property {Record<string, object>} [produces] — Data this surface outputs
|
|
19
|
-
* @property {Record<string, { keys: string[] }>} [consumes] — Named contexts consumed
|
|
20
|
-
* @property {string} [status] — generated | manual | template | placeholder
|
|
21
|
-
* @property {string} [generatedBy] — Execution ID from gen-ui pipeline
|
|
22
|
-
* @property {string[]} [tags] — Freeform tags
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @typedef {object} Association
|
|
27
|
-
* @property {string} type — routes-to | feeds | shares-context | depends-on | triggers | contains | slots-into
|
|
28
|
-
* @property {string} from — Source surface ID
|
|
29
|
-
* @property {string} to — Target surface ID
|
|
30
|
-
* @property {string} [trigger] — What activates this association
|
|
31
|
-
* @property {Record<string, object>} [params] — Data passed from source to target
|
|
32
|
-
* @property {object} [mapping] — Data flow mapping (for feeds)
|
|
33
|
-
* @property {string} [context] — Shared context name (for shares-context)
|
|
34
|
-
* @property {string} [condition] — Dependency condition (for depends-on)
|
|
35
|
-
* @property {string} [fallback] — Fallback action (for depends-on)
|
|
36
|
-
* @property {string} [effect] — Side effect (for triggers)
|
|
37
|
-
* @property {string} [slot] — Composition slot (for contains/slots-into)
|
|
38
|
-
* @property {number} [position] — Order within slot
|
|
39
|
-
* @property {object} [meta] — Freeform metadata
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
export class SurfaceManifest {
|
|
43
|
-
#id;
|
|
44
|
-
#name;
|
|
45
|
-
#version;
|
|
46
|
-
/** @type {Map<string, SurfaceDescriptor>} */
|
|
47
|
-
#surfaces = new Map();
|
|
48
|
-
/** @type {Association[]} */
|
|
49
|
-
#associations = [];
|
|
50
|
-
/** @type {Map<string, object>} */
|
|
51
|
-
#sharedContexts = new Map();
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @param {object} opts
|
|
55
|
-
* @param {string} opts.id — Manifest ID
|
|
56
|
-
* @param {string} opts.name — Human-readable name
|
|
57
|
-
* @param {string} [opts.version]
|
|
58
|
-
*/
|
|
59
|
-
constructor({ id, name, version = '1.0.0' }) {
|
|
60
|
-
this.#id = id;
|
|
61
|
-
this.#name = name;
|
|
62
|
-
this.#version = version;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ── Surface CRUD ──
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Add or update a surface descriptor.
|
|
69
|
-
* @param {string} surfaceId
|
|
70
|
-
* @param {SurfaceDescriptor} descriptor
|
|
71
|
-
*/
|
|
72
|
-
addSurface(surfaceId, descriptor) {
|
|
73
|
-
this.#surfaces.set(surfaceId, { ...descriptor });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Remove a surface and all its associations.
|
|
78
|
-
* @param {string} surfaceId
|
|
79
|
-
*/
|
|
80
|
-
removeSurface(surfaceId) {
|
|
81
|
-
this.#surfaces.delete(surfaceId);
|
|
82
|
-
this.#associations = this.#associations.filter(
|
|
83
|
-
a => a.from !== surfaceId && a.to !== surfaceId
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get a surface descriptor.
|
|
89
|
-
* @param {string} surfaceId
|
|
90
|
-
* @returns {SurfaceDescriptor | null}
|
|
91
|
-
*/
|
|
92
|
-
getSurface(surfaceId) {
|
|
93
|
-
return this.#surfaces.get(surfaceId) ?? null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** @returns {string[]} */
|
|
97
|
-
get surfaceIds() {
|
|
98
|
-
return [...this.#surfaces.keys()];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── Associations ──
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Add an association between surfaces.
|
|
105
|
-
* @param {Association} association
|
|
106
|
-
*/
|
|
107
|
-
addAssociation(association) {
|
|
108
|
-
// Validate surfaces exist
|
|
109
|
-
if (!this.#surfaces.has(association.from)) {
|
|
110
|
-
console.warn(`Manifest: surface "${association.from}" not found`);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (!this.#surfaces.has(association.to)) {
|
|
114
|
-
console.warn(`Manifest: surface "${association.to}" not found`);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Deduplicate: same type + from + to replaces existing
|
|
119
|
-
this.#associations = this.#associations.filter(
|
|
120
|
-
a => !(a.type === association.type && a.from === association.from && a.to === association.to)
|
|
121
|
-
);
|
|
122
|
-
this.#associations.push({ ...association });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Get associations for a surface (outgoing).
|
|
127
|
-
* @param {string} surfaceId
|
|
128
|
-
* @param {string} [type] — Filter by association type
|
|
129
|
-
* @returns {Association[]}
|
|
130
|
-
*/
|
|
131
|
-
getAssociationsFrom(surfaceId, type) {
|
|
132
|
-
return this.#associations.filter(
|
|
133
|
-
a => a.from === surfaceId && (!type || a.type === type)
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Get associations targeting a surface (incoming).
|
|
139
|
-
* @param {string} surfaceId
|
|
140
|
-
* @param {string} [type]
|
|
141
|
-
* @returns {Association[]}
|
|
142
|
-
*/
|
|
143
|
-
getAssociationsTo(surfaceId, type) {
|
|
144
|
-
return this.#associations.filter(
|
|
145
|
-
a => a.to === surfaceId && (!type || a.type === type)
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Get all surfaces sharing a context with the given surface.
|
|
151
|
-
* @param {string} surfaceId
|
|
152
|
-
* @returns {{ surfaceId: string, context: string }[]}
|
|
153
|
-
*/
|
|
154
|
-
getSharedContextPeers(surfaceId) {
|
|
155
|
-
const peers = [];
|
|
156
|
-
for (const a of this.#associations) {
|
|
157
|
-
if (a.type !== 'shares-context') continue;
|
|
158
|
-
if (a.from === surfaceId) peers.push({ surfaceId: a.to, context: a.context });
|
|
159
|
-
if (a.to === surfaceId) peers.push({ surfaceId: a.from, context: a.context });
|
|
160
|
-
}
|
|
161
|
-
return peers;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ── Shared Contexts ──
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Define a shared context.
|
|
168
|
-
* @param {string} name
|
|
169
|
-
* @param {object} config — { shape, source, params }
|
|
170
|
-
*/
|
|
171
|
-
defineSharedContext(name, config) {
|
|
172
|
-
this.#sharedContexts.set(name, { ...config });
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get a shared context definition.
|
|
177
|
-
* @param {string} name
|
|
178
|
-
* @returns {object | null}
|
|
179
|
-
*/
|
|
180
|
-
getSharedContext(name) {
|
|
181
|
-
return this.#sharedContexts.get(name) ?? null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── Validation ──
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Validate the manifest for common issues.
|
|
188
|
-
* @returns {{ valid: boolean, issues: { severity: string, message: string }[] }}
|
|
189
|
-
*/
|
|
190
|
-
validate() {
|
|
191
|
-
const issues = [];
|
|
192
|
-
|
|
193
|
-
// Check for orphan surfaces (no associations)
|
|
194
|
-
for (const id of this.#surfaces.keys()) {
|
|
195
|
-
const hasAssoc = this.#associations.some(a => a.from === id || a.to === id);
|
|
196
|
-
if (!hasAssoc && this.#surfaces.size > 1) {
|
|
197
|
-
issues.push({ severity: 'warning', message: `Surface "${id}" has no associations` });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check for missing entry points
|
|
202
|
-
const entryPoints = [...this.#surfaces.entries()].filter(([, d]) => d.entryPoint);
|
|
203
|
-
if (entryPoints.length === 0 && this.#surfaces.size > 0) {
|
|
204
|
-
issues.push({ severity: 'warning', message: 'No surface marked as entryPoint' });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Check depends-on has fallback
|
|
208
|
-
for (const a of this.#associations) {
|
|
209
|
-
if (a.type === 'depends-on' && !a.fallback) {
|
|
210
|
-
issues.push({ severity: 'warning', message: `depends-on from "${a.from}" to "${a.to}" has no fallback` });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Check shares-context references defined contexts
|
|
215
|
-
for (const a of this.#associations) {
|
|
216
|
-
if (a.type === 'shares-context' && a.context && !this.#sharedContexts.has(a.context)) {
|
|
217
|
-
issues.push({ severity: 'error', message: `Shared context "${a.context}" referenced but not defined` });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Check feeds associations have trigger
|
|
222
|
-
for (const a of this.#associations) {
|
|
223
|
-
if (a.type === 'feeds' && !a.trigger) {
|
|
224
|
-
issues.push({ severity: 'warning', message: `feeds from "${a.from}" to "${a.to}" has no trigger` });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Check routes-to targets have routes
|
|
229
|
-
for (const a of this.#associations) {
|
|
230
|
-
if (a.type === 'routes-to') {
|
|
231
|
-
const target = this.#surfaces.get(a.to);
|
|
232
|
-
if (target && !target.route) {
|
|
233
|
-
issues.push({ severity: 'error', message: `routes-to target "${a.to}" has no route defined` });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
valid: !issues.some(i => i.severity === 'error'),
|
|
240
|
-
issues,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ── Serialization ──
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Export as a JSON-serializable object.
|
|
248
|
-
* @returns {object}
|
|
249
|
-
*/
|
|
250
|
-
toJSON() {
|
|
251
|
-
return {
|
|
252
|
-
$schema: 'https://a2ui.dev/schema/relationships/v1',
|
|
253
|
-
id: this.#id,
|
|
254
|
-
name: this.#name,
|
|
255
|
-
version: this.#version,
|
|
256
|
-
surfaces: Object.fromEntries(this.#surfaces),
|
|
257
|
-
associations: [...this.#associations],
|
|
258
|
-
sharedContexts: Object.fromEntries(this.#sharedContexts),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Import from a JSON object.
|
|
264
|
-
* @param {object} json
|
|
265
|
-
* @returns {SurfaceManifest}
|
|
266
|
-
*/
|
|
267
|
-
static fromJSON(json) {
|
|
268
|
-
const manifest = new SurfaceManifest({
|
|
269
|
-
id: json.id,
|
|
270
|
-
name: json.name,
|
|
271
|
-
version: json.version,
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
if (json.surfaces) {
|
|
275
|
-
for (const [id, desc] of Object.entries(json.surfaces)) {
|
|
276
|
-
manifest.addSurface(id, desc);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (json.sharedContexts) {
|
|
281
|
-
for (const [name, config] of Object.entries(json.sharedContexts)) {
|
|
282
|
-
manifest.defineSharedContext(name, config);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (json.associations) {
|
|
287
|
-
for (const assoc of json.associations) {
|
|
288
|
-
manifest.addAssociation(assoc);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return manifest;
|
|
293
|
-
}
|
|
294
|
-
}
|
package/a2ui/surface.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Surface — A dock host where typed objects connect and disconnect cleanly.
|
|
3
|
-
*
|
|
4
|
-
* A Surface is the runtime representation of a rendered UI region.
|
|
5
|
-
* It holds a data model, resolved params, and a registry of docked objects
|
|
6
|
-
* (controllers, data sources, actions, providers, lifecycle hooks).
|
|
7
|
-
*
|
|
8
|
-
* Dockables attach via dock(surface) and clean up via undock().
|
|
9
|
-
* The Surface doesn't know what dockables do internally — it just manages
|
|
10
|
-
* their lifecycle and provides a shared context.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ── Dock order (lowest docks first, undocks last) ──
|
|
14
|
-
const DOCK_ORDER = { provider: 0, controller: 1, source: 2, action: 3, lifecycle: 4 };
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {object} DockEntry
|
|
18
|
-
* @property {import('./dockables/base.js').Dockable} dockable
|
|
19
|
-
* @property {Function|null} cleanup — returned from dock()
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export class Surface {
|
|
23
|
-
/** @type {string} */
|
|
24
|
-
surfaceId;
|
|
25
|
-
|
|
26
|
-
/** @type {Map<string, DockEntry>} keyed by dockable.id */
|
|
27
|
-
#docked = new Map();
|
|
28
|
-
|
|
29
|
-
/** @type {object} reactive-ish data model */
|
|
30
|
-
#model = {};
|
|
31
|
-
|
|
32
|
-
/** @type {object} resolved params (route, store, literal) */
|
|
33
|
-
#params = {};
|
|
34
|
-
|
|
35
|
-
/** @type {Map<string, Set<Function>>} model watchers keyed by path */
|
|
36
|
-
#watchers = new Map();
|
|
37
|
-
|
|
38
|
-
/** @type {HTMLElement} surface root element */
|
|
39
|
-
#rootElement;
|
|
40
|
-
|
|
41
|
-
/** @type {Map<string, HTMLElement>} componentId → DOM element */
|
|
42
|
-
#elements;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @param {string} surfaceId
|
|
46
|
-
* @param {HTMLElement} rootElement — the surface's root DOM node
|
|
47
|
-
* @param {Map<string, HTMLElement>} elements — componentId → element map
|
|
48
|
-
*/
|
|
49
|
-
constructor(surfaceId, rootElement, elements) {
|
|
50
|
-
this.surfaceId = surfaceId;
|
|
51
|
-
this.#rootElement = rootElement;
|
|
52
|
-
this.#elements = elements;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ── Context (passed to dockables) ─────────────────────────
|
|
56
|
-
|
|
57
|
-
/** @returns {SurfaceContext} */
|
|
58
|
-
get context() {
|
|
59
|
-
return {
|
|
60
|
-
surfaceId: this.surfaceId,
|
|
61
|
-
getElement: (id) => this.#elements.get(id) || null,
|
|
62
|
-
getRootElement: () => this.#rootElement,
|
|
63
|
-
getModel: (path) => path ? getPath(this.#model, path) : this.#model,
|
|
64
|
-
setModel: (path, value) => this.#setModel(path, value),
|
|
65
|
-
getDockable: (kind, id) => this.#getDockable(kind, id),
|
|
66
|
-
listDockables: (kind) => this.#listDockables(kind),
|
|
67
|
-
emit: (adiaEvent) => this.#emit(adiaEvent),
|
|
68
|
-
getParam: (key) => this.#params[key],
|
|
69
|
-
watchModel: (path, fn) => this.#watchModel(path, fn),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── Dock Protocol ─────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Dock a new object. Calls dockable.dock(context).
|
|
77
|
-
* @param {import('./dockables/base.js').Dockable} dockable
|
|
78
|
-
*/
|
|
79
|
-
dock(dockable) {
|
|
80
|
-
// If same id already docked, redock (hot-swap)
|
|
81
|
-
if (this.#docked.has(dockable.id)) {
|
|
82
|
-
this.undock(dockable.id);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const cleanup = dockable.dock(this.context);
|
|
86
|
-
this.#docked.set(dockable.id, {
|
|
87
|
-
dockable,
|
|
88
|
-
cleanup: typeof cleanup === 'function' ? cleanup : null,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Dock multiple objects in dependency order.
|
|
94
|
-
* @param {import('./dockables/base.js').Dockable[]} dockables
|
|
95
|
-
*/
|
|
96
|
-
dockAll(dockables) {
|
|
97
|
-
const sorted = [...dockables].sort(
|
|
98
|
-
(a, b) => (DOCK_ORDER[a.kind] ?? 9) - (DOCK_ORDER[b.kind] ?? 9)
|
|
99
|
-
);
|
|
100
|
-
for (const d of sorted) this.dock(d);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Undock by id. Calls cleanup then dockable.undock().
|
|
105
|
-
* @param {string} id
|
|
106
|
-
*/
|
|
107
|
-
undock(id) {
|
|
108
|
-
const entry = this.#docked.get(id);
|
|
109
|
-
if (!entry) return;
|
|
110
|
-
entry.cleanup?.();
|
|
111
|
-
entry.dockable.undock();
|
|
112
|
-
this.#docked.delete(id);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Undock everything in reverse dependency order.
|
|
117
|
-
*/
|
|
118
|
-
undockAll() {
|
|
119
|
-
const entries = [...this.#docked.entries()].sort(
|
|
120
|
-
(a, b) => (DOCK_ORDER[b[1].dockable.kind] ?? 9) - (DOCK_ORDER[a[1].dockable.kind] ?? 9)
|
|
121
|
-
);
|
|
122
|
-
for (const [id] of entries) this.undock(id);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Undock specific ids.
|
|
127
|
-
* @param {string[]} ids
|
|
128
|
-
*/
|
|
129
|
-
undockMany(ids) {
|
|
130
|
-
for (const id of ids) this.undock(id);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── Model ─────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
/** Set initial model state (before docking). */
|
|
136
|
-
setInitialModel(model) {
|
|
137
|
-
Object.assign(this.#model, model);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Set resolved params. */
|
|
141
|
-
setParams(params) {
|
|
142
|
-
Object.assign(this.#params, params);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Update element map (after re-render). */
|
|
146
|
-
updateElements(elements) {
|
|
147
|
-
this.#elements = elements;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── Private ───────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
#setModel(path, value) {
|
|
153
|
-
setPath(this.#model, path, value);
|
|
154
|
-
// Notify watchers for this path and any parent paths
|
|
155
|
-
for (const [watchPath, fns] of this.#watchers) {
|
|
156
|
-
if (path === watchPath || path.startsWith(watchPath + '/')) {
|
|
157
|
-
for (const fn of fns) fn(getPath(this.#model, watchPath));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
#watchModel(path, fn) {
|
|
163
|
-
if (!this.#watchers.has(path)) this.#watchers.set(path, new Set());
|
|
164
|
-
this.#watchers.get(path).add(fn);
|
|
165
|
-
return () => {
|
|
166
|
-
const set = this.#watchers.get(path);
|
|
167
|
-
set?.delete(fn);
|
|
168
|
-
if (set?.size === 0) this.#watchers.delete(path);
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
#getDockable(kind, id) {
|
|
173
|
-
const entry = this.#docked.get(id);
|
|
174
|
-
if (entry && entry.dockable.kind === kind) return entry.dockable;
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
#listDockables(kind) {
|
|
179
|
-
const result = [];
|
|
180
|
-
for (const { dockable } of this.#docked.values()) {
|
|
181
|
-
if (!kind || dockable.kind === kind) result.push(dockable);
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
#emit(adiaEvent) {
|
|
187
|
-
const target = adiaEvent.target
|
|
188
|
-
? this.#elements.get(adiaEvent.target)
|
|
189
|
-
: this.#rootElement;
|
|
190
|
-
if (!target) return;
|
|
191
|
-
|
|
192
|
-
target.dispatchEvent(new CustomEvent(adiaEvent.event, {
|
|
193
|
-
bubbles: true,
|
|
194
|
-
detail: adiaEvent,
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ── JSON Pointer helpers (simplified, "/" delimited) ────────
|
|
200
|
-
|
|
201
|
-
function getPath(obj, path) {
|
|
202
|
-
if (!path || path === '/') return obj;
|
|
203
|
-
const keys = path.replace(/^\//, '').split('/');
|
|
204
|
-
let current = obj;
|
|
205
|
-
for (const key of keys) {
|
|
206
|
-
if (current == null) return undefined;
|
|
207
|
-
current = current[key];
|
|
208
|
-
}
|
|
209
|
-
return current;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function setPath(obj, path, value) {
|
|
213
|
-
if (!path || path === '/') return;
|
|
214
|
-
const keys = path.replace(/^\//, '').split('/');
|
|
215
|
-
let current = obj;
|
|
216
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
217
|
-
const key = keys[i];
|
|
218
|
-
if (current[key] == null) current[key] = {};
|
|
219
|
-
current = current[key];
|
|
220
|
-
}
|
|
221
|
-
current[keys[keys.length - 1]] = value;
|
|
222
|
-
}
|
package/a2ui/wire-factory.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire Factory — Translates wireComponents messages into typed Dockable objects.
|
|
3
|
-
*
|
|
4
|
-
* This is the bridge between the LLM's declarative JSON and the runtime dock system.
|
|
5
|
-
* Each section of the wireComponents message maps to a Dockable type:
|
|
6
|
-
*
|
|
7
|
-
* data.sources → DataSourceDock[]
|
|
8
|
-
* state.controllers → ControllerDock[]
|
|
9
|
-
* actions → ActionDock[]
|
|
10
|
-
* provides → ProviderDock
|
|
11
|
-
* lifecycle → LifecycleDock
|
|
12
|
-
* state.model → set directly on Surface
|
|
13
|
-
*/
|
|
14
|
-
import { ControllerDock } from './dockables/controller.js';
|
|
15
|
-
import { DataSourceDock } from './dockables/data-source.js';
|
|
16
|
-
import { ActionDock } from './dockables/action.js';
|
|
17
|
-
import { ProviderDock } from './dockables/provider.js';
|
|
18
|
-
import { LifecycleDock } from './dockables/lifecycle.js';
|
|
19
|
-
import { resolveController, resolveData, resolveHandler } from './wiring-registry.js';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Adapt a registry handler (old ctx shape) to the dock signature (config, surfaceCtx, domEvent).
|
|
23
|
-
* The old handlers read from ctx.action.*, ctx.event, ctx.source, ctx.dataModel, ctx.updateModel, etc.
|
|
24
|
-
* The new signature passes (config, surfaceCtx, domEventOrResult).
|
|
25
|
-
*/
|
|
26
|
-
function adaptHandler(handlerFn) {
|
|
27
|
-
return async (config, surfaceCtx, domEventOrResult) => {
|
|
28
|
-
// Build a backward-compatible HandlerContext from the new args
|
|
29
|
-
const ctx = {
|
|
30
|
-
// Config props spread as "action" (old handlers read ctx.action.path, ctx.action.uri, etc.)
|
|
31
|
-
action: config,
|
|
32
|
-
// DOM event if present
|
|
33
|
-
event: domEventOrResult instanceof Event ? domEventOrResult : null,
|
|
34
|
-
source: domEventOrResult instanceof Event ? domEventOrResult.target : null,
|
|
35
|
-
// Model access
|
|
36
|
-
dataModel: surfaceCtx.getModel(),
|
|
37
|
-
updateModel: (path, value) => surfaceCtx.setModel(path, value),
|
|
38
|
-
// Controllers lookup
|
|
39
|
-
controllers: Object.fromEntries(
|
|
40
|
-
surfaceCtx.listDockables('controller').map(d => [d.id, d.controller])
|
|
41
|
-
),
|
|
42
|
-
// Params
|
|
43
|
-
params: {},
|
|
44
|
-
// Data resolver
|
|
45
|
-
resolveData,
|
|
46
|
-
};
|
|
47
|
-
return handlerFn(ctx);
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Create a handler resolver function that looks up from the registry
|
|
53
|
-
* and wraps with the adapter.
|
|
54
|
-
* @returns {(name: string) => Function|null}
|
|
55
|
-
*/
|
|
56
|
-
function createHandlerResolver() {
|
|
57
|
-
const cache = new Map();
|
|
58
|
-
return (name) => {
|
|
59
|
-
if (cache.has(name)) return cache.get(name);
|
|
60
|
-
const raw = resolveHandler(name);
|
|
61
|
-
if (!raw) return null;
|
|
62
|
-
const adapted = adaptHandler(raw);
|
|
63
|
-
cache.set(name, adapted);
|
|
64
|
-
return adapted;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Create a controller class resolver (async, lazy-loaded).
|
|
70
|
-
* @returns {(type: string) => Promise<Function|null>}
|
|
71
|
-
*/
|
|
72
|
-
function createControllerResolver() {
|
|
73
|
-
return async (type) => resolveController(type);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Create a data resolver that uses the wiring registry.
|
|
78
|
-
* @returns {(uri: string, ctx: object) => Promise<*>}
|
|
79
|
-
*/
|
|
80
|
-
function createDataResolver() {
|
|
81
|
-
return async (uri, ctx) => resolveData(uri, {});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Translate a wireComponents message into an array of Dockable objects.
|
|
86
|
-
*
|
|
87
|
-
* @param {object} msg — wireComponents message
|
|
88
|
-
* @returns {{ dockables: import('./dockables/base.js').Dockable[], initialModel: object|null }}
|
|
89
|
-
*/
|
|
90
|
-
export function createDockables(msg) {
|
|
91
|
-
const dockables = [];
|
|
92
|
-
const handlerResolver = createHandlerResolver();
|
|
93
|
-
const controllerResolver = createControllerResolver();
|
|
94
|
-
const dataResolver = createDataResolver();
|
|
95
|
-
|
|
96
|
-
// Data sources
|
|
97
|
-
if (msg.data?.sources) {
|
|
98
|
-
for (const source of msg.data.sources) {
|
|
99
|
-
dockables.push(new DataSourceDock(source, dataResolver));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Controllers
|
|
104
|
-
if (msg.state?.controllers) {
|
|
105
|
-
for (const ctrl of msg.state.controllers) {
|
|
106
|
-
dockables.push(new ControllerDock(ctrl, controllerResolver));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Actions
|
|
111
|
-
if (msg.actions) {
|
|
112
|
-
for (const action of msg.actions) {
|
|
113
|
-
dockables.push(new ActionDock(action, handlerResolver));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Provides
|
|
118
|
-
if (msg.provides) {
|
|
119
|
-
const provides = Array.isArray(msg.provides) ? msg.provides : [msg.provides];
|
|
120
|
-
for (const p of provides) {
|
|
121
|
-
dockables.push(new ProviderDock(p));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Lifecycle
|
|
126
|
-
if (msg.lifecycle) {
|
|
127
|
-
dockables.push(new LifecycleDock(msg.lifecycle, handlerResolver));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Initial model (not a dockable — set directly on Surface)
|
|
131
|
-
const initialModel = msg.state?.model || null;
|
|
132
|
-
|
|
133
|
-
return { dockables, initialModel };
|
|
134
|
-
}
|