@ggui-ai/iframe-runtime 0.1.0-rc.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.
Files changed (92) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +33 -0
  3. package/dist/bootstrap.d.ts +72 -0
  4. package/dist/bootstrap.js +500 -0
  5. package/dist/canvas/animator/animator-host.d.ts +63 -0
  6. package/dist/canvas/animator/animator-host.js +105 -0
  7. package/dist/canvas/animator/animator-pill.d.ts +42 -0
  8. package/dist/canvas/animator/animator-pill.js +119 -0
  9. package/dist/canvas/animator/state-machine.d.ts +106 -0
  10. package/dist/canvas/animator/state-machine.js +150 -0
  11. package/dist/canvas/canvas-shell.d.ts +77 -0
  12. package/dist/canvas/canvas-shell.js +117 -0
  13. package/dist/canvas/display-mode-policy.d.ts +104 -0
  14. package/dist/canvas/display-mode-policy.js +98 -0
  15. package/dist/canvas/mount.d.ts +150 -0
  16. package/dist/canvas/mount.js +202 -0
  17. package/dist/canvas/nav-stack.d.ts +133 -0
  18. package/dist/canvas/nav-stack.js +172 -0
  19. package/dist/channel-transport.d.ts +182 -0
  20. package/dist/channel-transport.js +344 -0
  21. package/dist/channels/channel-payload.d.ts +20 -0
  22. package/dist/channels/channel-payload.js +34 -0
  23. package/dist/channels/data.d.ts +48 -0
  24. package/dist/channels/data.js +103 -0
  25. package/dist/channels/drain-ack.d.ts +13 -0
  26. package/dist/channels/drain-ack.js +15 -0
  27. package/dist/channels/feedback.d.ts +14 -0
  28. package/dist/channels/feedback.js +19 -0
  29. package/dist/channels/index.d.ts +7 -0
  30. package/dist/channels/index.js +7 -0
  31. package/dist/channels/props-update.d.ts +53 -0
  32. package/dist/channels/props-update.js +130 -0
  33. package/dist/channels/push.d.ts +47 -0
  34. package/dist/channels/push.js +54 -0
  35. package/dist/channels/system.d.ts +26 -0
  36. package/dist/channels/system.js +44 -0
  37. package/dist/compiled-validators.d.ts +40 -0
  38. package/dist/compiled-validators.js +74 -0
  39. package/dist/context-observer.d.ts +205 -0
  40. package/dist/context-observer.js +312 -0
  41. package/dist/gadget-loader.d.ts +118 -0
  42. package/dist/gadget-loader.js +136 -0
  43. package/dist/globals.d.ts +234 -0
  44. package/dist/globals.js +101 -0
  45. package/dist/host-context-emitter.d.ts +91 -0
  46. package/dist/host-context-emitter.js +213 -0
  47. package/dist/iframe-runtime.js +261 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +4 -0
  50. package/dist/lifecycle.d.ts +66 -0
  51. package/dist/lifecycle.js +49 -0
  52. package/dist/mcp-app-iframe-host.d.ts +69 -0
  53. package/dist/mcp-app-iframe-host.js +214 -0
  54. package/dist/observability.d.ts +225 -0
  55. package/dist/observability.js +28 -0
  56. package/dist/protocol-error.d.ts +199 -0
  57. package/dist/protocol-error.js +116 -0
  58. package/dist/provisional-renderer.d.ts +62 -0
  59. package/dist/provisional-renderer.js +323 -0
  60. package/dist/react-renderer.d.ts +95 -0
  61. package/dist/react-renderer.js +343 -0
  62. package/dist/registry-subscribe.d.ts +102 -0
  63. package/dist/registry-subscribe.js +319 -0
  64. package/dist/runtime.d.ts +531 -0
  65. package/dist/runtime.js +2623 -0
  66. package/dist/server.d.ts +36 -0
  67. package/dist/server.js +72 -0
  68. package/dist/stack-item-renderer.d.ts +103 -0
  69. package/dist/stack-item-renderer.js +270 -0
  70. package/dist/stack.d.ts +90 -0
  71. package/dist/stack.js +118 -0
  72. package/dist/status-dom.d.ts +72 -0
  73. package/dist/status-dom.js +51 -0
  74. package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
  75. package/dist/system-cards/NoCredentialsCard.js +189 -0
  76. package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
  77. package/dist/system-cards/ProtocolProbeCard.js +463 -0
  78. package/dist/system-cards/QrCode.d.ts +46 -0
  79. package/dist/system-cards/QrCode.js +55 -0
  80. package/dist/system-cards/host-detect.d.ts +31 -0
  81. package/dist/system-cards/host-detect.js +108 -0
  82. package/dist/system-cards/host-intents.d.ts +85 -0
  83. package/dist/system-cards/host-intents.js +170 -0
  84. package/dist/system-cards/index.d.ts +70 -0
  85. package/dist/system-cards/index.js +134 -0
  86. package/dist/types.d.ts +69 -0
  87. package/dist/types.js +3 -0
  88. package/dist/validation.d.ts +83 -0
  89. package/dist/validation.js +106 -0
  90. package/dist/wire-config.d.ts +214 -0
  91. package/dist/wire-config.js +290 -0
  92. package/package.json +81 -0
