@ggui-ai/iframe-runtime 0.22.0 → 0.23.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/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export { parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './m
37
37
  export type { RendererBootFailureReason, RendererBootFailedMessage, } from './runtime.js';
38
38
  export type { ProtocolError, ProtocolErrorEmitter, BootstrapFailureReason, } from './protocol-error.js';
39
39
  export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
40
- export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, FontFaceBlockedEvent, AppThemeInvalidEvent, AppThemeMemberStrippedEvent, ActionSpecInvalidEvent, ActionSpecMemberStrippedEvent, ThemeReinjectFailedEvent, SubscribeFailedEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, ChannelPollDegradedEvent, ChannelPollRecoveredEvent, UiFeedbackEvent, RelayDeadTapEvent, RelayIncapabilityEvent, RelayLatchTrigger, UnknownObservabilityEvent, } from './observability.js';
40
+ export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, FontFaceBlockedEvent, AppThemeInvalidEvent, AppThemeMemberStrippedEvent, ActionSpecInvalidEvent, ActionSpecMemberStrippedEvent, SpentOneShotsInvalidEvent, ThemeReinjectFailedEvent, SubscribeFailedEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, ChannelPollDegradedEvent, ChannelPollRecoveredEvent, UiFeedbackEvent, RelayDeadTapEvent, RelayIncapabilityEvent, RelayLatchTrigger, ComponentEmptyEvent, OneShotUnenforceableEvent, UnknownObservabilityEvent, } from './observability.js';
41
41
  export { isObservabilityMessage, isRelayDeadTapEvent, isRelayIncapabilityEvent, postObservabilityToParent, } from './observability.js';
42
42
  export type { LifecycleEmitter } from './lifecycle.js';
43
43
  export { postLifecycleToParent, makeLifecycleEvent } from './lifecycle.js';
@@ -49,6 +49,13 @@ export declare function reportInvalidActionSpec(issues: readonly string[]): void
49
49
  * `parseMcpAppAiGguiRenderMeta` call in this package.
50
50
  */
51
51
  export declare function reportStrippedActionSpecMembers(keys: readonly string[]): void;
52
+ /**
53
+ * The wire parser refused the slice's `spentOneShots` (ggui#1223): report it
54
+ * to the embedding host as `spent-one-shots-invalid` and mount without it.
55
+ * The runtime keeps its in-memory guard alone, so a consumed action would
56
+ * look live again after a reload — never silently.
57
+ */
58
+ export declare function reportInvalidSpentOneShots(issues: readonly string[]): void;
52
59
  /**
53
60
  * Every degradation the wire parser's read door can report, bound to its
54
61
  * observability event — the ONE options object every
@@ -255,6 +255,9 @@ function projectMeta(meta, hasStaticContent) {
255
255
  // Already validated at the wire parser's tolerant read door, so no
256
256
  // re-validation here.
257
257
  ...(meta.actionSpec !== undefined ? { actionSpec: meta.actionSpec } : {}),
258
+ // The card's spent oneShot names (ggui#1223), carried through whole —
259
+ // validated at the wire parser's tolerant read door.
260
+ ...(meta.spentOneShots !== undefined ? { spentOneShots: meta.spentOneShots } : {}),
258
261
  ...(gadgets !== undefined ? { gadgets } : {}),
259
262
  ...(publicEnv !== undefined ? { publicEnv } : {}),
260
263
  ...(streamWebSocketLocalTools !== undefined
@@ -368,6 +371,15 @@ export function reportInvalidActionSpec(issues) {
368
371
  export function reportStrippedActionSpecMembers(keys) {
369
372
  postObservabilityToParent({ kind: 'action-spec-member-stripped', keys: [...keys] });
370
373
  }
374
+ /**
375
+ * The wire parser refused the slice's `spentOneShots` (ggui#1223): report it
376
+ * to the embedding host as `spent-one-shots-invalid` and mount without it.
377
+ * The runtime keeps its in-memory guard alone, so a consumed action would
378
+ * look live again after a reload — never silently.
379
+ */
380
+ export function reportInvalidSpentOneShots(issues) {
381
+ postObservabilityToParent({ kind: 'spent-one-shots-invalid', issues: [...issues] });
382
+ }
371
383
  /**
372
384
  * Every degradation the wire parser's read door can report, bound to its
373
385
  * observability event — the ONE options object every
@@ -380,6 +392,7 @@ export const READ_DOOR_REPORTERS = {
380
392
  onStrippedThemeMembers: reportStrippedThemeMembers,
381
393
  onInvalidActionSpec: reportInvalidActionSpec,
382
394
  onStrippedActionSpecMembers: reportStrippedActionSpecMembers,
395
+ onInvalidSpentOneShots: reportInvalidSpentOneShots,
383
396
  };
384
397
  /**
385
398
  * Parse the render slice from `globalThis.__GGUI_META__` — the
@@ -33,7 +33,7 @@ import { MCP_APP_OBSERVE_TYPE } from '@ggui-ai/protocol/integrations/mcp-apps';
33
33
  *
34
34
  * @public
35
35
  */
