@ggui-ai/iframe-runtime 0.10.0 → 0.12.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/host-capabilities.d.ts +6 -4
- package/dist/host-palette-bridge.d.ts +43 -0
- package/dist/host-palette-bridge.js +107 -0
- package/dist/iframe-runtime.js +83 -72
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/mcp-app-iframe-host.js +7 -2
- package/dist/react-renderer.d.ts +48 -6
- package/dist/react-renderer.js +54 -13
- package/dist/render-item.d.ts +17 -0
- package/dist/render-item.js +4 -0
- package/dist/runtime.d.ts +82 -1
- package/dist/runtime.js +214 -62
- package/dist/ui-feedback-chrome.d.ts +16 -0
- package/dist/ui-feedback-chrome.js +20 -0
- package/dist/wire-config.d.ts +7 -3
- package/package.json +7 -7
|
@@ -77,8 +77,13 @@ export function mountMcpAppIframe(container, opts) {
|
|
|
77
77
|
iframe.style.maxWidth = dims.maxWidth !== undefined ? `${dims.maxWidth}px` : '100%';
|
|
78
78
|
if (dims.maxHeight !== undefined)
|
|
79
79
|
iframe.style.maxHeight = `${dims.maxHeight}px`;
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// Round-6 doctrine (ggui#589 @6e15724a1, kit C1 grade): the helper
|
|
81
|
+
// paints CONTAINMENT ONLY — silhouette (border/radius) belongs to
|
|
82
|
+
// the embedding host's own chrome, tokens to the theme. The former
|
|
83
|
+
// 1px #e5e5e5 / radius-8 here was the same ungoverned-chrome class
|
|
84
|
+
// the RN helper shipped, surfaced by the host-helper catalog's
|
|
85
|
+
// first-party run (ggui#600 OPEN FINDING, closed here).
|
|
86
|
+
iframe.style.border = 'none';
|
|
82
87
|
iframe.style.display = 'block';
|
|
83
88
|
container.appendChild(iframe);
|
|
84
89
|
// Cache contentWindow — on detach, `iframe.contentWindow` is null
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -62,17 +62,59 @@ export interface ReactRootMountOptions {
|
|
|
62
62
|
*/
|
|
63
63
|
readonly themeMode?: 'light' | 'dark';
|
|
64
64
|
/**
|
|
65
|
-
* Per-app theme overlay
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
65
|
+
* Per-app theme overlay — a partial set of `--ggui-*` values; unset
|
|
66
|
+
* vars keep their token defaults. Merged INTO the scoped in-body
|
|
67
|
+
* token block after the base block (and after `hostPalette`), which
|
|
68
|
+
* is the only placement that can recolor tree content: a `:root`-only
|
|
69
|
+
* append is cascade-dead for components — the scoped block sits later
|
|
70
|
+
* in document order and re-specifies every token (browser-verified,
|
|
71
|
+
* see the scoped-merge comment in `renderTree`). The `:root` append
|
|
72
|
+
* still happens, but for body chrome only. A structural subset of
|
|
73
|
+
* protocol's `AppTheme` (`name` is display-only, not needed at
|
|
74
|
+
* render). `mode` drives `color-scheme` so native controls /
|
|
75
|
+
* scrollbars / canvas pick up dark vs light.
|
|
76
|
+
*
|
|
77
|
+
* Precedence (ggui#573 ruling): base tokens < `hostPalette` <
|
|
78
|
+
* `appTheme` < `cssOverrides` — the slice's stamped theme always
|
|
79
|
+
* wins; the host palette is the fallback layer beneath it.
|
|
71
80
|
*/
|
|
72
81
|
readonly appTheme?: {
|
|
73
82
|
readonly mode: 'light' | 'dark';
|
|
74
83
|
readonly cssVariables: Record<string, string>;
|
|
84
|
+
/**
|
|
85
|
+
* The REGISTERED base ladder, delivered over the wire (ggui#598-C
|
|
86
|
+
* runtime theme registration — see protocol's `appThemeSchema`).
|
|
87
|
+
* BOTH modes' resolved variable sets ride the envelope; when
|
|
88
|
+
* present, the mode-selected set REPLACES the compiled ladder
|
|
89
|
+
* block (`getScopedThemeCss`/`getScopedCssTokens`) at the base
|
|
90
|
+
* position of the #573 precedence above — delivery changes WHERE
|
|
91
|
+
* the ladder's bytes come from, never WHICH layer wins. Mode
|
|
92
|
+
* selection reuses the mount's already-resolved `themeMode` (one
|
|
93
|
+
* projection — no second mode resolution here), and a mode switch
|
|
94
|
+
* re-paints from the retained other set with no network activity.
|
|
95
|
+
* Values are injection-safe by the wire schema — never
|
|
96
|
+
* re-validated here; serialization is key-sorted for byte-stable
|
|
97
|
+
* output. `documentHash` is the registration's identity (joins a
|
|
98
|
+
* painted ladder to its registration record); the renderer
|
|
99
|
+
* carries it but exposes no observability surface for it today.
|
|
100
|
+
*/
|
|
101
|
+
readonly base?: {
|
|
102
|
+
readonly documentHash: string;
|
|
103
|
+
readonly light: Readonly<Record<string, string>>;
|
|
104
|
+
readonly dark: Readonly<Record<string, string>>;
|
|
105
|
+
};
|
|
75
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Host-announced palette, already translated onto `--ggui-*` tokens
|
|
109
|
+
* (see `host-palette-bridge.ts` — the spec `--color-*` vocabulary
|
|
110
|
+
* never reaches this layer). Merged into the scoped token block
|
|
111
|
+
* after the base block and BEFORE `appTheme`, so it repaints only
|
|
112
|
+
* the slots no operator theme re-specifies (ggui#572; precedence
|
|
113
|
+
* per the ggui#573 ruling above). Values are sanitized by the
|
|
114
|
+
* bridge before they get here — host input never reaches this
|
|
115
|
+
* string-join raw.
|
|
116
|
+
*/
|
|
117
|
+
readonly hostPalette?: Readonly<Record<string, string>>;
|
|
76
118
|
readonly cssOverrides?: string;
|
|
77
119
|
readonly onError?: (error: Error) => void;
|
|
78
120
|
/**
|
package/dist/react-renderer.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import React, { Component, createElement, Fragment, } from 'react';
|
|
33
33
|
import { createRoot } from 'react-dom/client';
|
|
34
|
-
import { stripMarkers, rewriteImports, getScopedThemeCss, getScopedCssTokens, getThemeCss, getCssTokens, } from '@ggui-ai/design/rendering';
|
|
34
|
+
import { stripMarkers, rewriteImports, getScopedThemeCss, getScopedCssTokens, assembleDeliveredThemeCss, getThemeCss, getCssTokens, } from '@ggui-ai/design/rendering';
|
|
35
35
|
import { hoistImports, loadModule, loadModuleInline, probeUrlModuleLoad, } from '@ggui-ai/design/module-loader';
|
|
36
36
|
/**
|
|
37
37
|
* Document-lifetime verdict: URL-scheme module loading (`blob:` main
|
|
@@ -59,6 +59,18 @@ function makeScopeClass() {
|
|
|
59
59
|
scopeCounter += 1;
|
|
60
60
|
return `ggui-rcr-${scopeCounter}`;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Join a custom-property record into CSS declarations. Values were
|
|
64
|
+
* validated injection-safe upstream (appTheme: write-side
|
|
65
|
+
* `appThemeSchema` + the wire parser; hostPalette: the host-palette
|
|
66
|
+
* bridge's sanitizer), so the string-join is safe — do NOT
|
|
67
|
+
* re-sanitize here.
|
|
68
|
+
*/
|
|
69
|
+
function toCssDecls(vars) {
|
|
70
|
+
return Object.entries(vars)
|
|
71
|
+
.map(([k, v]) => `${k}: ${v};`)
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
62
74
|
// =============================================================================
|
|
63
75
|
// Error boundary — port of RCR's internal ErrorBoundary (L68–203).
|
|
64
76
|
// =============================================================================
|
|
@@ -302,13 +314,35 @@ export async function mountReactRoot(container, opts) {
|
|
|
302
314
|
}
|
|
303
315
|
}
|
|
304
316
|
function renderTree(opts) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
// Delivered base ladder (ggui#598-C): when the slice theme carries
|
|
318
|
+
// `base`, the mode-selected set REPLACES the compiled ladder block
|
|
319
|
+
// at the base position — same precedence slot, different byte
|
|
320
|
+
// source. Mode selection reuses the already-resolved `themeMode`
|
|
321
|
+
// (one projection; absent means light — the same default
|
|
322
|
+
// `getScopedThemeCss` applies). Both sets stay retained on the
|
|
323
|
+
// options, so a mode-switch re-render picks the other ladder from
|
|
324
|
+
// memory — never the network.
|
|
325
|
+
const deliveredBase = opts.appTheme?.base;
|
|
326
|
+
let themeCss = deliveredBase !== undefined
|
|
327
|
+
? // Design-package assembler (ggui#598-C, injection-review flag 1):
|
|
328
|
+
// the delivered ladder gets the SAME scaffolding the compiled
|
|
329
|
+
// ladder gets — base inherits, derived gradient/effect tokens
|
|
330
|
+
// (color-mix fallback split), box-sizing, font-inherit — from
|
|
331
|
+
// ONE shared builder, so the two paths cannot drift.
|
|
332
|
+
assembleDeliveredThemeCss(scopeClass, opts.themeMode === 'dark' ? deliveredBase.dark : deliveredBase.light)
|
|
333
|
+
: opts.themeId
|
|
334
|
+
? getScopedThemeCss(opts.themeId, scopeClass, opts.themeMode)
|
|
335
|
+
: getScopedCssTokens(scopeClass, opts.themeMode);
|
|
336
|
+
if (opts.hostPalette) {
|
|
337
|
+
// Host palette = the fallback layer (ggui#572, #573 ruling):
|
|
338
|
+
// after the base scoped block so it can repaint at all (the base
|
|
339
|
+
// re-specifies every token), BEFORE the appTheme overlay so the
|
|
340
|
+
// slice's stamped theme wins by document order. Keys + values
|
|
341
|
+
// arrive pre-sanitized from the host-palette bridge.
|
|
342
|
+
themeCss += `.${scopeClass}{${toCssDecls(opts.hostPalette)}}`;
|
|
343
|
+
}
|
|
308
344
|
if (opts.appTheme) {
|
|
309
|
-
const decls =
|
|
310
|
-
.map(([k, v]) => `${k}: ${v};`)
|
|
311
|
-
.join('');
|
|
345
|
+
const decls = toCssDecls(opts.appTheme.cssVariables);
|
|
312
346
|
// The overlay must be merged HERE, into the scoped style that
|
|
313
347
|
// mounts INSIDE the scope div — the `:root` append below is
|
|
314
348
|
// cascade-dead for tree content: the in-scope block sits later
|
|
@@ -316,9 +350,11 @@ export async function mountReactRoot(container, opts) {
|
|
|
316
350
|
// specificity the base scoped tokens always win over a head-level
|
|
317
351
|
// override (browser-verified in rnd/gen-ui/beauty/experiments/001,
|
|
318
352
|
// the probe sequence that found this). Appended AFTER the base
|
|
319
|
-
// block and BEFORE `cssOverrides`, so: base <
|
|
320
|
-
//
|
|
321
|
-
// (
|
|
353
|
+
// block + host palette and BEFORE `cssOverrides`, so: base <
|
|
354
|
+
// host palette < operator overlay < explicit overrides
|
|
355
|
+
// (ggui#573: slice wins, host fallback). Values validated
|
|
356
|
+
// injection-safe upstream (same guarantee the `:root` append
|
|
357
|
+
// cites).
|
|
322
358
|
themeCss += `.${scopeClass}{${decls}}`;
|
|
323
359
|
}
|
|
324
360
|
// Also inject theme CSS at `:root` on `document.head`. The scoped
|
|
@@ -334,10 +370,15 @@ export async function mountReactRoot(container, opts) {
|
|
|
334
370
|
let rootCss = opts.themeId
|
|
335
371
|
? getThemeCss(opts.themeId, opts.themeMode)
|
|
336
372
|
: getCssTokens(opts.themeMode);
|
|
373
|
+
if (opts.hostPalette) {
|
|
374
|
+
// BODY-CHROME ONLY, same as the appTheme `:root` append below —
|
|
375
|
+
// and beneath it, mirroring the scoped-block order (#573:
|
|
376
|
+
// slice wins, host fallback). Keeps the embedding shell's
|
|
377
|
+
// body font/color/background coherent with the tree.
|
|
378
|
+
rootCss += `:root{${toCssDecls(opts.hostPalette)}}`;
|
|
379
|
+
}
|
|
337
380
|
if (opts.appTheme) {
|
|
338
|
-
const decls =
|
|
339
|
-
.map(([k, v]) => `${k}: ${v};`)
|
|
340
|
-
.join('');
|
|
381
|
+
const decls = toCssDecls(opts.appTheme.cssVariables);
|
|
341
382
|
// BODY-CHROME ONLY. This `:root` append themes the embedding
|
|
342
383
|
// shell's body styles (font/color/background outside the scope
|
|
343
384
|
// div). It does NOT reach tree content — the scoped in-body
|
package/dist/render-item.d.ts
CHANGED
|
@@ -51,11 +51,28 @@ export interface RenderItemOptions {
|
|
|
51
51
|
* so the renderer injects the `--ggui-*` overrides + `color-scheme` at
|
|
52
52
|
* `:root`. Sourced from the bootstrap's `_meta["ai.ggui/render"].theme`.
|
|
53
53
|
* A structural subset of protocol's `AppTheme` (`name` is display-only).
|
|
54
|
+
* `base` is the delivered registered ladder (ggui#598-C) — forwarded
|
|
55
|
+
* verbatim; the renderer injects the mode-selected set in place of
|
|
56
|
+
* the compiled ladder block (see
|
|
57
|
+
* {@link ReactRootMountOptions.appTheme}).
|
|
54
58
|
*/
|
|
55
59
|
readonly appTheme?: {
|
|
56
60
|
readonly mode: 'light' | 'dark';
|
|
57
61
|
readonly cssVariables: Record<string, string>;
|
|
62
|
+
readonly base?: {
|
|
63
|
+
readonly documentHash: string;
|
|
64
|
+
readonly light: Readonly<Record<string, string>>;
|
|
65
|
+
readonly dark: Readonly<Record<string, string>>;
|
|
66
|
+
};
|
|
58
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Host-announced palette, pre-mapped onto `--ggui-*` tokens by the
|
|
70
|
+
* host-palette bridge. Forwarded to
|
|
71
|
+
* {@link ReactRootMountOptions.hostPalette}: the fallback layer
|
|
72
|
+
* beneath `appTheme` in the scoped token block (ggui#572, merge
|
|
73
|
+
* order per the ggui#573 ruling — slice wins, host fallback).
|
|
74
|
+
*/
|
|
75
|
+
readonly hostPalette?: Readonly<Record<string, string>>;
|
|
59
76
|
readonly cssOverrides?: string;
|
|
60
77
|
readonly onError?: (err: Error) => void;
|
|
61
78
|
/**
|
package/dist/render-item.js
CHANGED
|
@@ -177,6 +177,9 @@ export async function mountRender(container, opts) {
|
|
|
177
177
|
...(currentOpts.themeId !== undefined ? { themeId: currentOpts.themeId } : {}),
|
|
178
178
|
...(currentOpts.themeMode !== undefined ? { themeMode: currentOpts.themeMode } : {}),
|
|
179
179
|
...(currentOpts.appTheme !== undefined ? { appTheme: currentOpts.appTheme } : {}),
|
|
180
|
+
...(currentOpts.hostPalette !== undefined
|
|
181
|
+
? { hostPalette: currentOpts.hostPalette }
|
|
182
|
+
: {}),
|
|
180
183
|
...(currentOpts.cssOverrides !== undefined
|
|
181
184
|
? { cssOverrides: currentOpts.cssOverrides }
|
|
182
185
|
: {}),
|
|
@@ -244,6 +247,7 @@ export async function mountRender(container, opts) {
|
|
|
244
247
|
...(next.themeId !== undefined ? { themeId: next.themeId } : {}),
|
|
245
248
|
...(next.themeMode !== undefined ? { themeMode: next.themeMode } : {}),
|
|
246
249
|
...(next.appTheme !== undefined ? { appTheme: next.appTheme } : {}),
|
|
250
|
+
...(next.hostPalette !== undefined ? { hostPalette: next.hostPalette } : {}),
|
|
247
251
|
...(next.cssOverrides !== undefined ? { cssOverrides: next.cssOverrides } : {}),
|
|
248
252
|
...(next.onError !== undefined ? { onError: next.onError } : {}),
|
|
249
253
|
renderWrapper: wrapInScopedProvider,
|
package/dist/runtime.d.ts
CHANGED
|
@@ -97,6 +97,87 @@ export declare function setCurrentApp(app: App): void;
|
|
|
97
97
|
* App); production caller is `buildOpts` inside `bootSequence`.
|
|
98
98
|
*/
|
|
99
99
|
export declare function hostAnnouncedThemeMode(): 'light' | 'dark' | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the mount's base-stylesheet color mode from the slice, with
|
|
102
|
+
* the host as the final fallback (ggui#589, completing the ggui#551
|
|
103
|
+
* precedence ruling — "slice wins, host is the fallback, absent ≠
|
|
104
|
+
* light"):
|
|
105
|
+
*
|
|
106
|
+
* stamped `themeMode` > `theme.mode` (the slice theme OBJECT) >
|
|
107
|
+
* host-announced theme > undefined
|
|
108
|
+
*
|
|
109
|
+
* The middle leg is the ggui#589 fix: a render envelope carrying a
|
|
110
|
+
* per-app theme (`theme: {mode, cssVariables}`) but no top-level
|
|
111
|
+
* `themeMode` is still a slice-stamped mode opinion — before this,
|
|
112
|
+
* the base token ladder ignored it and painted the LIGHT token set
|
|
113
|
+
* under a dark overlay whenever the host (correctly, per the
|
|
114
|
+
* adapter-boundary doctrine in the native host helpers) announced no
|
|
115
|
+
* `hostContext.theme`: a light skeleton in a dark skin. The theme
|
|
116
|
+
* object's own `mode` already drove `color-scheme`; now it also
|
|
117
|
+
* selects the ladder.
|
|
118
|
+
*
|
|
119
|
+
* `undefined` still means "no opinion anywhere" — the renderer's
|
|
120
|
+
* default applies; never coerced to `'light'`.
|
|
121
|
+
*
|
|
122
|
+
* @internal — exported for unit tests; production caller is
|
|
123
|
+
* `buildOpts` inside `bootSequence`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resolveMountThemeMode(meta: {
|
|
126
|
+
readonly themeMode?: 'light' | 'dark';
|
|
127
|
+
readonly theme?: {
|
|
128
|
+
readonly mode: 'light' | 'dark';
|
|
129
|
+
readonly cssVariables?: Record<string, string>;
|
|
130
|
+
};
|
|
131
|
+
}): 'light' | 'dark' | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the mount's base-theme id from the slice (ggui#589 ask 3):
|
|
134
|
+
*
|
|
135
|
+
* stamped `themeId` > `theme.name` (the slice theme OBJECT's name)
|
|
136
|
+
* > undefined (renderer default ladder)
|
|
137
|
+
*
|
|
138
|
+
* The name leg makes a slice theme whose `name` matches a REGISTERED
|
|
139
|
+
* theme id select that theme as the BASE token ladder — the overlay's
|
|
140
|
+
* cssVariables still apply above it, so the base carries the full
|
|
141
|
+
* brand ramp coverage a sparse overlay cannot (the "only the mapped
|
|
142
|
+
* vars carry brand" store-frame class). An unregistered name is
|
|
143
|
+
* harmless by construction: `getScopedThemeCss` falls back to the
|
|
144
|
+
* default theme, which is byte-identical to the no-themeId path
|
|
145
|
+
* (`getScopedCssTokens` ≡ `getScopedThemeCss(default)`), so a slice
|
|
146
|
+
* naming its theme "My Custom" renders exactly as before.
|
|
147
|
+
*
|
|
148
|
+
* @internal — exported for unit tests; production caller is
|
|
149
|
+
* `buildOpts` inside `bootSequence`.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resolveMountThemeId(meta: {
|
|
152
|
+
readonly themeId?: string;
|
|
153
|
+
readonly theme?: {
|
|
154
|
+
readonly name?: string;
|
|
155
|
+
readonly mode?: 'light' | 'dark';
|
|
156
|
+
readonly cssVariables?: Record<string, string>;
|
|
157
|
+
};
|
|
158
|
+
}): string | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* The host's announced palette, read from the connected App's
|
|
161
|
+
* pre-merged hostContext and translated onto `--ggui-*` tokens by the
|
|
162
|
+
* host-palette bridge. `undefined` when no App is connected, the host
|
|
163
|
+
* sent no `styles.variables`, or nothing in it maps/survives
|
|
164
|
+
* sanitization — callers spread-skip the option, exactly like
|
|
165
|
+
* {@link hostAnnouncedThemeMode}.
|
|
166
|
+
*
|
|
167
|
+
* This is the palette input leg of host-theme adaptation (ggui#572,
|
|
168
|
+
* the color analog of #551's mode leg above): the spec `--color-*`
|
|
169
|
+
* variables land as inline custom properties on `<html>` via the
|
|
170
|
+
* canonical ext-apps helper, but nothing ggui renders consumes that
|
|
171
|
+
* vocabulary, and the scoped token block shadows root inheritance
|
|
172
|
+
* anyway — so without this mapping a host palette repaints nothing.
|
|
173
|
+
* `buildOpts` threads it onto the mount options; the renderer merges
|
|
174
|
+
* it as the fallback layer BENEATH the slice's own theme (ggui#573
|
|
175
|
+
* ruling: slice wins, host fallback).
|
|
176
|
+
*
|
|
177
|
+
* @internal — exported for unit tests (`setCurrentApp` injects the
|
|
178
|
+
* App); production caller is `buildOpts` inside `bootSequence`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function hostAnnouncedPalette(): Readonly<Record<string, string>> | undefined;
|
|
100
181
|
/**
|
|
101
182
|
* @internal — exported for unit tests to reset module state between
|
|
102
183
|
* scenarios.
|
|
@@ -733,7 +814,7 @@ export declare function requestDisplayModeInParent(args: {
|
|
|
733
814
|
* or honor an upstream `defaultPrevented` from a higher-priority
|
|
734
815
|
* handler.
|
|
735
816
|
*
|
|
736
|
-
* Idempotent: re-mounts (via `
|
|
817
|
+
* Idempotent: re-mounts (via `installPersistentToolResultListener`) call this
|
|
737
818
|
* helper again; the {@link anchorClickInterceptInstalled} guard makes
|
|
738
819
|
* subsequent calls a no-op.
|
|
739
820
|
*/
|