@ggui-ai/iframe-runtime 0.1.0-rc.1 → 0.2.0-alpha.1
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/README.md +7 -6
- package/dist/channel-transport.d.ts +15 -15
- package/dist/channel-transport.js +32 -35
- package/dist/channels/channel-payload.d.ts +2 -2
- package/dist/channels/channel-payload.js +1 -1
- package/dist/channels/data.d.ts +14 -19
- package/dist/channels/data.js +13 -31
- package/dist/channels/drain-ack.d.ts +2 -2
- package/dist/channels/drain-ack.js +1 -1
- package/dist/channels/feedback.d.ts +1 -1
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.js +1 -1
- package/dist/channels/props-update.d.ts +33 -37
- package/dist/channels/props-update.js +30 -106
- package/dist/channels/render-frame.d.ts +52 -0
- package/dist/channels/render-frame.js +53 -0
- package/dist/channels/system.d.ts +1 -1
- package/dist/compiled-validators.d.ts +15 -0
- package/dist/compiled-validators.js +31 -0
- package/dist/context-observer.d.ts +68 -8
- package/dist/context-observer.js +25 -45
- package/dist/events-polling.d.ts +95 -0
- package/dist/events-polling.js +143 -0
- package/dist/gadget-loader.d.ts +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/host-context-emitter.d.ts +84 -20
- package/dist/host-context-emitter.js +159 -29
- package/dist/iframe-runtime.js +98 -59
- package/dist/index.d.ts +18 -13
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +5 -5
- package/dist/mcp-app-iframe-host.d.ts +9 -9
- package/dist/mcp-app-iframe-host.js +16 -17
- package/dist/meta-parse.d.ts +44 -0
- package/dist/meta-parse.js +390 -0
- package/dist/observability.d.ts +6 -6
- package/dist/protocol-error.d.ts +7 -7
- package/dist/provisional-renderer.d.ts +1 -1
- package/dist/provisional-renderer.js +1 -1
- package/dist/react-renderer.d.ts +10 -11
- package/dist/react-renderer.js +8 -8
- package/dist/registry-subscribe.d.ts +27 -9
- package/dist/registry-subscribe.js +32 -27
- package/dist/render-item.d.ts +83 -0
- package/dist/{stack-item-renderer.js → render-item.js} +106 -106
- package/dist/runtime.d.ts +183 -131
- package/dist/runtime.js +1264 -1330
- package/dist/status-dom.d.ts +52 -47
- package/dist/status-dom.js +51 -34
- package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
- package/dist/system-cards/NoCredentialsCard.js +1 -1
- package/dist/system-cards/ProtocolProbeCard.js +2 -2
- package/dist/system-cards/index.d.ts +4 -4
- package/dist/system-cards/index.js +4 -4
- package/dist/types.d.ts +70 -34
- package/dist/types.js +1 -1
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +2 -2
- package/dist/wire-config.d.ts +54 -82
- package/dist/wire-config.js +61 -93
- package/package.json +9 -8
- package/dist/bootstrap.d.ts +0 -72
- package/dist/bootstrap.js +0 -500
- package/dist/canvas/animator/animator-host.d.ts +0 -63
- package/dist/canvas/animator/animator-host.js +0 -105
- package/dist/canvas/animator/animator-pill.d.ts +0 -42
- package/dist/canvas/animator/animator-pill.js +0 -119
- package/dist/canvas/animator/state-machine.d.ts +0 -106
- package/dist/canvas/animator/state-machine.js +0 -150
- package/dist/canvas/canvas-shell.d.ts +0 -77
- package/dist/canvas/canvas-shell.js +0 -117
- package/dist/canvas/display-mode-policy.d.ts +0 -104
- package/dist/canvas/display-mode-policy.js +0 -98
- package/dist/canvas/mount.d.ts +0 -150
- package/dist/canvas/mount.js +0 -202
- package/dist/canvas/nav-stack.d.ts +0 -133
- package/dist/canvas/nav-stack.js +0 -172
- package/dist/channels/push.d.ts +0 -47
- package/dist/channels/push.js +0 -54
- package/dist/stack-item-renderer.d.ts +0 -103
- package/dist/stack.d.ts +0 -90
- package/dist/stack.js +0 -118
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Render dispatcher — routes a single {@link Render} to the
|
|
3
3
|
* appropriate renderer based on its discriminator.
|
|
4
4
|
*
|
|
5
|
+
* Post-render-identity-collapse (2026-05-27): the iframe-runtime mounts
|
|
6
|
+
* EXACTLY ONE render per iframe, in a single container. This module
|
|
7
|
+
* exposes one entry point — {@link mountRender} — that returns a
|
|
8
|
+
* {@link RenderItemHandle} the runtime uses to apply subsequent
|
|
9
|
+
* patches (props_update) or unmount on teardown. The earlier
|
|
10
|
+
* `StackRenderer` / `StackRenderContext` orchestrator (a stack-of-N
|
|
11
|
+
* map keyed by item id) was retired along with `StackModel`.
|
|
12
|
+
*
|
|
5
13
|
* The dispatch tree mirrors `@ggui-ai/react::DynamicComponent` +
|
|
6
|
-
* `
|
|
14
|
+
* `GguiRender`'s MCP-Apps branch:
|
|
7
15
|
*
|
|
8
16
|
* - `type === 'mcpApps'` → `mountMcpAppIframe` (vanilla iframe
|
|
9
17
|
* host; the recursive foreign-MCP-App path).
|
|
10
18
|
* - `componentCode` empty OR absent → `mountProvisional` (A2UI
|
|
11
19
|
* preview fed by the `_ggui:preview` reserved channel).
|
|
12
|
-
* - otherwise → `mountReactRoot` with a per-
|
|
13
|
-
* built
|
|
14
|
-
* The mounted component is wrapped in
|
|
20
|
+
* - otherwise → `mountReactRoot` with a per-render `WireConfig`
|
|
21
|
+
* built by the caller. The mounted component is wrapped in
|
|
15
22
|
* `<GguiWireProvider config={scopedConfig}>` through the
|
|
16
23
|
* `renderWrapper` seam.
|
|
17
24
|
*
|
|
18
|
-
* Per-
|
|
25
|
+
* Per-render lifecycle:
|
|
19
26
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* unmount + remount — props-only changes skip the React eval; kind
|
|
24
|
-
* changes unmount the current and mount the replacement.
|
|
27
|
+
* The handle's `update()` either transitions in-place (props-only,
|
|
28
|
+
* same kind) or tears down + remounts (kind changed — provisional
|
|
29
|
+
* → react when componentCode lands).
|
|
25
30
|
*
|
|
26
31
|
* StreamBus integration:
|
|
27
32
|
*
|
|
@@ -40,16 +45,17 @@ import { mountMcpAppIframe, } from './mcp-app-iframe-host.js';
|
|
|
40
45
|
// =============================================================================
|
|
41
46
|
// Kind detection
|
|
42
47
|
// =============================================================================
|
|
43
|
-
function detectKind(
|
|
44
|
-
if (
|
|
48
|
+
function detectKind(render) {
|
|
49
|
+
if (render.type === 'mcpApps')
|
|
45
50
|
return 'mcpApps';
|
|
46
|
-
// System
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// System renders mount via the built-in `SystemCardHost` registry
|
|
52
|
+
// (the `'system'` branch in `mountByKind`). This is the single mount
|
|
53
|
+
// surface for server-emitted `kind` cards post boot-consolidation —
|
|
54
|
+
// the old self-contained path that hand-rolled `SystemCardHost` is
|
|
55
|
+
// gone, so this MUST route to `'system'`, not the A2UI placeholder.
|
|
56
|
+
if (render.type === 'system')
|
|
57
|
+
return 'system';
|
|
58
|
+
const code = render.componentCode;
|
|
53
59
|
if (typeof code === 'string' && code.trim().length > 0)
|
|
54
60
|
return 'react';
|
|
55
61
|
return 'provisional';
|
|
@@ -57,13 +63,18 @@ function detectKind(stackItem) {
|
|
|
57
63
|
// =============================================================================
|
|
58
64
|
// Mount
|
|
59
65
|
// =============================================================================
|
|
60
|
-
export async function
|
|
66
|
+
export async function mountRender(container, opts) {
|
|
61
67
|
let currentOpts = opts;
|
|
62
68
|
let currentKind = 'none';
|
|
63
69
|
let reactMount = null;
|
|
64
70
|
let provisionalMount = null;
|
|
65
71
|
let mcpMount = null;
|
|
66
72
|
let previewUnsubscribe = null;
|
|
73
|
+
// System-card mount (type:'system'). The render-only `SystemCardHost`
|
|
74
|
+
// tree has no wire/context wrap; we hold the react-dom Root for
|
|
75
|
+
// teardown + a re-render closure for same-kind prop updates.
|
|
76
|
+
let systemRoot = null;
|
|
77
|
+
let systemRerender = null;
|
|
67
78
|
function teardown() {
|
|
68
79
|
if (reactMount !== null) {
|
|
69
80
|
reactMount.unmount();
|
|
@@ -77,6 +88,11 @@ export async function renderStackItem(container, opts) {
|
|
|
77
88
|
mcpMount.unmount();
|
|
78
89
|
mcpMount = null;
|
|
79
90
|
}
|
|
91
|
+
if (systemRoot !== null) {
|
|
92
|
+
systemRoot.unmount();
|
|
93
|
+
systemRoot = null;
|
|
94
|
+
systemRerender = null;
|
|
95
|
+
}
|
|
80
96
|
if (previewUnsubscribe !== null) {
|
|
81
97
|
previewUnsubscribe();
|
|
82
98
|
previewUnsubscribe = null;
|
|
@@ -85,11 +101,11 @@ export async function renderStackItem(container, opts) {
|
|
|
85
101
|
}
|
|
86
102
|
async function mountByKind(kind) {
|
|
87
103
|
if (kind === 'mcpApps') {
|
|
88
|
-
//
|
|
89
|
-
const
|
|
104
|
+
// Render narrows to McpAppsRender when type='mcpApps'.
|
|
105
|
+
const render = currentOpts.render;
|
|
90
106
|
mcpMount = mountMcpAppIframe(container, {
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
render,
|
|
108
|
+
renderId: currentOpts.renderId,
|
|
93
109
|
...(currentOpts.mcpAppsServerBaseUrl !== undefined
|
|
94
110
|
? { serverBaseUrl: currentOpts.mcpAppsServerBaseUrl }
|
|
95
111
|
: {}),
|
|
@@ -97,6 +113,39 @@ export async function renderStackItem(container, opts) {
|
|
|
97
113
|
currentKind = 'mcpApps';
|
|
98
114
|
return;
|
|
99
115
|
}
|
|
116
|
+
if (kind === 'system') {
|
|
117
|
+
// System card — server-emitted `kind` mapped to a built-in
|
|
118
|
+
// `.tsx` via `SystemCardHost`. Render-only: no `__ggui__` shim,
|
|
119
|
+
// no GguiWireProvider, no contextSpec wrap (system cards don't
|
|
120
|
+
// dispatch wire actions or read contexts). The registry handles
|
|
121
|
+
// unknown kinds via a typed fallback. Dynamic-import keeps the
|
|
122
|
+
// system-cards module out of the base graph (boot.test.ts +
|
|
123
|
+
// non-system mounts never pull it).
|
|
124
|
+
const render = currentOpts.render;
|
|
125
|
+
if (render.type !== 'system')
|
|
126
|
+
return;
|
|
127
|
+
const [reactDomClient, systemCardsMod] = await Promise.all([
|
|
128
|
+
import('react-dom/client'),
|
|
129
|
+
import('./system-cards/index.js'),
|
|
130
|
+
]);
|
|
131
|
+
const root = reactDomClient.createRoot(container);
|
|
132
|
+
const renderCard = (r) => {
|
|
133
|
+
if (r.type !== 'system')
|
|
134
|
+
return;
|
|
135
|
+
root.render(createElement(systemCardsMod.SystemCardHost, {
|
|
136
|
+
kind: r.kind,
|
|
137
|
+
props: r.props ?? {},
|
|
138
|
+
...(currentOpts.themeId !== undefined
|
|
139
|
+
? { themeId: currentOpts.themeId }
|
|
140
|
+
: {}),
|
|
141
|
+
}));
|
|
142
|
+
};
|
|
143
|
+
renderCard(render);
|
|
144
|
+
systemRoot = root;
|
|
145
|
+
systemRerender = renderCard;
|
|
146
|
+
currentKind = 'system';
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
100
149
|
if (kind === 'provisional') {
|
|
101
150
|
provisionalMount = mountProvisional(container);
|
|
102
151
|
previewUnsubscribe = currentOpts.streamBus.subscribe('_ggui:preview', (env) => {
|
|
@@ -107,21 +156,21 @@ export async function renderStackItem(container, opts) {
|
|
|
107
156
|
}
|
|
108
157
|
// kind === 'react' — detectKind guarantees a generated component
|
|
109
158
|
// variant (no mcpApps, no system) by this point.
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
159
|
+
const render = currentOpts.render;
|
|
160
|
+
if (render.type === 'mcpApps' || render.type === 'system')
|
|
112
161
|
return;
|
|
113
|
-
// GG.8.2 — gadget package names from the
|
|
162
|
+
// GG.8.2 — gadget package names from the render's descriptor sidecar
|
|
114
163
|
// so the rewriter resolves each direct gadget import to its
|
|
115
164
|
// per-package shim. STDLIB `@ggui-ai/gadgets` is always rewritten.
|
|
116
|
-
const gadgetPackages = 'gadgetDescriptors' in
|
|
117
|
-
?
|
|
118
|
-
:
|
|
165
|
+
const gadgetPackages = 'gadgetDescriptors' in render && render.gadgetDescriptors !== undefined
|
|
166
|
+
? render.gadgetDescriptors.map((d) => d.package)
|
|
167
|
+
: currentOpts.gadgetPackages;
|
|
119
168
|
reactMount = await mountReactRoot(container, {
|
|
120
|
-
|
|
121
|
-
...(
|
|
122
|
-
componentCode:
|
|
123
|
-
...(
|
|
124
|
-
? { props:
|
|
169
|
+
render: {
|
|
170
|
+
...(render.id !== undefined ? { id: render.id } : {}),
|
|
171
|
+
componentCode: render.componentCode ?? '',
|
|
172
|
+
...(render.props !== undefined
|
|
173
|
+
? { props: render.props }
|
|
125
174
|
: {}),
|
|
126
175
|
},
|
|
127
176
|
...(gadgetPackages !== undefined ? { gadgetPackages } : {}),
|
|
@@ -150,7 +199,7 @@ export async function renderStackItem(container, opts) {
|
|
|
150
199
|
* wrapOuter(<GguiWireProvider><userComponent/></GguiWireProvider>)
|
|
151
200
|
*
|
|
152
201
|
* `wrapOuter` is supplied by `bootProduction` to install the
|
|
153
|
-
* `<ContextStateHost>` provider tree around the per-
|
|
202
|
+
* `<ContextStateHost>` provider tree around the per-render React
|
|
154
203
|
* mount. When absent, the wire provider is the only wrapper.
|
|
155
204
|
*/
|
|
156
205
|
function wrapInScopedProvider(mountedComponent) {
|
|
@@ -162,7 +211,7 @@ export async function renderStackItem(container, opts) {
|
|
|
162
211
|
: wireWrapped;
|
|
163
212
|
}
|
|
164
213
|
// Initial mount.
|
|
165
|
-
const initialKind = detectKind(opts.
|
|
214
|
+
const initialKind = detectKind(opts.render);
|
|
166
215
|
await mountByKind(initialKind);
|
|
167
216
|
return {
|
|
168
217
|
get kind() {
|
|
@@ -171,7 +220,7 @@ export async function renderStackItem(container, opts) {
|
|
|
171
220
|
async update(next) {
|
|
172
221
|
const prevKind = currentKind;
|
|
173
222
|
currentOpts = next;
|
|
174
|
-
const nextKind = detectKind(next.
|
|
223
|
+
const nextKind = detectKind(next.render);
|
|
175
224
|
if (nextKind !== prevKind) {
|
|
176
225
|
// Kind changed — tear down old + mount new.
|
|
177
226
|
teardown();
|
|
@@ -180,15 +229,15 @@ export async function renderStackItem(container, opts) {
|
|
|
180
229
|
}
|
|
181
230
|
// Same-kind update — route to the inner renderer's update.
|
|
182
231
|
if (nextKind === 'react' && reactMount !== null) {
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
232
|
+
const render = next.render;
|
|
233
|
+
if (render.type === 'mcpApps' || render.type === 'system')
|
|
185
234
|
return;
|
|
186
235
|
await reactMount.update({
|
|
187
|
-
|
|
188
|
-
...(
|
|
189
|
-
componentCode:
|
|
190
|
-
...(
|
|
191
|
-
? { props:
|
|
236
|
+
render: {
|
|
237
|
+
...(render.id !== undefined ? { id: render.id } : {}),
|
|
238
|
+
componentCode: render.componentCode ?? '',
|
|
239
|
+
...(render.props !== undefined
|
|
240
|
+
? { props: render.props }
|
|
192
241
|
: {}),
|
|
193
242
|
},
|
|
194
243
|
...(next.themeId !== undefined ? { themeId: next.themeId } : {}),
|
|
@@ -200,6 +249,17 @@ export async function renderStackItem(container, opts) {
|
|
|
200
249
|
: {}),
|
|
201
250
|
renderWrapper: wrapInScopedProvider,
|
|
202
251
|
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Same-kind system update — re-render the card with new props
|
|
255
|
+
// through the retained root (no teardown). System cards are
|
|
256
|
+
// pure functions of (kind, props); a props delta just re-renders.
|
|
257
|
+
if (nextKind === 'system' && systemRerender !== null) {
|
|
258
|
+
const render = next.render;
|
|
259
|
+
if (render.type !== 'system')
|
|
260
|
+
return;
|
|
261
|
+
systemRerender(render);
|
|
262
|
+
return;
|
|
203
263
|
}
|
|
204
264
|
// provisional / mcpApps same-kind: no in-place update surface.
|
|
205
265
|
// Provisional keeps accumulating envelopes; mcpApps iframe
|
|
@@ -208,63 +268,3 @@ export async function renderStackItem(container, opts) {
|
|
|
208
268
|
unmount: teardown,
|
|
209
269
|
};
|
|
210
270
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Minimal stack-level orchestrator — keyed by stackItem.id, mounts
|
|
213
|
-
* new items, updates existing ones, unmounts removed ones.
|
|
214
|
-
*
|
|
215
|
-
* The production renderer tree uses this; tests can drive the
|
|
216
|
-
* individual `renderStackItem` entry point directly.
|
|
217
|
-
*/
|
|
218
|
-
export class StackRenderer {
|
|
219
|
-
ctx;
|
|
220
|
-
handles = new Map();
|
|
221
|
-
constructor(ctx) {
|
|
222
|
-
this.ctx = ctx;
|
|
223
|
-
}
|
|
224
|
-
async applyStack(stack) {
|
|
225
|
-
const nextIds = new Set(stack.map((s) => s.id));
|
|
226
|
-
// Unmount removed items.
|
|
227
|
-
for (const [id, handle] of this.handles) {
|
|
228
|
-
if (!nextIds.has(id)) {
|
|
229
|
-
handle.unmount();
|
|
230
|
-
this.handles.delete(id);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
// Mount or update each item in stack order.
|
|
234
|
-
for (const item of stack) {
|
|
235
|
-
const container = this.ctx.containerFor(item.id);
|
|
236
|
-
const scopedWireConfig = this.ctx.getScopedWireConfig(item);
|
|
237
|
-
const wrapOuter = this.ctx.getOuterWrapper?.(item);
|
|
238
|
-
const opts = {
|
|
239
|
-
stackItem: item,
|
|
240
|
-
scopedWireConfig,
|
|
241
|
-
streamBus: this.ctx.streamBus,
|
|
242
|
-
sessionId: this.ctx.sessionId,
|
|
243
|
-
...(this.ctx.mcpAppsServerBaseUrl !== undefined
|
|
244
|
-
? { mcpAppsServerBaseUrl: this.ctx.mcpAppsServerBaseUrl }
|
|
245
|
-
: {}),
|
|
246
|
-
...(this.ctx.themeId !== undefined ? { themeId: this.ctx.themeId } : {}),
|
|
247
|
-
...(this.ctx.themeMode !== undefined ? { themeMode: this.ctx.themeMode } : {}),
|
|
248
|
-
...(this.ctx.onError !== undefined ? { onError: this.ctx.onError } : {}),
|
|
249
|
-
...(this.ctx.onRequestRepair !== undefined
|
|
250
|
-
? { onRequestRepair: this.ctx.onRequestRepair }
|
|
251
|
-
: {}),
|
|
252
|
-
...(wrapOuter !== undefined ? { wrapOuter } : {}),
|
|
253
|
-
};
|
|
254
|
-
const existing = this.handles.get(item.id);
|
|
255
|
-
if (existing === undefined) {
|
|
256
|
-
const handle = await renderStackItem(container, opts);
|
|
257
|
-
this.handles.set(item.id, handle);
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
await existing.update(opts);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
unmountAll() {
|
|
265
|
-
for (const handle of this.handles.values()) {
|
|
266
|
-
handle.unmount();
|
|
267
|
-
}
|
|
268
|
-
this.handles.clear();
|
|
269
|
-
}
|
|
270
|
-
}
|