36
- export type ObservabilityEvent = SchemaVersionMismatchEvent | FontFaceBlockedEvent | AppThemeInvalidEvent | AppThemeMemberStrippedEvent | ActionSpecInvalidEvent | ActionSpecMemberStrippedEvent | OneShotUnenforceableEvent | ThemeReinjectFailedEvent | SubscribeFailedEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | ChannelPollDegradedEvent | ChannelPollRecoveredEvent | UiFeedbackEvent | RelayIncapabilityEvent | RelayDeadTapEvent | ComponentEmptyEvent | UnknownObservabilityEvent;
36
+ export type ObservabilityEvent = SchemaVersionMismatchEvent | FontFaceBlockedEvent | AppThemeInvalidEvent | AppThemeMemberStrippedEvent | ActionSpecInvalidEvent | ActionSpecMemberStrippedEvent | OneShotUnenforceableEvent | SpentOneShotsInvalidEvent | ThemeReinjectFailedEvent | SubscribeFailedEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | ChannelPollDegradedEvent | ChannelPollRecoveredEvent | UiFeedbackEvent | RelayIncapabilityEvent | RelayDeadTapEvent | ComponentEmptyEvent | UnknownObservabilityEvent;
37
37
  /**
38
38
  * Fired when the protocol-version handshake rejects the connection.
39
39
  * Parallel to `UpgradeRequiredError` — the host can choose to render
@@ -402,6 +402,18 @@ export interface OneShotUnenforceableEvent {
402
402
  readonly renderId: string;
403
403
  readonly actionName: string;
404
404
  }
405
+ /**
406
+ * The render slice carried a `spentOneShots` the wire schema refused
407
+ * (ggui#1223): the parser dropped it, and the card mounted with only its
408
+ * in-memory one-shot guard — a consumed action reads as live again after a
409
+ * reload. `issues` are the schema's own messages.
410
+ *
411
+ * @public
412
+ */
413
+ export interface SpentOneShotsInvalidEvent {
414
+ readonly kind: 'spent-one-shots-invalid';
415
+ readonly issues: readonly string[];
416
+ }
405
417
  /**
406
418
  * The live re-injection on a host theme flip (ggui#987 §4) failed: the
407
419
  * renderer's update rejected, so the card kept its previous overlay /
@@ -38,8 +38,11 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
38
38
  * `McpAppAiGguiMetaParseFailureReason` (`MISSING_TOOL_OUTPUT` /
39
39
  * `MISSING_META_GGUI_BOOTSTRAP` / `MALFORMED_BOOTSTRAP` /
40
40
  * `EXPIRED_BOOTSTRAP`). `MISSING_META_GGUI_BOOTSTRAP` and
41
- * `BOOTSTRAP_META_MISSING` are synonyms — the latter is the
42
- * on-wire name, the former the parse-internal name.
41
+ * `BOOTSTRAP_META_MISSING` name one condition. The runtime and the
42
+ * reference shell emit `MISSING_META_GGUI_BOOTSTRAP` on
43
+ * `ggui:bootstrap-failed`; `BOOTSTRAP_META_MISSING` stays a member
44
+ * so a host classifies either spelling as that condition
45
+ * (SPEC §5.5.2).
43
46
  *
44
47
  * 2. **Post-parse orchestration failures** — `UI_INITIALIZE_FAILED`
45
48
  * / `WS_HANDSHAKE_FAILED`. Matches `RendererBootFailureReason`.
@@ -32,6 +32,7 @@
32
32
  import React, { Component, createElement, Fragment, } from 'react';
33
33
  import { createRoot } from 'react-dom/client';
34
34
  import { postObservabilityToParent } from './observability.js';
35
+ import { trackThemeFontFaces } from './host-fonts.js';
35
36
  import { stripMarkers, rewriteImports, composeThemeCss, } from '@ggui-ai/design/rendering';
36
37
  import { hoistImports, loadModule, loadModuleInline, probeUrlModuleLoad, } from '@ggui-ai/design/module-loader';
37
38
  /**
@@ -367,6 +368,10 @@ export async function mountReactRoot(container, opts) {
367
368
  if (styleEl.textContent !== rootCss) {
368
369
  styleEl.textContent = rootCss;
369
370
  }
371
+ // ggui#1147: the chrome CSS carries the stored theme's @font-face rules,
372
+ // mid-session included, while `font-src` was fixed at mount. Track them so
373
+ // a blocked face is reported as `font-face-blocked`, not lost silently.
374
+ trackThemeFontFaces(rootCss);
370
375
  }
371
376
  const componentElement = currentComponent === null
372
377
  ? null
package/dist/runtime.d.ts CHANGED
@@ -529,6 +529,12 @@ export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
529
529
  * (ggui#1178): a mount with no live session frame paints from this seed, and
530
530
  * the one-shot guard reads the current render's spec, so a seed without it
531
531
  * could not enforce `oneShot`. A system seed never carries one.
532
+ *
533
+ * It also carries the card's `epoch` and, when the slice lists them, the
534
+ * `oneShot` names this card already spent, as the render's record
535
+ * `{ epoch, actions }` (ggui#1223). `deriveRenderMeta` projected those names
536
+ * for this card only, so the record's epoch is the card's own and the guard
537
+ * stops the first gesture on a spent action after a reload.
532
538
  */
