@ggui-ai/iframe-runtime 0.10.0 → 0.11.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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Spec→ggui token bridge (ggui#572).
3
+ *
4
+ * MCP Apps hosts announce their palette as spec `--color-*` custom
5
+ * properties in `hostContext.styles.variables`. The canonical
6
+ * `@modelcontextprotocol/ext-apps` helper applies them as inline
7
+ * custom properties on the iframe `<html>` — which is correct spec
8
+ * behavior but repaints nothing ggui renders: generated UI, the
9
+ * design system, and the shell chrome consume exclusively `--ggui-*`
10
+ * tokens, and the renderer's scoped in-body token block shadows
11
+ * root-level inheritance for every token it defines anyway.
12
+ *
13
+ * This module translates the spec vocabulary onto the ggui token
14
+ * ladder so the renderer can merge the host's palette INTO the scoped
15
+ * block — as the fallback layer beneath the app's own theme (#573
16
+ * ruling: the slice's stamped theme wins; the host palette fills the
17
+ * slots no operator repainted).
18
+ *
19
+ * Coverage is deliberately PARTIAL in both directions:
20
+ *
21
+ * - Spec keys with no faithful ggui counterpart (ghost/disabled/
22
+ * inverse variants, ring colors, fonts, radii, shadows) are
23
+ * dropped — they keep applying via the ext-apps inline-DOM path
24
+ * for any non-ggui content, and inventing a mapping would repaint
25
+ * tokens with the WRONG semantic role.
26
+ * - ggui tokens the spec doesn't express (the primary accent scale,
27
+ * containers beyond error, the neutral ladder) keep their
28
+ * mode-resolved base values — the host-announced color MODE
29
+ * (ggui#551) already selects the coherent light/dark ladder these
30
+ * slots resolve from.
31
+ */
32
+ /**
33
+ * Map a host-announced `styles.variables` record onto `--ggui-*`
34
+ * declarations ready for the renderer's scoped token block.
35
+ *
36
+ * Unknown keys and unsafe/blank values are dropped silently (the same
37
+ * tolerant posture as the rest of host-context handling — a partial
38
+ * palette is a partial repaint, never an error). Returns `undefined`
39
+ * when nothing survives so callers can spread-skip the option.
40
+ *
41
+ * @public
42
+ */
43
+ export declare function mapHostPaletteToGguiVars(variables: Readonly<Record<string, string | undefined>> | undefined): Readonly<Record<string, string>> | undefined;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Spec→ggui token bridge (ggui#572).
3
+ *
4
+ * MCP Apps hosts announce their palette as spec `--color-*` custom
5
+ * properties in `hostContext.styles.variables`. The canonical
6
+ * `@modelcontextprotocol/ext-apps` helper applies them as inline
7
+ * custom properties on the iframe `<html>` — which is correct spec
8
+ * behavior but repaints nothing ggui renders: generated UI, the
9
+ * design system, and the shell chrome consume exclusively `--ggui-*`
10
+ * tokens, and the renderer's scoped in-body token block shadows
11
+ * root-level inheritance for every token it defines anyway.
12
+ *
13
+ * This module translates the spec vocabulary onto the ggui token
14
+ * ladder so the renderer can merge the host's palette INTO the scoped
15
+ * block — as the fallback layer beneath the app's own theme (#573
16
+ * ruling: the slice's stamped theme wins; the host palette fills the
17
+ * slots no operator repainted).
18
+ *
19
+ * Coverage is deliberately PARTIAL in both directions:
20
+ *
21
+ * - Spec keys with no faithful ggui counterpart (ghost/disabled/
22
+ * inverse variants, ring colors, fonts, radii, shadows) are
23
+ * dropped — they keep applying via the ext-apps inline-DOM path
24
+ * for any non-ggui content, and inventing a mapping would repaint
25
+ * tokens with the WRONG semantic role.
26
+ * - ggui tokens the spec doesn't express (the primary accent scale,
27
+ * containers beyond error, the neutral ladder) keep their
28
+ * mode-resolved base values — the host-announced color MODE
29
+ * (ggui#551) already selects the coherent light/dark ladder these
30
+ * slots resolve from.
31
+ */
32
+ /**
33
+ * Spec `--color-*` key → ggui token, one entry per slot whose semantic
34
+ * role matches 1:1. Semantic status text maps to the ladder's `-500`
35
+ * stop — the "live" stop tone slots and Alert actually read; a flat
36
+ * `--ggui-color-error` target would repaint nothing (no theme emits
37
+ * flat semantic tokens).
38
+ */
39
+ const SPEC_TO_GGUI = {
40
+ '--color-background-primary': '--ggui-color-surface',
41
+ '--color-background-secondary': '--ggui-color-surfaceVariant',
42
+ '--color-background-tertiary': '--ggui-color-container',
43
+ '--color-background-danger': '--ggui-color-errorContainer',
44
+ '--color-text-primary': '--ggui-color-onSurface',
45
+ '--color-text-secondary': '--ggui-color-onSurfaceVariant',
46
+ '--color-text-tertiary': '--ggui-color-neutral-500',
47
+ '--color-text-danger': '--ggui-color-error-500',
48
+ '--color-text-success': '--ggui-color-success-500',
49
+ '--color-text-warning': '--ggui-color-warning-500',
50
+ '--color-text-info': '--ggui-color-info-500',
51
+ '--color-border-primary': '--ggui-color-outline',
52
+ '--color-border-secondary': '--ggui-color-outlineVariant',
53
+ };
54
+ /**
55
+ * Host palette values arrive over postMessage and are string-joined
56
+ * into a `<style>` element by the renderer — unlike the app's own
57
+ * theme overlay they are NOT pre-validated by the wire parser, so
58
+ * this is the sanitization gate. Conservative deny-list: anything
59
+ * that could close the declaration (`;`), the rule (`{`/`}`), or the
60
+ * style element (`<`/`>`), smuggle strings/escapes, comment out the
61
+ * rest of the sheet (`/*`), or trigger a fetch (`url(`) drops the
62
+ * value. Plain color syntax — hex, rgb()/hsl()/oklch()/color-mix(),
63
+ * named colors — passes untouched.
64
+ */
65
+ function isSafeCssValue(value) {
66
+ if (value.length === 0 || value.length > 256)
67
+ return false;
68
+ if (/[;{}<>"'`\\]/.test(value))
69
+ return false;
70
+ if (/\/\*/.test(value))
71
+ return false;
72
+ if (/url\s*\(/i.test(value))
73
+ return false;
74
+ // Control characters (incl. newlines) have no place in a color value.
75
+ // eslint-disable-next-line no-control-regex
76
+ if (/[\u0000-\u001f\u007f]/.test(value))
77
+ return false;
78
+ return true;
79
+ }
80
+ /**
81
+ * Map a host-announced `styles.variables` record onto `--ggui-*`
82
+ * declarations ready for the renderer's scoped token block.
83
+ *
84
+ * Unknown keys and unsafe/blank values are dropped silently (the same
85
+ * tolerant posture as the rest of host-context handling — a partial
86
+ * palette is a partial repaint, never an error). Returns `undefined`
87
+ * when nothing survives so callers can spread-skip the option.
88
+ *
89
+ * @public
90
+ */
91
+ export function mapHostPaletteToGguiVars(variables) {
92
+ if (variables === undefined)
93
+ return undefined;
94
+ const mapped = {};
95
+ let count = 0;
96
+ for (const [specKey, gguiKey] of Object.entries(SPEC_TO_GGUI)) {
97
+ const raw = variables[specKey];
98
+ if (typeof raw !== 'string')
99
+ continue;
100
+ const value = raw.trim();
101
+ if (!isSafeCssValue(value))
102
+ continue;
103
+ mapped[gguiKey] = value;
104
+ count += 1;
105
+ }
106
+ return count > 0 ? mapped : undefined;
107
+ }