@ggui-ai/iframe-runtime 0.17.0 → 0.19.0
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/dist/dismiss-intent.d.ts +28 -0
- package/dist/dismiss-intent.js +65 -0
- package/dist/iframe-runtime.js +64 -64
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/react-renderer.d.ts +9 -0
- package/dist/react-renderer.js +3 -0
- package/dist/render-item.d.ts +8 -0
- package/dist/render-item.js +5 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.js +45 -3
- package/package.json +7 -7
package/dist/react-renderer.d.ts
CHANGED
|
@@ -129,6 +129,15 @@ export interface ReactRootMount {
|
|
|
129
129
|
unmount(): void;
|
|
130
130
|
/** Current component code (useful for tests + audit). */
|
|
131
131
|
readonly currentCode: string | null;
|
|
132
|
+
/**
|
|
133
|
+
* Whether this mount HOLDS a component — i.e. whether the tree it renders
|
|
134
|
+
* is anything but its own stylesheet (ggui#1103). `applyRender` succeeding
|
|
135
|
+
* is not the same fact: a payload with no code, or a module that threw,
|
|
136
|
+
* leaves the scope element carrying only its `<style>`, and a caller that
|
|
137
|
+
* treats "a render was applied" as "something painted" retires the host's
|
|
138
|
+
* waiting indicator over a blank card.
|
|
139
|
+
*/
|
|
140
|
+
readonly painted: boolean;
|
|
132
141
|
}
|
|
133
142
|
/**
|
|
134
143
|
* Mount a React root inside `container` and render `render`'s
|
package/dist/react-renderer.js
CHANGED
package/dist/render-item.d.ts
CHANGED
|
@@ -106,6 +106,14 @@ export interface RenderItemHandle {
|
|
|
106
106
|
unmount(): void;
|
|
107
107
|
/** Current render kind. Test-only — not load-bearing. */
|
|
108
108
|
readonly kind: MountedKind;
|
|
109
|
+
/**
|
|
110
|
+
* Whether the active mount PAINTS something (ggui#1103). A provisional,
|
|
111
|
+
* system or MCP-app mount always does; a React mount does only while it
|
|
112
|
+
* holds a component. The runtime reads this to decide whether the served
|
|
113
|
+
* shell's waiting indicator has become a lie — "a render was applied" is
|
|
114
|
+
* not that fact.
|
|
115
|
+
*/
|
|
116
|
+
readonly painted: boolean;
|
|
109
117
|
}
|
|
110
118
|
export declare function mountRender(container: HTMLElement, opts: RenderItemOptions): Promise<RenderItemHandle>;
|
|
111
119
|
export {};
|
package/dist/render-item.js
CHANGED
|
@@ -222,6 +222,11 @@ export async function mountRender(container, opts) {
|
|
|
222
222
|
get kind() {
|
|
223
223
|
return currentKind;
|
|
224
224
|
},
|
|
225
|
+
get painted() {
|
|
226
|
+
if (currentKind === 'react')
|
|
227
|
+
return reactMount?.painted === true;
|
|
228
|
+
return currentKind !== 'none';
|
|
229
|
+
},
|
|
225
230
|
async update(next) {
|
|
226
231
|
const prevKind = currentKind;
|
|
227
232
|
currentOpts = next;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -421,6 +421,14 @@ export interface RendererHandle {
|
|
|
421
421
|
* `propsSpec` / `streamSpec`.
|
|
422
422
|
*/
|
|
423
423
|
getCurrentGguiSession(): GguiSession | GguiSessionSeedInput | null;
|
|
424
|
+
/**
|
|
425
|
+
* Whether the mounted render PAINTS something (ggui#1103) — false while no
|
|
426
|
+
* render has mounted, and false for a mount holding no component (a payload
|
|
427
|
+
* with no code, or a module that threw). Read by the boot to decide whether
|
|
428
|
+
* the served shell's waiting indicator has become a lie; "a render was
|
|
429
|
+
* applied" is a different fact and was the one being used.
|
|
430
|
+
*/
|
|
431
|
+
painted(): boolean;
|
|
424
432
|
readonly validatorCtx: RendererValidatorContext;
|
|
425
433
|
/**
|
|
426
434
|
* Send surface for outbound frames. Wired by `setup()` to the WS
|
package/dist/runtime.js
CHANGED
|
@@ -32,6 +32,7 @@ import { MCP_APP_BOOTSTRAP_FAILED_TYPE, MCP_APP_RENDERER_READY_TYPE, parseMcpApp
|
|
|
32
32
|
import { effectiveThemeId, effectiveThemeMode, } from '@ggui-ai/protocol/integrations/theme-binding';
|
|
33
33
|
import { extractLocatorFromToolResult, parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, reportInvalidAppTheme, reportStrippedThemeMembers, } from './meta-parse.js';
|
|
34
34
|
import { projectHostContext } from '@ggui-ai/protocol/wire';
|
|
35
|
+
import { installDismissIntentListener } from './dismiss-intent.js';
|
|
35
36
|
import { App, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
36
37
|
import { applyHostContextStyling, attachListener as attachHostContextListener, seed as seedHostContext, subscribeThemeChange as subscribeHostThemeChange, } from './host-context-emitter.js';
|
|
37
38
|
import { mapHostPaletteToGguiVars } from './host-palette-bridge.js';
|
|
@@ -373,6 +374,13 @@ export async function bootSequence(opts) {
|
|
|
373
374
|
onLifecycle?.(makeLifecycleEvent('error', { error: { code: reason, message } }));
|
|
374
375
|
};
|
|
375
376
|
const refs = ensureStatusDom(doc);
|
|
377
|
+
// ggui#1109: a key pressed inside the card's document never reaches the page
|
|
378
|
+
// around it, so a dismiss gesture died here — which is the moment a user who
|
|
379
|
+
// just clicked the card presses Escape. The card forwards the INTENT and
|
|
380
|
+
// never acts on it; the host owns what dismissal means, and ignoring is
|
|
381
|
+
// conformant. Installed before any mount, because the gesture can precede
|
|
382
|
+
// the first frame.
|
|
383
|
+
installDismissIntentListener(doc);
|
|
376
384
|
// Live regions up BEFORE anything can announce through them
|
|
377
385
|
// (ggui#447). A region created in the same tick as its first message
|
|
378
386
|
// is a region no screen reader was watching yet, and that first
|
|
@@ -652,6 +660,7 @@ export async function bootSequence(opts) {
|
|
|
652
660
|
mountedRender = target;
|
|
653
661
|
if (renderer !== null) {
|
|
654
662
|
await renderer.applyRender(target);
|
|
663
|
+
retireStandbyIfPainted();
|
|
655
664
|
if (target.type !== 'mcpApps' && target.type !== 'system') {
|
|
656
665
|
renderer.channelTransport.applyRender({
|
|
657
666
|
sessionId: target.id,
|
|
@@ -671,11 +680,42 @@ export async function bootSequence(opts) {
|
|
|
671
680
|
if (codeReadyEmitted)
|
|
672
681
|
return;
|
|
673
682
|
codeReadyEmitted = true;
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
|
|
683
|
+
// The served shell's working-state mark (#667) is retired by PAINT, not by
|
|
684
|
+
// this event (ggui#1103): `code-ready` fires after a render was APPLIED,
|
|
685
|
+
// and applying succeeds when the mount holds no component — so retiring
|
|
686
|
+
// here left a blank box with no indicator, indistinguishable from a card
|
|
687
|
+
// still negotiating. `code-ready` itself keeps firing exactly where it did:
|
|
688
|
+
// hosts pin selectors on it (E2E specs, accessibility scanners).
|
|
689
|
+
retireStandbyIfPainted();
|
|
677
690
|
onLifecycle?.(makeLifecycleEvent('code-ready', { sessionId: meta.sessionId }));
|
|
678
691
|
};
|
|
692
|
+
/**
|
|
693
|
+
* Retire the shell's standby ONLY when the mount paints something, and say
|
|
694
|
+
* so when a boot reaches its end painting nothing (ggui#1103). Three states
|
|
695
|
+
* that looked alike — negotiating, empty, painted — now read apart: the
|
|
696
|
+
* standby means "still coming", its absence with content means the card,
|
|
697
|
+
* its absence with a said status means "nothing to show".
|
|
698
|
+
*/
|
|
699
|
+
let saidNothingToShow = false;
|
|
700
|
+
function retireStandbyIfPainted() {
|
|
701
|
+
if (renderer !== null && renderer.painted()) {
|
|
702
|
+
retireShellLoadingIndicator(doc);
|
|
703
|
+
// A frame that lands after we said there was nothing withdraws the
|
|
704
|
+
// sentence — the card is the answer now.
|
|
705
|
+
if (saidNothingToShow) {
|
|
706
|
+
saidNothingToShow = false;
|
|
707
|
+
setConnectedStatus(refs);
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (!codeReadyEmitted)
|
|
712
|
+
return; // still coming — the standby is honest
|
|
713
|
+
retireShellLoadingIndicator(doc);
|
|
714
|
+
if (!saidNothingToShow) {
|
|
715
|
+
saidNothingToShow = true;
|
|
716
|
+
setStatus(refs, 'Nothing to show for this card.', 'idle');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
679
719
|
// Mode discriminators. The mount surface is DECOUPLED from the live
|
|
680
720
|
// channel: static content (codeUrl/codeB64/kind) paints immediately
|
|
681
721
|
// with no WS; the live trio (wsUrl+wsToken) is an OPTIONAL
|
|
@@ -745,6 +785,7 @@ export async function bootSequence(opts) {
|
|
|
745
785
|
if (seed !== null) {
|
|
746
786
|
await renderer.applyRender(seed);
|
|
747
787
|
mountedRender = seed;
|
|
788
|
+
retireStandbyIfPainted();
|
|
748
789
|
emitCodeReadyOnce();
|
|
749
790
|
}
|
|
750
791
|
}
|
|
@@ -4084,6 +4125,7 @@ async function bootProduction(opts) {
|
|
|
4084
4125
|
getChannelTransport: () => channelTransport,
|
|
4085
4126
|
}));
|
|
4086
4127
|
return {
|
|
4128
|
+
painted: () => renderHandle?.painted === true,
|
|
4087
4129
|
rootWireConfig: rootConfig,
|
|
4088
4130
|
streamBus,
|
|
4089
4131
|
applyRender,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "The iframe-local ggui runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and mounts the rendered UI. Bundles React, ReactDOM, and the ggui design system so generated component code has a single seam to read from.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"react": "^19.0.0",
|
|
42
42
|
"react-dom": "^19.0.0",
|
|
43
43
|
"zod": "^4.3.6",
|
|
44
|
-
"@ggui-ai/design": "0.
|
|
45
|
-
"@ggui-ai/gadgets": "0.
|
|
46
|
-
"@ggui-ai/
|
|
47
|
-
"@ggui-ai/
|
|
48
|
-
"@ggui-ai/
|
|
49
|
-
"@ggui-ai/wire": "0.
|
|
44
|
+
"@ggui-ai/design": "0.19.0",
|
|
45
|
+
"@ggui-ai/gadgets": "0.19.0",
|
|
46
|
+
"@ggui-ai/preview-a2ui": "0.19.0",
|
|
47
|
+
"@ggui-ai/protocol": "0.19.0",
|
|
48
|
+
"@ggui-ai/live-channel": "0.19.0",
|
|
49
|
+
"@ggui-ai/wire": "0.19.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.27.0"
|