533
539
  export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta): Promise<GguiSessionSeedInput | null>;
534
540
  /**
package/dist/runtime.js CHANGED
@@ -1180,10 +1180,22 @@ function parseSeedProps(propsJson) {
1180
1180
  * (ggui#1178): a mount with no live session frame paints from this seed, and
1181
1181
  * the one-shot guard reads the current render's spec, so a seed without it
1182
1182
  * could not enforce `oneShot`. A system seed never carries one.
1183
+ *
1184
+ * It also carries the card's `epoch` and, when the slice lists them, the
1185
+ * `oneShot` names this card already spent, as the render's record
1186
+ * `{ epoch, actions }` (ggui#1223). `deriveRenderMeta` projected those names
1187
+ * for this card only, so the record's epoch is the card's own and the guard
1188
+ * stops the first gesture on a spent action after a reload.
1183
1189
  */
1184
1190
  export async function buildGguiSessionSeedInput(meta) {
1185
1191
  const props = parseSeedProps(meta.propsJson);
1186
- const actionSpec = meta.actionSpec !== undefined ? { actionSpec: meta.actionSpec } : {};
1192
+ const componentFields = {
1193
+ ...(meta.actionSpec !== undefined ? { actionSpec: meta.actionSpec } : {}),
1194
+ ...(typeof meta.epoch === 'number' ? { epoch: meta.epoch } : {}),
1195
+ ...(meta.spentOneShots !== undefined
1196
+ ? { spentOneShots: { epoch: meta.epoch ?? 0, actions: meta.spentOneShots } }
1197
+ : {}),
1198
+ };
1187
1199
  // System-card mode — `kind` keyed against the built-in registry.
1188
1200
  if (meta.kind !== undefined) {
1189
1201
  return {
@@ -1201,7 +1213,7 @@ export async function buildGguiSessionSeedInput(meta) {
1201
1213
  appId: meta.appId,
1202
1214
  componentCode: decodeCodeB64(meta.codeB64),
1203
1215
  ...(props !== undefined ? { props } : {}),
1204
- ...actionSpec,
1216
+ ...componentFields,
1205
1217
  };
1206
1218
  }
1207
1219
  // Compiled-component mode — fetch the content-addressable bytes.
@@ -1217,7 +1229,7 @@ export async function buildGguiSessionSeedInput(meta) {
1217
1229
  appId: meta.appId,
1218
1230
  componentCode,
1219
1231
  ...(props !== undefined ? { props } : {}),
1220
- ...actionSpec,
1232
+ ...componentFields,
1221
1233
  };
1222
1234
  }
1223
1235
  /**
package/dist/types.d.ts CHANGED
@@ -40,7 +40,9 @@ type GguiSessionLedgerFields = 'eventSequence' | 'createdAt' | 'lastActivityAt'
40
40
  * (`propsSpec` / `streamSpec` / `actionSpec`), so the channel handlers that
41
41
  * read those after the `type !== 'mcpApps' && type !== 'system'` narrowing
42
42
  * stay type-clean. On a seed, `propsSpec` and `streamSpec` are `undefined`;
43
- * `actionSpec` is the slice's own when the slice carried one (ggui#1178).
43
+ * `actionSpec` is the slice's own when the slice carried one (ggui#1178), and
44
+ * so are `epoch` and the spent-`oneShot` record built from the slice's
45
+ * `spentOneShots` (ggui#1223).
44
46
  */
45
47
  export type GguiSessionSeedInput = Omit<ComponentGguiSession, GguiSessionLedgerFields> | Omit<SystemGguiSession, GguiSessionLedgerFields>;
46
48
  /**
@@ -59,9 +61,10 @@ export type GguiSessionSeedInput = Omit<ComponentGguiSession, GguiSessionLedgerF
59
61
  * deferred.)
60
62
  * - `MISSING_META_GGUI_BOOTSTRAP` — `params` is an object but
61
63
  * `params._meta` is absent or carries no `ai.ggui/render` slice.
62
- * The on-wire synonym is `BOOTSTRAP_META_MISSING`. (Name retained
63
- * pre-R4 for the observability + host-postMessage protocol;
64
- * cosmetic rename is deferred.)
64
+ * This is the spelling emitted on `ggui:bootstrap-failed`;
65
+ * `BOOTSTRAP_META_MISSING` is a synonym a host classifies alike
66
+ * (SPEC §5.5.2). (Name retained pre-R4 for the observability +
67
+ * host-postMessage protocol; cosmetic rename is deferred.)
65
68
  * - `MALFORMED_BOOTSTRAP` — slice is present but fails business
66
69
  * rules (no live mode and no static content, mode discriminators
67
70
  * mutually exclusive, structural rejection by the combiner).
@@ -87,6 +87,20 @@ export function buildRootWireConfig(opts) {
87
87
  : undefined;
88
88
  },
89
89
  onActionSpecAbsent: nameUnenforceableOnce,
90
+ // ggui#1223 — the card's persisted spent `oneShot` names. A re-served card
91
+ // boots a new iframe, so the guard's in-memory set starts empty; the
92
+ // render's record says what this card already spent. It counts only when
93
+ // written on THIS card (its epoch is the render's own; absent ⇒ 0), the
94
+ // same gate `deriveRenderMeta` applies, since a `ggui_update` mints a fresh
95
+ // card whose record would otherwise carry over.
96
+ getSpentOneShots: () => {
97
+ const currentRender = opts.getCurrentGguiSession();
98
+ if (currentRender === null || currentRender.type === 'mcpApps' || currentRender.type === 'system') {
99
+ return undefined;
100
+ }
101
+ const record = currentRender.spentOneShots;
102
+ return record !== undefined && record.epoch === (currentRender.epoch ?? 0) ? record.actions : undefined;
103
+ },
90
104
  // The iframe's precompiled-validator variant — the dispatch never
91
105
  // trips the iframe's no-`unsafe-eval` CSP.
92
106
  validateEnvelope: validateOutboundActionEnvelope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/iframe-runtime",
3
- "version": "0.22.0",
3
+ "version": "0.23.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.22.0",
45
- "@ggui-ai/live-channel": "0.22.0",
46
- "@ggui-ai/preview-a2ui": "0.22.0",
47
- "@ggui-ai/gadgets": "0.22.0",
48
- "@ggui-ai/protocol": "0.22.0",
49
- "@ggui-ai/wire": "0.22.0"
44
+ "@ggui-ai/live-channel": "0.23.0",
45
+ "@ggui-ai/preview-a2ui": "0.23.0",
46
+ "@ggui-ai/protocol": "0.23.0",
47
+ "@ggui-ai/gadgets": "0.23.0",
48
+ "@ggui-ai/design": "0.23.0",
49
+ "@ggui-ai/wire": "0.23.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@modelcontextprotocol/sdk": "^1.27.0"