@ggui-ai/iframe-runtime 0.12.0 → 0.13.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/iframe-runtime.js +61 -61
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/observability.d.ts +2 -7
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +21 -10
- package/dist/status-dom.d.ts +9 -0
- package/dist/status-dom.js +11 -0
- package/dist/system-cards/UiFeedbackCard.d.ts +13 -8
- package/dist/system-cards/UiFeedbackCard.js +32 -41
- package/dist/ui-feedback-chrome.d.ts +30 -0
- package/dist/ui-feedback-chrome.js +22 -0
- package/package.json +8 -8
package/dist/observability.d.ts
CHANGED
|
@@ -151,10 +151,7 @@ export interface ChannelPollRecoveredEvent {
|
|
|
151
151
|
* mirror `UiFeedbackPayload` in `@ggui-ai/mcp-apps-react` / `@ggui-ai/mcp-apps-react-native`
|
|
152
152
|
* (the host-chrome twin of this affordance):
|
|
153
153
|
*
|
|
154
|
-
* - `verdict` — `'
|
|
155
|
-
* free-text flow.
|
|
156
|
-
* - `comment` — present only for `verdict: 'other'` with a non-empty
|
|
157
|
-
* trimmed comment.
|
|
154
|
+
* - `verdict` — `'up'` / `'down'` (the two thumb verdicts, #653).
|
|
158
155
|
* - `sessionId` / `toolName` — present exactly when the runtime knew
|
|
159
156
|
* them at mount time.
|
|
160
157
|
*
|
|
@@ -175,9 +172,7 @@ export interface ChannelPollRecoveredEvent {
|
|
|
175
172
|
*/
|
|
176
173
|
export interface UiFeedbackEvent {
|
|
177
174
|
readonly kind: 'ui-feedback';
|
|
178
|
-
readonly verdict: '
|
|
179
|
-
/** Trimmed free-text comment; only on `verdict: 'other'`, never empty. */
|
|
180
|
-
readonly comment?: string;
|
|
175
|
+
readonly verdict: 'up' | 'down';
|
|
181
176
|
/** GguiSession id of the render the feedback is about. */
|
|
182
177
|
readonly sessionId?: string;
|
|
183
178
|
/** Tool that produced the render (e.g. `ggui_render`). */
|
package/dist/runtime.d.ts
CHANGED
|
@@ -276,6 +276,18 @@ export interface BootSequenceOptions {
|
|
|
276
276
|
* or not this option is bound.
|
|
277
277
|
*/
|
|
278
278
|
readonly onObserve?: ObservabilityEmitter;
|
|
279
|
+
/**
|
|
280
|
+
* Opt-in knob for the in-iframe UI-feedback chrome (#653). DEFAULT
|
|
281
|
+
* OFF by founder ruling — a generated component card carries no
|
|
282
|
+
* feedback affordance unless the embedding deployment passes `true`
|
|
283
|
+
* here. Even when opted in, the #471 live-sink gate still applies
|
|
284
|
+
* (see `shouldMountUiFeedbackChrome`): hosts that drop the
|
|
285
|
+
* `ggui:observe` envelope never get the affordance. Chrome
|
|
286
|
+
* configuration, not wire data — the agent cannot observe this knob
|
|
287
|
+
* (data-vs-behavior), so it is a boot option, never a contract
|
|
288
|
+
* field.
|
|
289
|
+
*/
|
|
290
|
+
readonly uiFeedback?: boolean;
|
|
279
291
|
/**
|
|
280
292
|
* Optional {@link LifecycleEmitter} sink. Fires on every renderer
|
|
281
293
|
* mount-state transition (`mounting` → `code-ready` | `error`,
|
package/dist/runtime.js
CHANGED
|
@@ -43,13 +43,13 @@ import { createChannelErrorHandler, createChannelPayloadHandler, createDataHandl
|
|
|
43
43
|
import { connectViaRegistry, } from './registry-subscribe.js';
|
|
44
44
|
import { buildBridgePolling, buildEventsPolling, createSequenceCursor, } from './events-polling.js';
|
|
45
45
|
import { unwrapCallToolResult } from './call-tool-unwrap.js';
|
|
46
|
-
import { ensureStatusDom, setStatus, setConnectedStatus, } from './status-dom.js';
|
|
46
|
+
import { ensureStatusDom, setStatus, setConnectedStatus, retireShellLoadingIndicator, } from './status-dom.js';
|
|
47
47
|
import { mergeReservedValidators, setActiveValidatorSet, } from './validation.js';
|
|
48
48
|
import { loadCompiledValidatorsFromUrl } from './compiled-validators.js';
|
|
49
49
|
import { mountRender, } from './render-item.js';
|
|
50
50
|
import { fromBootstrapFailure, } from './protocol-error.js';
|
|
51
51
|
import { postObservabilityToParent, } from './observability.js';
|
|
52
|
-
import { ACTION_TOAST_Z_INDEX, mountUiFeedbackChrome } from './ui-feedback-chrome.js';
|
|
52
|
+
import { ACTION_TOAST_Z_INDEX, mountUiFeedbackChrome, shouldMountUiFeedbackChrome, } from './ui-feedback-chrome.js';
|
|
53
53
|
import { hostCanReceiveMessages, hostCanRelayToolCalls, hostCapabilitiesCaptured, setHostCapabilities, } from './host-capabilities.js';
|
|
54
54
|
import { makeLifecycleEvent, postLifecycleToParent, } from './lifecycle.js';
|
|
55
55
|
import { contextSlotLastValues, createContextStateHost, installContextRegistry, reemitLastContextValues, } from './context-observer.js';
|
|
@@ -335,7 +335,7 @@ function postBootFailure(reason, message) {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
export async function bootSequence(opts) {
|
|
338
|
-
const { doc, app, transport, notifyParent, renderer: rendererHooks, onProtocolError, onObserve, onLifecycle } = opts;
|
|
338
|
+
const { doc, app, transport, notifyParent, renderer: rendererHooks, onProtocolError, onObserve, onLifecycle, uiFeedback } = opts;
|
|
339
339
|
const connectFn = opts.connectFn ?? connectViaRegistry;
|
|
340
340
|
const toolResultTimeoutMs = opts.toolResultTimeoutMs ?? POSTMESSAGE_BOOT_TIMEOUT_MS;
|
|
341
341
|
// Emit typed {@link ProtocolError} for every bootstrap-failure site
|
|
@@ -415,6 +415,7 @@ export async function bootSequence(opts) {
|
|
|
415
415
|
const initResult = await connectApp(app, transport);
|
|
416
416
|
if (!initResult.ok) {
|
|
417
417
|
setStatus(refs, `ui/initialize failed: ${initResult.message}`, 'error');
|
|
418
|
+
retireShellLoadingIndicator(doc);
|
|
418
419
|
emitBootFailure('UI_INITIALIZE_FAILED', initResult.message);
|
|
419
420
|
return { ok: false, mountedRender };
|
|
420
421
|
}
|
|
@@ -511,6 +512,7 @@ export async function bootSequence(opts) {
|
|
|
511
512
|
if (!parsed.ok) {
|
|
512
513
|
const message = `slice-meta parse failed: ${parsed.reason}`;
|
|
513
514
|
setStatus(refs, message, 'error');
|
|
515
|
+
retireShellLoadingIndicator(doc);
|
|
514
516
|
emitBootFailure(parsed.reason, message);
|
|
515
517
|
return { ok: false, mountedRender };
|
|
516
518
|
}
|
|
@@ -547,10 +549,12 @@ export async function bootSequence(opts) {
|
|
|
547
549
|
...(onObserve !== undefined ? { onObserve } : {}),
|
|
548
550
|
})
|
|
549
551
|
: null;
|
|
550
|
-
// In-iframe UI-feedback affordance (ggui#244) —
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
552
|
+
// In-iframe UI-feedback affordance (ggui#244) — DEFAULT OFF since
|
|
553
|
+
// #653 (founder ruling): mounts only when the boot options opt in
|
|
554
|
+
// via `uiFeedback: true`. When opted in it is additionally gated
|
|
555
|
+
// inside the helper on `window.parent !== window` (top-level
|
|
556
|
+
// `/r/<shortCode>` tabs have no `ggui:observe` egress, so they get
|
|
557
|
+
// NO affordance) AND here on a LIVE SINK: an
|
|
554
558
|
// injected `onObserve` emitter, or a first-party embed host — the
|
|
555
559
|
// only parent that consumes the `ggui:observe` envelope
|
|
556
560
|
// (mcp-app-iframe-host answers `ui/initialize` with exactly this
|
|
@@ -560,9 +564,12 @@ export async function bootSequence(opts) {
|
|
|
560
564
|
// retest surfaced it as unstyled clutter that could never deliver
|
|
561
565
|
// feedback anywhere. Fire-and-forget: chrome never blocks or fails
|
|
562
566
|
// the boot.
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
567
|
+
const mountFeedback = shouldMountUiFeedbackChrome({
|
|
568
|
+
uiFeedback,
|
|
569
|
+
onObserveBound: onObserve !== undefined,
|
|
570
|
+
hostName: app.getHostVersion()?.name,
|
|
571
|
+
});
|
|
572
|
+
if (mountFeedback) {
|
|
566
573
|
void mountUiFeedbackChrome(doc, {
|
|
567
574
|
emit: onObserve ?? postObservabilityToParent,
|
|
568
575
|
sessionId: meta.sessionId,
|
|
@@ -630,6 +637,9 @@ export async function bootSequence(opts) {
|
|
|
630
637
|
if (codeReadyEmitted)
|
|
631
638
|
return;
|
|
632
639
|
codeReadyEmitted = true;
|
|
640
|
+
// First real paint — the served shell's working-state mark (#667)
|
|
641
|
+
// becomes a lie the moment content lands.
|
|
642
|
+
retireShellLoadingIndicator(doc);
|
|
633
643
|
onLifecycle?.(makeLifecycleEvent('code-ready', { sessionId: meta.sessionId }));
|
|
634
644
|
};
|
|
635
645
|
// Mode discriminators. The mount surface is DECOUPLED from the live
|
|
@@ -721,6 +731,7 @@ export async function bootSequence(opts) {
|
|
|
721
731
|
if (!hasLiveTrio && mountedRender === null) {
|
|
722
732
|
const message = 'bootstrap carries neither static content (codeUrl/codeB64/kind) nor a live trio (wsUrl/wsToken)';
|
|
723
733
|
setStatus(refs, message, 'error');
|
|
734
|
+
retireShellLoadingIndicator(doc);
|
|
724
735
|
emitBootFailure('MISSING_META_GGUI_BOOTSTRAP', message);
|
|
725
736
|
return { ok: false, mountedRender };
|
|
726
737
|
}
|
package/dist/status-dom.d.ts
CHANGED
|
@@ -75,3 +75,12 @@ export declare function setStatus(refs: StatusRefs, text: string, state: StatusK
|
|
|
75
75
|
* intercepting `console.log`) get a stable string on every render.
|
|
76
76
|
*/
|
|
77
77
|
export declare function setConnectedStatus(refs: StatusRefs): void;
|
|
78
|
+
/**
|
|
79
|
+
* Retire the served shell's pre-render working-state mark (#667 —
|
|
80
|
+
* `[data-ggui-shell-loading]`, the ggui glyph treatment or an
|
|
81
|
+
* operator's court block). Called at `code-ready` (the first real
|
|
82
|
+
* paint makes a loading mark a lie) and on terminal boot failure (the
|
|
83
|
+
* failure card must not sit under an "everything is fine" animation).
|
|
84
|
+
* Idempotent; a shell without the mark is a no-op.
|
|
85
|
+
*/
|
|
86
|
+
export declare function retireShellLoadingIndicator(doc: Document): void;
|
package/dist/status-dom.js
CHANGED
|
@@ -66,3 +66,14 @@ export function setStatus(refs, text, state) {
|
|
|
66
66
|
export function setConnectedStatus(refs) {
|
|
67
67
|
setStatus(refs, 'Connected.', 'connected');
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Retire the served shell's pre-render working-state mark (#667 —
|
|
71
|
+
* `[data-ggui-shell-loading]`, the ggui glyph treatment or an
|
|
72
|
+
* operator's court block). Called at `code-ready` (the first real
|
|
73
|
+
* paint makes a loading mark a lie) and on terminal boot failure (the
|
|
74
|
+
* failure card must not sit under an "everything is fine" animation).
|
|
75
|
+
* Idempotent; a shell without the mark is a no-op.
|
|
76
|
+
*/
|
|
77
|
+
export function retireShellLoadingIndicator(doc) {
|
|
78
|
+
doc.querySelector('[data-ggui-shell-loading]')?.remove();
|
|
79
|
+
}
|
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
* ("did this generated UI work for you?").
|
|
4
4
|
*
|
|
5
5
|
* The in-iframe twin of the host-chrome `UiFeedback` component in
|
|
6
|
-
* `@ggui-ai/mcp-apps-react`: same
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* `@ggui-ai/mcp-apps-react`: same two verdicts — thumbs up / thumbs
|
|
7
|
+
* down (#653) — same acknowledgement after submit, same dismiss
|
|
8
|
+
* control, same payload semantics (context stamps present exactly when
|
|
9
|
+
* known). Where the host-chrome twin hands a payload to an
|
|
10
|
+
* `onUiFeedback` callback, this card builds a {@link UiFeedbackEvent}
|
|
11
|
+
* and hands it to the injected {@link ObservabilityEmitter} —
|
|
12
|
+
* production binds the `ggui:observe` postMessage-to-parent default,
|
|
13
|
+
* tests record.
|
|
14
|
+
*
|
|
15
|
+
* The verdict buttons are icon-only: monochrome stroked SVGs drawn in
|
|
16
|
+
* `currentColor`, so the icon ink follows the button's `--ggui-*`
|
|
17
|
+
* color token in both modes (the #653 theme-applicability
|
|
18
|
+
* requirement). The verdict's name lives in the `aria-label`.
|
|
14
19
|
*
|
|
15
20
|
* This is CHROME the runtime mounts adjacent to the session root
|
|
16
21
|
* (see `ui-feedback-chrome.ts`), NOT a system card: it has no
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* UiFeedbackCard — the runtime's in-iframe UI-feedback affordance
|
|
4
4
|
* ("did this generated UI work for you?").
|
|
5
5
|
*
|
|
6
6
|
* The in-iframe twin of the host-chrome `UiFeedback` component in
|
|
7
|
-
* `@ggui-ai/mcp-apps-react`: same
|
|
8
|
-
* (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* `@ggui-ai/mcp-apps-react`: same two verdicts — thumbs up / thumbs
|
|
8
|
+
* down (#653) — same acknowledgement after submit, same dismiss
|
|
9
|
+
* control, same payload semantics (context stamps present exactly when
|
|
10
|
+
* known). Where the host-chrome twin hands a payload to an
|
|
11
|
+
* `onUiFeedback` callback, this card builds a {@link UiFeedbackEvent}
|
|
12
|
+
* and hands it to the injected {@link ObservabilityEmitter} —
|
|
13
|
+
* production binds the `ggui:observe` postMessage-to-parent default,
|
|
14
|
+
* tests record.
|
|
15
|
+
*
|
|
16
|
+
* The verdict buttons are icon-only: monochrome stroked SVGs drawn in
|
|
17
|
+
* `currentColor`, so the icon ink follows the button's `--ggui-*`
|
|
18
|
+
* color token in both modes (the #653 theme-applicability
|
|
19
|
+
* requirement). The verdict's name lives in the `aria-label`.
|
|
15
20
|
*
|
|
16
21
|
* This is CHROME the runtime mounts adjacent to the session root
|
|
17
22
|
* (see `ui-feedback-chrome.ts`), NOT a system card: it has no
|
|
@@ -56,6 +61,8 @@ function buildStyles(scheme) {
|
|
|
56
61
|
},
|
|
57
62
|
verdictButton: {
|
|
58
63
|
appearance: 'none',
|
|
64
|
+
display: 'inline-flex',
|
|
65
|
+
alignItems: 'center',
|
|
59
66
|
background: dark
|
|
60
67
|
? 'var(--ggui-color-neutral-50, #1f2937)'
|
|
61
68
|
: 'var(--ggui-color-neutral-50, #f9fafb)',
|
|
@@ -63,9 +70,10 @@ function buildStyles(scheme) {
|
|
|
63
70
|
? '1px solid var(--ggui-color-neutral-200, #374151)'
|
|
64
71
|
: '1px solid var(--ggui-color-neutral-200, #e5e7eb)',
|
|
65
72
|
borderRadius: 'var(--ggui-shape-radius-sm, 4px)',
|
|
66
|
-
padding: '
|
|
73
|
+
padding: '3px 7px',
|
|
67
74
|
fontFamily: 'inherit',
|
|
68
75
|
fontSize: 'inherit',
|
|
76
|
+
// The icons stroke in currentColor — this token IS the icon ink.
|
|
69
77
|
color: dark
|
|
70
78
|
? 'var(--ggui-color-neutral-600, #d1d5db)'
|
|
71
79
|
: 'var(--ggui-color-neutral-600, #4b5563)',
|
|
@@ -83,51 +91,34 @@ function buildStyles(scheme) {
|
|
|
83
91
|
: 'var(--ggui-color-neutral-400, #9ca3af)',
|
|
84
92
|
cursor: 'pointer',
|
|
85
93
|
},
|
|
86
|
-
commentForm: {
|
|
87
|
-
display: 'inline-flex',
|
|
88
|
-
alignItems: 'center',
|
|
89
|
-
gap: 4,
|
|
90
|
-
margin: 0,
|
|
91
|
-
},
|
|
92
|
-
commentInput: {
|
|
93
|
-
fontFamily: 'inherit',
|
|
94
|
-
fontSize: 'inherit',
|
|
95
|
-
padding: '2px 6px',
|
|
96
|
-
minWidth: 140,
|
|
97
|
-
border: dark
|
|
98
|
-
? '1px solid var(--ggui-color-neutral-200, #374151)'
|
|
99
|
-
: '1px solid var(--ggui-color-neutral-200, #e5e7eb)',
|
|
100
|
-
borderRadius: 'var(--ggui-shape-radius-sm, 4px)',
|
|
101
|
-
background: dark
|
|
102
|
-
? 'var(--ggui-color-neutral-50, #111827)'
|
|
103
|
-
: 'var(--ggui-color-neutral-50, #ffffff)',
|
|
104
|
-
color: dark
|
|
105
|
-
? 'var(--ggui-color-neutral-800, #e5e7eb)'
|
|
106
|
-
: 'var(--ggui-color-neutral-800, #1f2937)',
|
|
107
|
-
},
|
|
108
94
|
};
|
|
109
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Monochrome stroked thumb icons. `stroke="currentColor"` is the
|
|
98
|
+
* theme-applicability contract: the glyph takes whatever ink the
|
|
99
|
+
* button's `color` token resolves to, in either mode.
|
|
100
|
+
*/
|
|
101
|
+
function ThumbIcon({ direction, }) {
|
|
102
|
+
return (_jsx("svg", { width: 13, height: 13, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: direction === 'up' ? (_jsxs(_Fragment, { children: [_jsx("path", { d: "M7 10v12" }), _jsx("path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" })] })) : (_jsxs(_Fragment, { children: [_jsx("path", { d: "M17 14V2" }), _jsx("path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" })] })) }));
|
|
103
|
+
}
|
|
104
|
+
const VERDICT_LABELS = {
|
|
105
|
+
up: 'Thumbs up',
|
|
106
|
+
down: 'Thumbs down',
|
|
107
|
+
};
|
|
110
108
|
export function UiFeedbackCard({ emit, sessionId, toolName, }) {
|
|
111
109
|
const [phase, setPhase] = React.useState('idle');
|
|
112
|
-
const [comment, setComment] = React.useState('');
|
|
113
110
|
const scheme = useColorScheme();
|
|
114
111
|
const styles = buildStyles(scheme);
|
|
115
|
-
const send = React.useCallback((verdict
|
|
116
|
-
const trimmed = commentText?.trim();
|
|
112
|
+
const send = React.useCallback((verdict) => {
|
|
117
113
|
emit({
|
|
118
114
|
kind: 'ui-feedback',
|
|
119
115
|
verdict,
|
|
120
|
-
...(trimmed !== undefined && trimmed.length > 0 ? { comment: trimmed } : {}),
|
|
121
116
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
122
117
|
...(toolName !== undefined ? { toolName } : {}),
|
|
123
118
|
});
|
|
124
119
|
setPhase('sent');
|
|
125
120
|
}, [emit, sessionId, toolName]);
|
|
126
|
-
const onCommentSubmit = React.useCallback((e) => {
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
send('other', comment);
|
|
129
|
-
}, [send, comment]);
|
|
130
121
|
if (phase === 'dismissed')
|
|
131
122
|
return null;
|
|
132
|
-
return (_jsxs("div", { "data-ggui-ui-feedback": true, role: "group", "aria-label": "Share feedback on this UI", style: styles.root, children: [phase === 'sent' ? (_jsx("span", { "data-ggui-ui-feedback-thanks": true, children: "Thanks for the feedback" })) :
|
|
123
|
+
return (_jsxs("div", { "data-ggui-ui-feedback": true, role: "group", "aria-label": "Share feedback on this UI", style: styles.root, children: [phase === 'sent' ? (_jsx("span", { "data-ggui-ui-feedback-thanks": true, children: "Thanks for the feedback" })) : (['up', 'down'].map((verdict) => (_jsx("button", { type: "button", style: styles.verdictButton, "data-ggui-ui-feedback-verdict": verdict, "aria-label": VERDICT_LABELS[verdict], title: VERDICT_LABELS[verdict], onClick: () => send(verdict), children: _jsx(ThumbIcon, { direction: verdict }) }, verdict)))), _jsx("button", { type: "button", style: styles.dismissButton, "aria-label": "Dismiss feedback", "data-ggui-ui-feedback-dismiss": true, onClick: () => setPhase('dismissed'), children: '×' })] }));
|
|
133
124
|
}
|
|
@@ -15,6 +15,36 @@ import type { ObservabilityEmitter } from './observability.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const UI_FEEDBACK_CHROME_Z_INDEX = 2147483647;
|
|
17
17
|
export declare const ACTION_TOAST_Z_INDEX = 2147483646;
|
|
18
|
+
/** Inputs to the boot path's mount decision — see
|
|
19
|
+
* {@link shouldMountUiFeedbackChrome}. */
|
|
20
|
+
export interface UiFeedbackMountDecisionInput {
|
|
21
|
+
/**
|
|
22
|
+
* The opt-in knob from the boot options (#653). Feedback chrome is
|
|
23
|
+
* DEFAULT OFF: only an explicit `true` can mount it.
|
|
24
|
+
*/
|
|
25
|
+
readonly uiFeedback: boolean | undefined;
|
|
26
|
+
/** Whether the boot bound an `onObserve` emitter. */
|
|
27
|
+
readonly onObserveBound: boolean;
|
|
28
|
+
/** The MCP-Apps host's announced name, when known. */
|
|
29
|
+
readonly hostName: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pure mount decision the boot path consults before touching the DOM.
|
|
33
|
+
* Two independent gates compose:
|
|
34
|
+
*
|
|
35
|
+
* 1. **The #653 opt-in knob** — the founder's default-off ruling:
|
|
36
|
+
* generated-component cards carry NO feedback chrome unless the
|
|
37
|
+
* embedding deployment explicitly opts in (`uiFeedback: true` on
|
|
38
|
+
* the boot options). Absent or `false` ⇒ never mount, regardless
|
|
39
|
+
* of sink state.
|
|
40
|
+
* 2. **The #471 live-sink gate** — even opted in, only a parent that
|
|
41
|
+
* actually consumes the `ggui:observe` envelope gets the
|
|
42
|
+
* affordance: a bound `onObserve` emitter, or the first-party
|
|
43
|
+
* embed host. Third-party MCP-Apps hosts drop the envelope, and a
|
|
44
|
+
* feedback control that can never deliver feedback is worse than
|
|
45
|
+
* none.
|
|
46
|
+
*/
|
|
47
|
+
export declare function shouldMountUiFeedbackChrome(input: UiFeedbackMountDecisionInput): boolean;
|
|
18
48
|
export interface UiFeedbackChromeOptions {
|
|
19
49
|
/**
|
|
20
50
|
* Observability sink the card emits `ui-feedback` events into.
|
|
@@ -52,6 +52,28 @@ import { createElement } from 'react';
|
|
|
52
52
|
*/
|
|
53
53
|
export const UI_FEEDBACK_CHROME_Z_INDEX = 2147483647;
|
|
54
54
|
export const ACTION_TOAST_Z_INDEX = 2147483646;
|
|
55
|
+
/**
|
|
56
|
+
* Pure mount decision the boot path consults before touching the DOM.
|
|
57
|
+
* Two independent gates compose:
|
|
58
|
+
*
|
|
59
|
+
* 1. **The #653 opt-in knob** — the founder's default-off ruling:
|
|
60
|
+
* generated-component cards carry NO feedback chrome unless the
|
|
61
|
+
* embedding deployment explicitly opts in (`uiFeedback: true` on
|
|
62
|
+
* the boot options). Absent or `false` ⇒ never mount, regardless
|
|
63
|
+
* of sink state.
|
|
64
|
+
* 2. **The #471 live-sink gate** — even opted in, only a parent that
|
|
65
|
+
* actually consumes the `ggui:observe` envelope gets the
|
|
66
|
+
* affordance: a bound `onObserve` emitter, or the first-party
|
|
67
|
+
* embed host. Third-party MCP-Apps hosts drop the envelope, and a
|
|
68
|
+
* feedback control that can never deliver feedback is worse than
|
|
69
|
+
* none.
|
|
70
|
+
*/
|
|
71
|
+
export function shouldMountUiFeedbackChrome(input) {
|
|
72
|
+
if (input.uiFeedback !== true)
|
|
73
|
+
return false;
|
|
74
|
+
return (input.onObserveBound ||
|
|
75
|
+
input.hostName === 'ggui-iframe-runtime-embed-host');
|
|
76
|
+
}
|
|
55
77
|
/**
|
|
56
78
|
* Mount the in-iframe UI-feedback affordance, or do nothing.
|
|
57
79
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"react": "^19.0.0",
|
|
37
37
|
"react-dom": "^19.0.0",
|
|
38
38
|
"zod": "^4.3.6",
|
|
39
|
-
"@ggui-ai/design": "0.
|
|
40
|
-
"@ggui-ai/
|
|
41
|
-
"@ggui-ai/gadgets": "0.
|
|
42
|
-
"@ggui-ai/
|
|
43
|
-
"@ggui-ai/
|
|
44
|
-
"@ggui-ai/
|
|
39
|
+
"@ggui-ai/design": "0.13.0",
|
|
40
|
+
"@ggui-ai/preview-a2ui": "0.13.0",
|
|
41
|
+
"@ggui-ai/gadgets": "0.13.0",
|
|
42
|
+
"@ggui-ai/protocol": "0.13.0",
|
|
43
|
+
"@ggui-ai/wire": "0.13.0",
|
|
44
|
+
"@ggui-ai/live-channel": "0.13.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.27.0"
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"author": "ggui contributors <hello@ggui.ai>",
|
|
77
77
|
"scripts": {
|
|
78
|
-
"build": "rm -rf dist && node esbuild.config.mjs &&
|
|
78
|
+
"build": "rm -rf dist.staging && tsc -p tsconfig.types.json --outDir dist.staging && GGUI_ESBUILD_OUT_DIR=dist.staging node esbuild.config.mjs && node ../scripts/atomic-swap.mjs dist.staging dist && pnpm run check:bundle-size",
|
|
79
79
|
"check:bundle-size": "tsx scripts/check-bundle-size.ts",
|
|
80
80
|
"check:bundle-size:self-test": "tsx scripts/check-bundle-size.ts --self-test",
|
|
81
81
|
"typecheck": "tsc --noEmit",
|