@ggui-ai/iframe-runtime 0.12.0 → 0.14.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/connection.d.ts +20 -0
- package/dist/connection.js +20 -0
- package/dist/iframe-runtime.js +93 -91
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/observability.d.ts +129 -21
- package/dist/observability.js +59 -0
- package/dist/react-renderer.d.ts +2 -5
- package/dist/react-renderer.js +20 -4
- package/dist/render-item.d.ts +2 -6
- package/dist/runtime.d.ts +31 -0
- package/dist/runtime.js +266 -65
- 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/dist/wire-config.js +9 -3
- package/package.json +15 -10
|
@@ -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/dist/wire-config.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { buildWireConfig, StreamBus, } from '@ggui-ai/wire';
|
|
2
2
|
import { validateOutboundActionEnvelope } from './validation.js';
|
|
3
3
|
import { defaultProtocolErrorEmitter, fromClientContractViolation, } from './protocol-error.js';
|
|
4
|
-
// `StreamBus` (and its bounded reserved-channel replay ring)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// `StreamBus` (and its bounded reserved-channel replay ring) lives in
|
|
5
|
+
// `@ggui-ai/wire` (one implementation for every first-party renderer).
|
|
6
|
+
// It was originally hoisted to share with `<GguiRender>` — that
|
|
7
|
+
// component and its WS action path are DELETED (react host-helper
|
|
8
|
+
// ruling; ggui#599 cycle-2 caught the stale reference here) — and the
|
|
9
|
+
// hoist stands on its own merits. Re-exported for internal modules.
|
|
7
10
|
export { StreamBus };
|
|
8
11
|
/**
|
|
9
12
|
* Build the per-render `WireConfig` for the iframe runtime.
|
|
@@ -44,6 +47,9 @@ export function buildRootWireConfig(opts) {
|
|
|
44
47
|
}
|
|
45
48
|
return buildWireConfig({
|
|
46
49
|
app: { appId: opts.appId, appName: opts.appId },
|
|
50
|
+
// `isConnected` here is the static config field; `useRender()`
|
|
51
|
+
// reads the LIVE value from wire's document connection store,
|
|
52
|
+
// which the runtime writes at the relay latch's edges (ggui#670).
|
|
47
53
|
render: { sessionId: opts.sessionId, isConnected: true },
|
|
48
54
|
auth: { isAuthenticated: false },
|
|
49
55
|
// Resolve the active render's actionSpec on every dispatch.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"types": "./dist/server.d.ts",
|
|
29
29
|
"import": "./dist/server.js",
|
|
30
30
|
"default": "./dist/server.js"
|
|
31
|
+
},
|
|
32
|
+
"./observability": {
|
|
33
|
+
"types": "./dist/observability.d.ts",
|
|
34
|
+
"import": "./dist/observability.js",
|
|
35
|
+
"default": "./dist/observability.js"
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
@@ -36,12 +41,12 @@
|
|
|
36
41
|
"react": "^19.0.0",
|
|
37
42
|
"react-dom": "^19.0.0",
|
|
38
43
|
"zod": "^4.3.6",
|
|
39
|
-
"@ggui-ai/
|
|
40
|
-
"@ggui-ai/
|
|
41
|
-
"@ggui-ai/
|
|
42
|
-
"@ggui-ai/
|
|
43
|
-
"@ggui-ai/
|
|
44
|
-
"@ggui-ai/wire": "0.
|
|
44
|
+
"@ggui-ai/gadgets": "0.14.0",
|
|
45
|
+
"@ggui-ai/preview-a2ui": "0.14.0",
|
|
46
|
+
"@ggui-ai/live-channel": "0.14.0",
|
|
47
|
+
"@ggui-ai/protocol": "0.14.0",
|
|
48
|
+
"@ggui-ai/design": "0.14.0",
|
|
49
|
+
"@ggui-ai/wire": "0.14.0"
|
|
45
50
|
},
|
|
46
51
|
"peerDependencies": {
|
|
47
52
|
"@modelcontextprotocol/sdk": "^1.27.0"
|
|
@@ -52,7 +57,7 @@
|
|
|
52
57
|
"@types/node": "^24.0.0",
|
|
53
58
|
"@types/react": "^19.0.0",
|
|
54
59
|
"@types/react-dom": "^19.0.0",
|
|
55
|
-
"esbuild": "^0.
|
|
60
|
+
"esbuild": "^0.28.2",
|
|
56
61
|
"jsdom": "^24.0.0",
|
|
57
62
|
"tsx": "^4.21.0",
|
|
58
63
|
"typescript": "^5.0.0",
|
|
@@ -71,11 +76,11 @@
|
|
|
71
76
|
"access": "public"
|
|
72
77
|
},
|
|
73
78
|
"engines": {
|
|
74
|
-
"node": ">=
|
|
79
|
+
"node": ">=22.0.0"
|
|
75
80
|
},
|
|
76
81
|
"author": "ggui contributors <hello@ggui.ai>",
|
|
77
82
|
"scripts": {
|
|
78
|
-
"build": "rm -rf dist && node esbuild.config.mjs &&
|
|
83
|
+
"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
84
|
"check:bundle-size": "tsx scripts/check-bundle-size.ts",
|
|
80
85
|
"check:bundle-size:self-test": "tsx scripts/check-bundle-size.ts --self-test",
|
|
81
86
|
"typecheck": "tsc --noEmit",
|