@ggui-ai/iframe-runtime 0.11.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.
@@ -36,6 +36,44 @@
36
36
  * gate passes.
37
37
  */
38
38
  import { createElement } from 'react';
39
+ /**
40
+ * Stacking contract between the two body-appended runtime layers
41
+ * (ggui#603): the feedback chrome must sit strictly ABOVE the action
42
+ * toast. The persistent `action_required` toast parks
43
+ * `pointer-events:auto` at fixed bottom-center — the same region this
44
+ * chrome occupies — and is re-armed per gesture; without this order an
45
+ * armed toast eats every tap aimed at the feedback controls. The toast
46
+ * remains dismissable on its own uncovered area (it accepts the click
47
+ * anywhere on itself), so partial occlusion costs it nothing.
48
+ *
49
+ * Single source: `runtime.ts`'s toast reads {@link ACTION_TOAST_Z_INDEX};
50
+ * this module applies {@link UI_FEEDBACK_CHROME_Z_INDEX} to the chrome
51
+ * container. The test suite pins the relationship, not the numbers.
52
+ */
53
+ export const UI_FEEDBACK_CHROME_Z_INDEX = 2147483647;
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
+ }
39
77
  /**
40
78
  * Mount the in-iframe UI-feedback affordance, or do nothing.
41
79
  *
@@ -69,6 +107,10 @@ export async function mountUiFeedbackChrome(doc, opts) {
69
107
  doc.querySelector('[data-ggui-ui-feedback-chrome]')?.remove();
70
108
  const container = doc.createElement('div');
71
109
  container.setAttribute('data-ggui-ui-feedback-chrome', '');
110
+ // Win the stacking race against the action toast (ggui#603 — see
111
+ // the constants' docblock above).
112
+ container.style.position = 'relative';
113
+ container.style.zIndex = String(UI_FEEDBACK_CHROME_Z_INDEX);
72
114
  doc.body.appendChild(container);
73
115
  const root = reactDomClient.createRoot(container);
74
116
  root.render(createElement(cardMod.UiFeedbackCard, {
@@ -95,9 +95,13 @@ export interface BuildRootWireConfigOptions {
95
95
  *
96
96
  * The default WS-frame send reaches the agent only on servers whose
97
97
  * live channel bridges WS `data:submit` actions onto the same
98
- * pending-events pipe (first-party `createGguiServer` does — see
99
- * `GguiSessionChannelOptions.pendingEventConsumer`); it remains the
100
- * seam tests and direct-WS callers exercise.
98
+ * pending-events pipe. First-party `createGguiServer` bridges it
99
+ * ONLY when composed with DEFAULT handlers (`opts.handlers`
100
+ * undefined); a custom `handlers` list drains its own pipe, so the
101
+ * channel deliberately does not bridge — the hosted pod is such a
102
+ * caller, and on it a WS gesture lands on the retained ledger only.
103
+ * See `GguiSessionChannelOptions.pendingEventConsumer`. The default
104
+ * send remains the seam tests and direct-WS callers exercise.
101
105
  *
102
106
  * Called AFTER outbound validation passes. Receives the validated
103
107
  * {@link ActionEnvelope}; caller extracts payload.action +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/iframe-runtime",
3
- "version": "0.11.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/preview-a2ui": "0.11.0",
40
- "@ggui-ai/live-channel": "0.11.0",
41
- "@ggui-ai/gadgets": "0.11.0",
42
- "@ggui-ai/design": "0.11.0",
43
- "@ggui-ai/protocol": "0.11.0",
44
- "@ggui-ai/wire": "0.11.0"
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 && tsc -p tsconfig.types.json && pnpm run check:bundle-size",
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",