package/dist/stack.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * In-memory model of the visible stack. Owned by the runtime; mutated
3
+ * via the methods (no setter — keeps the "model is the truth" doctrine
4
+ * obvious in code reviews).
5
+ */
6
+ export class StackModel {
7
+ items = [];
8
+ filterToItemId;
9
+ constructor(options = {}) {
10
+ this.filterToItemId = options.filterToItemId;
11
+ }
12
+ /**
13
+ * Replace the entire stack — used after the subscribe ack lands.
14
+ *
15
+ * Under `filterToItemId` (single-item mode), the incoming list is
16
+ * filtered to just the matching entry. If no entry matches, the
17
+ * model ends up empty — the server no longer includes the pinned
18
+ * item and the renderer's empty-state is the correct surface.
19
+ */
20
+ setAll(items) {
21
+ if (this.filterToItemId !== undefined) {
22
+ const target = this.filterToItemId;
23
+ this.items = items.filter((item) => item.id === target);
24
+ return;
25
+ }
26
+ this.items = [...items];
27
+ }
28
+ /**
29
+ * Upsert a stack item. If an entry with the same id already exists
30
+ * its slot is replaced in place (preserves ordering); otherwise the
31
+ * item is appended at the tail. Mirrors `GguiSession.tsx`'s push
32
+ * reducer.
33
+ *
34
+ * Under `filterToItemId` (single-item mode): entries whose id does
35
+ * not match the filter are ignored. The renderer's live-channel
36
+ * subscription still delivers every push for the session (subscribe
37
+ * remains session-scoped); this filter is the client-side pin that
38
+ * keeps the per-card iframe bound to its one stack entry.
39
+ */
40
+ upsert(item) {
41
+ if (this.filterToItemId !== undefined && item.id !== this.filterToItemId) {
42
+ return;
43
+ }
44
+ const idx = this.items.findIndex((existing) => existing.id === item.id);
45
+ if (idx >= 0) {
46
+ const next = [...this.items];
47
+ next[idx] = item;
48
+ this.items = next;
49
+ return;
50
+ }
51
+ this.items = [...this.items, item];
52
+ }
53
+ /** Read-only view — callers MUST NOT mutate. */
54
+ snapshot() {
55
+ return this.items;
56
+ }
57
+ /** Item count — convenience for status-line rendering. */
58
+ size() {
59
+ return this.items.length;
60
+ }
61
+ }
62
+ /**
63
+ * Best-effort textual label for a stack item — title-ish for the
64
+ * placeholder UI. Tries `description` → `prompt` → a generic fallback.
65
+ *
66
+ * The wire shape keeps these fields optional on every entry kind
67
+ * (generated, McpApps), so the absence-handling has to be honest.
68
+ */
69
+ function labelFor(item) {
70
+ if (typeof item.description === 'string' && item.description.length > 0) {
71
+ return item.description;
72
+ }
73
+ // Only generated component items carry `prompt`. McpApps + system
74
+ // variants surface their own description (or a generic fallback).
75
+ if (item.type !== 'mcpApps' && item.type !== 'system') {
76
+ if (typeof item.prompt === 'string' && item.prompt.length > 0) {
77
+ return item.prompt;
78
+ }
79
+ }
80
+ return '(untitled stack item)';
81
+ }
82
+ /**
83
+ * Render the stack model into the target list element as structural
84
+ * `<li data-ggui-stack-item="<id>">` placeholders. Idempotent — every
85
+ * call replaces the element's children based on the current model.
86
+ *
87
+ * The DOM shape (data attributes + classnames) is intentionally
88
+ * stable: E2E tests and the React-mount path both need to address
89
+ * stack-item rows by `data-ggui-stack-item` selector.
90
+ */
91
+ export function renderStackInto(target, model) {
92
+ // Replace children rather than mutate-in-place — the placeholder
93
+ // doesn't need diffing, and the wire-frame burst rate is low enough
94
+ // (push events arrive roughly per agent turn) that full-replace is
95
+ // cheap.
96
+ target.replaceChildren();
97
+ const items = model.snapshot();
98
+ if (items.length === 0) {
99
+ const empty = target.ownerDocument.createElement('li');
100
+ empty.textContent = '(no stack items yet)';
101
+ empty.setAttribute('data-ggui-empty', 'true');
102
+ target.appendChild(empty);
103
+ return;
104
+ }
105
+ for (const item of items) {
106
+ const li = target.ownerDocument.createElement('li');
107
+ li.setAttribute('data-ggui-stack-item', item.id);
108
+ const idEl = target.ownerDocument.createElement('span');
109
+ idEl.className = 'id';
110
+ idEl.textContent = item.id;
111
+ li.appendChild(idEl);
112
+ const labelEl = target.ownerDocument.createElement('span');
113
+ labelEl.className = 'label';
114
+ labelEl.textContent = labelFor(item);
115
+ li.appendChild(labelEl);
116
+ target.appendChild(li);
117
+ }
118
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * In-iframe status DOM. Extracted from `runtime.ts` in B3b so the
3
+ * channel-client handlers can import the `setStatus` + status-refs
4
+ * helpers without a circular dep on the runtime module (which imports
5
+ * the handlers for registration at boot).
6
+ *
7
+ * Operators see the boot state at a glance; failures land here with a
8
+ * prefix that makes them grep-able in dev consoles.
9
+ *
10
+ * The status DOM is a `<div data-ggui-status>` + `<ul data-ggui-stack>`
11
+ * pair built once at boot and reused for the iframe's lifetime. The
12
+ * `data-ggui-status` attribute carries a {@link StatusKind} value so
13
+ * operator stylesheets + e2e specs can pin assertions against the
14
+ * narrow union rather than reading the text content.
15
+ */
16
+ import type { StackModel } from './stack.js';
17
+ /**
18
+ * Closed union of values the `data-ggui-status` attribute carries.
19
+ *
20
+ * - `idle` — initial state before any boot IO. Default text reads
21
+ * "Initializing renderer…"; set by {@link ensureStatusDom}.
22
+ * - `initializing` — same as `idle` (legacy alias kept for the rare
23
+ * external CSS hook in `e2e/`).
24
+ * - `connecting` — ui/initialize fired or WS handshake in flight.
25
+ * - `connected` — first ack landed + every subsequent successful
26
+ * push. Text shows the live stack count.
27
+ * - `reconnecting` — WS dropped; the reconnect ladder is running.
28
+ * - `disconnected` — terminal close + ladder exhausted, or a graceful
29
+ * stop. Authors may layer reload UX here.
30
+ * - `upgrade-required` — version handshake failed; operator should
31
+ * upgrade the client.
32
+ * - `error` — every other failure (ui/initialize, bootstrap parse,
33
+ * WS handshake non-version).
34
+ *
35
+ * Closed at this layer; new values require a code change here PLUS
36
+ * any e2e specs / CSS that pattern-match against the attribute.
37
+ */
38
+ export type StatusKind = 'idle' | 'initializing' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'upgrade-required' | 'error';
39
+ /**
40
+ * Refs handed back from {@link ensureStatusDom}. Stays narrow (two
41
+ * elements) so the channel handlers can close over them without
42
+ * pulling other runtime state.
43
+ */
44
+ export interface StatusRefs {
45
+ readonly status: HTMLElement;
46
+ readonly stack: HTMLElement;
47
+ }
48
+ /**
49
+ * Build the minimum viable status DOM the runtime owns. Adopters can
50
+ * style via the `data-ggui-status` / `data-ggui-stack` attributes; the
51
+ * bundle ships zero CSS so hosts see only the structure.
52
+ */
53
+ export declare function ensureStatusDom(doc: Document): StatusRefs;
54
+ /**
55
+ * Set the status text + the `data-ggui-status` attribute. Centralised
56
+ * so every caller stays consistent across the runtime + the channel
57
+ * handlers (push handler keeps the count fresh on every fold).
58
+ */
59
+ export declare function setStatus(refs: StatusRefs, text: string, state: StatusKind): void;
60
+ /**
61
+ * Convenience — render the `connected (N items)` status line based on
62
+ * the current stack model size. Used by the push handler on every
63
+ * successful upsert + by the boot sequence on first ack.
64
+ */
65
+ export declare function setConnectedStatus(refs: StatusRefs, model: StackModel): void;
66
+ /**
67
+ * Re-render the stack DOM into the placeholder element. Wraps the
68
+ * standalone {@link renderStackInto} so callers can pass refs + model
69
+ * without unpacking; the standalone export stays for the legacy
70
+ * callers that have only the bare element in scope.
71
+ */
72
+ export declare function refreshStackDom(refs: StatusRefs, model: StackModel): void;
@@ -0,0 +1,51 @@
1
+ import { renderStackInto } from './stack.js';
2
+ /**
3
+ * Build the minimum viable status DOM the runtime owns. Adopters can
4
+ * style via the `data-ggui-status` / `data-ggui-stack` attributes; the
5
+ * bundle ships zero CSS so hosts see only the structure.
6
+ */
7
+ export function ensureStatusDom(doc) {
8
+ // Reuse pre-existing nodes if the thin shell wrote them — lets the
9
+ // shell inline a status line if it wants to.
10
+ const existingStatus = doc.querySelector('[data-ggui-status]');
11
+ const existingStack = doc.querySelector('[data-ggui-stack]');
12
+ if (existingStatus !== null && existingStack !== null) {
13
+ return { status: existingStatus, stack: existingStack };
14
+ }
15
+ const root = doc.body;
16
+ const status = doc.createElement('div');
17
+ status.setAttribute('data-ggui-status', 'idle');
18
+ status.textContent = 'Initializing renderer…';
19
+ const stack = doc.createElement('ul');
20
+ stack.setAttribute('data-ggui-stack', '');
21
+ root.appendChild(status);
22
+ root.appendChild(stack);
23
+ return { status, stack };
24
+ }
25
+ /**
26
+ * Set the status text + the `data-ggui-status` attribute. Centralised
27
+ * so every caller stays consistent across the runtime + the channel
28
+ * handlers (push handler keeps the count fresh on every fold).
29
+ */
30
+ export function setStatus(refs, text, state) {
31
+ refs.status.textContent = text;
32
+ refs.status.setAttribute('data-ggui-status', state);
33
+ }
34
+ /**
35
+ * Convenience — render the `connected (N items)` status line based on
36
+ * the current stack model size. Used by the push handler on every
37
+ * successful upsert + by the boot sequence on first ack.
38
+ */
39
+ export function setConnectedStatus(refs, model) {
40
+ const size = model.size();
41
+ setStatus(refs, `Connected (${size} item${size === 1 ? '' : 's'}).`, 'connected');
42
+ }
43
+ /**
44
+ * Re-render the stack DOM into the placeholder element. Wraps the
45
+ * standalone {@link renderStackInto} so callers can pass refs + model
46
+ * without unpacking; the standalone export stays for the legacy
47
+ * callers that have only the bare element in scope.
48
+ */
49
+ export function refreshStackDom(refs, model) {
50
+ renderStackInto(refs.stack, model);
51
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * No-credentials system card.
3
+ *
4
+ * Rendered when the server processed a `ggui_push` for a session whose
5
+ * LLM credentials are missing or unresolvable. The user needs to open
6
+ * the server's `/settings` page in a real browser tab to paste an API
7
+ * key.
8
+ *
9
+ * Open-url strategy is delegated to {@link requestOpenUrl} — fires
10
+ * `window.open`, programmatic anchor click, `ui/request/open-url`
11
+ * postMessage, and `tools/call:open_url` postMessage in parallel,
12
+ * with `clipboard.writeText` as the guaranteed-floor fallback. The
13
+ * card observes the `{outcome, mechanism}` result and shows distinct
14
+ * copy for opened / copied / unsupported so the user understands
15
+ * what happened. Hosts that block popups (claude.ai's
16
+ * `claudemcpcontent.com` iframe sandbox) gracefully degrade to the
17
+ * clipboard path; the URL block's `user-select: all` covers the
18
+ * worst case.
19
+ */
20
+ import * as React from 'react';
21
+ export interface NoCredentialsCardProps {
22
+ /**
23
+ * Absolute URL of the server's settings page. Constructed at boot
24
+ * from `--public-base-url` (or falls back to `host:port`). MUST be
25
+ * absolute — host iframes resolve relative URLs against their own
26
+ * sandbox origin (e.g. `claudemcpcontent.com`), where `/settings`
27
+ * goes to a 404.
28
+ */
29
+ readonly settingsUrl: string;
30
+ /** Optional: the original prompt that triggered the gen, surfaced as a footnote. */
31
+ readonly intent?: string;
32
+ }
33
+ export declare function NoCredentialsCard({ settingsUrl, intent, }: NoCredentialsCardProps): React.JSX.Element;
@@ -0,0 +1,189 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * No-credentials system card.
4
+ *
5
+ * Rendered when the server processed a `ggui_push` for a session whose
6
+ * LLM credentials are missing or unresolvable. The user needs to open
7
+ * the server's `/settings` page in a real browser tab to paste an API
8
+ * key.
9
+ *
10
+ * Open-url strategy is delegated to {@link requestOpenUrl} — fires
11
+ * `window.open`, programmatic anchor click, `ui/request/open-url`
12
+ * postMessage, and `tools/call:open_url` postMessage in parallel,
13
+ * with `clipboard.writeText` as the guaranteed-floor fallback. The
14
+ * card observes the `{outcome, mechanism}` result and shows distinct
15
+ * copy for opened / copied / unsupported so the user understands
16
+ * what happened. Hosts that block popups (claude.ai's
17
+ * `claudemcpcontent.com` iframe sandbox) gracefully degrade to the
18
+ * clipboard path; the URL block's `user-select: all` covers the
19
+ * worst case.
20
+ */
21
+ import * as React from 'react';
22
+ import { Card, Stack, Heading, Text, Button, Badge, } from '@ggui-ai/design/primitives';
23
+ import { requestOpenUrl } from './host-intents.js';
24
+ import { QrCode } from './QrCode.js';
25
+ const BUILD_ID = typeof __GGUI_RUNTIME_BUILD_ID__ === 'string'
26
+ ? __GGUI_RUNTIME_BUILD_ID__
27
+ : 'dev';
28
+ /**
29
+ * ggui wordmark — `g g u i` in the brand's two-tone alternation.
30
+ * Inlined SVG (24px tall) so the bundle picks up no asset dependency.
31
+ * Colors track theme tokens: `chrome` ≈ surface, `ink` ≈ onSurface.
32
+ * Each glyph sits in a 50×50 box with 8px gaps.
33
+ */
34
+ function GguiWordmark() {
35
+ // `text-primary` flips dark↔light per theme mode (charcoal in light,
36
+ // off-white in dark). `gray-300` flips too — the gray scale in
37
+ // `darkTheme` is intentionally inverted, so the wordmark's
38
+ // contrasting "chrome" tone reads correctly under either theme.
39
+ const ink = 'var(--ggui-color-onSurface, #292929)';
40
+ const chrome = 'var(--ggui-color-neutral-300, #d1d5db)';
41
+ return (_jsxs("svg", { viewBox: "0 0 224 50", width: "72", height: "16", role: "img", "aria-label": "ggui", style: { display: 'block', flexShrink: 0 }, children: [_jsx("path", { d: "M 0 0 H 50 V 25 H 25 V 50 H 0 Z", fill: chrome }), _jsx("rect", { x: "33", y: "33", width: "17", height: "17", fill: ink }), _jsx("path", { d: "M 58 0 H 108 V 25 H 83 V 50 H 58 Z", fill: ink }), _jsx("rect", { x: "91", y: "33", width: "17", height: "17", fill: chrome }), _jsx("path", { d: "M 141 50 C 154.807 50 166 38.8071 166 25 V 0 H 116 V 25 C 116 38.8071 127.193 50 141 50 Z", fill: ink }), _jsx("rect", { x: "174", y: "0", width: "50", height: "50", fill: chrome })] }));
42
+ }
43
+ const OUTCOME_TTL_MS = 4000;
44
+ export function NoCredentialsCard({ settingsUrl, intent, }) {
45
+ const [ctaState, setCtaState] = React.useState('idle');
46
+ const onPrimary = React.useCallback(() => {
47
+ // Diagnostic logging — surfaces in the iframe's devtools console
48
+ // (claude.ai web: "Open frame in new tab" then DevTools; desktop:
49
+ // sandboxed and console-less, so we ALSO surface state in the
50
+ // CTA label). The `[ggui:no-creds-card]` prefix makes these
51
+ // greppable.
52
+ if (typeof console !== 'undefined') {
53
+ // eslint-disable-next-line no-console -- diagnostic trace, see helper docstring
54
+ console.log('[ggui:no-creds-card] click → settingsUrl =', settingsUrl);
55
+ }
56
+ setCtaState('working');
57
+ void requestOpenUrl(settingsUrl).then((result) => {
58
+ if (typeof console !== 'undefined') {
59
+ // eslint-disable-next-line no-console -- diagnostic trace, see helper docstring
60
+ console.log('[ggui:no-creds-card] requestOpenUrl resolved →', result);
61
+ }
62
+ const next = result.outcome === 'opened'
63
+ ? 'opened'
64
+ : result.outcome === 'copied'
65
+ ? 'copied'
66
+ : 'manual';
67
+ setCtaState(next);
68
+ if (typeof window !== 'undefined') {
69
+ window.setTimeout(() => setCtaState('idle'), OUTCOME_TTL_MS);
70
+ }
71
+ });
72
+ }, [settingsUrl]);
73
+ const ctaLabel = ctaState === 'working'
74
+ ? 'Opening…'
75
+ : ctaState === 'opened'
76
+ ? 'Opened in browser ✓'
77
+ : ctaState === 'copied'
78
+ ? 'URL copied — paste in your browser'
79
+ : ctaState === 'manual'
80
+ ? 'Tap the URL above to copy'
81
+ : 'Open settings';
82
+ const ctaDisabled = ctaState === 'working';
83
+ // Color tokens that auto-flip between light + dark mode. Only the
84
+ // tokens defined in BOTH `lightTheme` and `darkTheme` are safe to
85
+ // use without fallback divergence — `surface`, `text-primary`,
86
+ // `text-secondary`, and the gray scale (which is intentionally
87
+ // INVERTED in darkTheme so `gray-100` is light-on-dark when in
88
+ // dark mode and dark-on-light when in light mode).
89
+ //
90
+ // - `--ggui-color-surface` — opaque card surface (white in light,
91
+ // slate-800 in dark). Used for the URL block + QR backdrop.
92
+ // - `--ggui-color-onSurface` — high-contrast body text.
93
+ // - `--ggui-color-onSurfaceVariant` — muted captions/headings.
94
+ // - `--ggui-color-neutral-100` — subtle tinted background that
95
+ // auto-flips. Used for the inner "details" panel — slightly
96
+ // differentiated from outer transparent canvas without being
97
+ // a hard surface.
98
+ // - `--ggui-color-neutral-200` — subtle outline that auto-flips.
99
+ return (
100
+ // Outer Card is forced transparent — when this card renders inside
101
+ // an MCP App iframe (claude.ai web, Claude Desktop), the host
102
+ // already draws its own chat-bubble surface. A second opaque
103
+ // background creates a card-on-card look. Padding stays so content
104
+ // doesn't crowd the iframe edges. Outside Claude (preview iframe,
105
+ // console inspector), the ThemeProvider's body background is the
106
+ // theme's `--ggui-color-surface`, so a transparent card just
107
+ // shows the theme's canvas behind it — also fine.
108
+ _jsx(Card, { padding: "lg", border: false, shadow: "none", style: { backgroundColor: 'transparent' }, children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "md", children: [_jsxs("div", { style: {
109
+ display: 'flex',
110
+ alignItems: 'center',
111
+ justifyContent: 'space-between',
112
+ gap: 'var(--ggui-spacing-3, 12px)',
113
+ }, children: [_jsx(GguiWordmark, {}), _jsx(Badge, { variant: "info", children: "Setup needed" })] }), _jsxs("div", { style: {
114
+ display: 'flex',
115
+ alignItems: 'baseline',
116
+ gap: 'var(--ggui-spacing-3, 12px)',
117
+ flexWrap: 'wrap',
118
+ }, children: [_jsx(Heading, { level: 3, children: "Connect an LLM provider" }), _jsx("code", { style: {
119
+ fontFamily: 'var(--ggui-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace)',
120
+ fontSize: 'var(--ggui-font-size-xs, 11px)',
121
+ color: 'var(--ggui-color-onSurfaceVariant, #71717a)',
122
+ opacity: 0.7,
123
+ userSelect: 'all',
124
+ }, title: "iframe-runtime build identifier \u2014 useful for diagnosing stale caches", children: BUILD_ID })] }), _jsx(Text, { style: {
125
+ color: 'var(--ggui-color-onSurfaceVariant, #52525b)',
126
+ lineHeight: '1.55',
127
+ }, children: "ggui needs an LLM key to render UIs. Open this server\u2019s settings page to paste an Anthropic, OpenAI, Google, or OpenRouter API key." })] }), _jsx("div", { style: {
128
+ display: 'flex',
129
+ flexDirection: 'column',
130
+ gap: 'var(--ggui-spacing-3, 12px)',
131
+ padding: 'var(--ggui-spacing-4, 16px)',
132
+ background: 'var(--ggui-color-neutral-100, #f4f4f5)',
133
+ border: '1px solid var(--ggui-color-neutral-200, #e4e4e7)',
134
+ borderRadius: 'var(--ggui-shape-radius-md, 10px)',
135
+ }, children: _jsxs("div", { style: {
136
+ display: 'flex',
137
+ gap: 'var(--ggui-spacing-4, 16px)',
138
+ alignItems: 'flex-start',
139
+ flexWrap: 'wrap',
140
+ }, children: [_jsxs("div", { style: {
141
+ display: 'flex',
142
+ flexDirection: 'column',
143
+ alignItems: 'center',
144
+ gap: 'var(--ggui-spacing-2, 8px)',
145
+ }, children: [_jsx("div", { style: {
146
+ padding: 'var(--ggui-spacing-2, 8px)',
147
+ background: '#ffffff',
148
+ borderRadius: 'var(--ggui-shape-radius-sm, 6px)',
149
+ lineHeight: 0,
150
+ }, children: _jsx(QrCode, { value: settingsUrl, size: 120, foreground: "#000000", background: "#ffffff" }) }), _jsx(Text, { variant: "caption", style: {
151
+ color: 'var(--ggui-color-onSurfaceVariant, #52525b)',
152
+ fontSize: 'var(--ggui-font-size-xs, 11px)',
153
+ textAlign: 'center',
154
+ }, children: "Scan with your phone" })] }), _jsxs("div", { style: {
155
+ display: 'flex',
156
+ flexDirection: 'column',
157
+ gap: 'var(--ggui-spacing-2, 8px)',
158
+ flex: '1 1 240px',
159
+ minWidth: '200px',
160
+ }, children: [_jsx(Text, { variant: "caption", style: {
161
+ color: 'var(--ggui-color-onSurfaceVariant, #52525b)',
162
+ fontWeight: 'var(--ggui-font-weight-semibold, 600)',
163
+ textTransform: 'uppercase',
164
+ letterSpacing: '0.06em',
165
+ fontSize: 'var(--ggui-font-size-xs, 11px)',
166
+ }, children: "Or open this URL in your browser" }), _jsx("div", { style: {
167
+ fontFamily: 'var(--ggui-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace)',
168
+ fontSize: 'var(--ggui-font-size-sm, 13px)',
169
+ color: 'var(--ggui-color-onSurface, #111)',
170
+ wordBreak: 'break-all',
171
+ userSelect: 'all',
172
+ padding: 'var(--ggui-spacing-3, 12px)',
173
+ background: 'var(--ggui-color-surface, #fff)',
174
+ border: '1px solid var(--ggui-color-neutral-200, #e4e4e7)',
175
+ borderRadius: 'var(--ggui-shape-radius-sm, 6px)',
176
+ lineHeight: '1.45',
177
+ }, children: settingsUrl }), _jsxs("div", { style: {
178
+ display: 'flex',
179
+ alignItems: 'center',
180
+ gap: 'var(--ggui-spacing-3, 12px)',
181
+ flexWrap: 'wrap',
182
+ }, children: [_jsx(Button, { variant: "primary", size: "md", onClick: onPrimary, disabled: ctaDisabled, children: ctaLabel }), ctaState === 'idle' || ctaState === 'working' ? (_jsx(Text, { variant: "caption", style: {
183
+ color: 'var(--ggui-color-onSurfaceVariant, #52525b)',
184
+ opacity: 0.85,
185
+ }, children: "or tap-and-hold the URL above to copy" })) : null] })] })] }) }), intent ? (_jsxs(Text, { variant: "caption", style: {
186
+ color: 'var(--ggui-color-onSurfaceVariant, #52525b)',
187
+ opacity: 0.7,
188
+ }, children: ["Pending prompt: ", intent] })) : null] }) }));
189
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * MCP Apps protocol probe system card.
3
+ *
4
+ * Diagnostic + regression fixture exercising every gesture surface
5
+ * the iframe runtime routes:
6
+ *
7
+ * 1. **Pattern β bridge** — `tools/call ggui_runtime_submit_action` (audit) +
8
+ * `ui/update-model-context` (silent context) + `ui/message`
9
+ * (consent prompt). Fires for cross-server / non-app-visible
10
+ * `actionSpec[name].dispatch.tool` targets, OR any
11
+ * `dispatch.kind === 'agent'` entry. Empirical-first reproduction
12
+ * of the production `dispatchWiredAction` path.
13
+ * 2. **Pattern α direct fire** — `tools/call ggui_runtime_submit_action`
14
+ * (audit) + `tools/call <T>` (direct, no consent prompt). Fires
15
+ * for same-server, app-visible target tools. Probes against
16
+ * `ggui_runtime_submit_action` itself (the only universally-app-visible
17
+ * tool the iframe can be sure exists).
18
+ * 3. **Native-idiom: anchor click** — `ui/open-link` paired with
19
+ * `kind: 'openLink'` audit. Triggered by the runtime's
20
+ * capture-phase `<a href>` interceptor (cross-origin or
21
+ * `target="_blank"`). The probe row simulates the postMessage
22
+ * directly so the operator sees the envelope without needing to
23
+ * click an anchor.
24
+ * 4. **Native-idiom: fullscreen** — `ui/request-display-mode` +
25
+ * `kind: 'requestDisplayMode'` audit. Triggered by the runtime's
26
+ * `Element.prototype.requestFullscreen` /
27
+ * `Document.prototype.exitFullscreen` overrides. The probe row
28
+ * simulates the postMessage; the row labelled "exitFullscreen"
29
+ * sends `mode: 'inline'` per the spec'd inverse mapping.
30
+ *
31
+ * Each button transitions through `idle → pending → success | error`
32
+ * so the operator can see exactly which methods the embedding host
33
+ * (claude.ai web, Claude Desktop, …) actually honors today. The
34
+ * persistent receive-log captures every `ui/notifications/*` and
35
+ * host-issued request seen while the card is mounted.
36
+ *
37
+ * Why this lives outside the wire-context: system cards (`kind:
38
+ * 'mcp-apps-probe'`, `'no-credentials'`, …) mount in a render-only
39
+ * branch of `bootSelfContained` that does NOT install
40
+ * `<GguiWireProvider>` — they exist precisely to surface protocol
41
+ * state when the wire-context might not be live (auth missing,
42
+ * compile failed, etc.). The probe card mirrors the postMessage
43
+ * envelopes the runtime's gesture surfaces would emit, derived from
44
+ * the canonical `SubmitActionEnvelope` shape in
45
+ * `@ggui-ai/protocol/integrations/mcp-apps`.
46
+ *
47
+ * Originating context: an earlier runtime posted its outbound
48
+ * action dispatch only as `{method:'tools/call',
49
+ * name:'ggui_runtime_submit_action', ...}`. Per MCP Apps spec §401, hosts MUST
50
+ * reject `tools/call` from views for tools without `'app'` in
51
+ * visibility — claude.ai silently dropped the call. The original
52
+ * probe diagnosed the gap; the routing simplification work landed
53
+ * Pattern α / Pattern β / native idioms in response.
54
+ */
55
+ import * as React from 'react';
56
+ export interface ProtocolProbeCardProps {
57
+ /**
58
+ * Optional: the original prompt that triggered the probe push.
59
+ * Surfaced as a footnote so the card matches the framing of other
60
+ * system cards (`NoCredentialsCard`).
61
+ */
62
+ readonly intent?: string;
63
+ }
64
+ export declare function ProtocolProbeCard({ intent, }: ProtocolProbeCardProps): React.JSX.Element;