sunpeak 0.16.21 → 0.16.27

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 (90) hide show
  1. package/README.md +4 -3
  2. package/bin/commands/dev.mjs +120 -8
  3. package/bin/commands/new.mjs +6 -2
  4. package/bin/commands/start.mjs +7 -2
  5. package/bin/lib/get-port.mjs +60 -0
  6. package/bin/lib/live/browser-auth.mjs +161 -0
  7. package/bin/lib/live/chatgpt-config.d.mts +5 -0
  8. package/bin/lib/live/chatgpt-config.mjs +12 -0
  9. package/bin/lib/live/chatgpt-fixtures.d.mts +12 -0
  10. package/bin/lib/live/chatgpt-fixtures.mjs +25 -0
  11. package/bin/lib/live/chatgpt-page.mjs +210 -0
  12. package/bin/lib/live/global-setup.mjs +158 -0
  13. package/bin/lib/live/host-fixtures.mjs +61 -0
  14. package/bin/lib/live/host-page.mjs +294 -0
  15. package/bin/lib/live/live-config.d.mts +38 -0
  16. package/bin/lib/live/live-config.mjs +98 -0
  17. package/bin/lib/live/live-fixtures.d.mts +11 -0
  18. package/bin/lib/live/live-fixtures.mjs +102 -0
  19. package/bin/lib/live/test-config.d.mts +10 -0
  20. package/bin/lib/live/test-config.mjs +35 -0
  21. package/bin/lib/live/types.d.mts +54 -0
  22. package/bin/lib/live/utils.mjs +70 -0
  23. package/bin/lib/sandbox-server.mjs +304 -0
  24. package/bin/sunpeak.js +1 -1
  25. package/dist/chatgpt/chatgpt-conversation.d.ts +3 -7
  26. package/dist/chatgpt/globals.css +18 -0
  27. package/dist/chatgpt/index.cjs +1 -1
  28. package/dist/chatgpt/index.js +1 -1
  29. package/dist/claude/claude-conversation.d.ts +3 -2
  30. package/dist/claude/index.cjs +1 -1
  31. package/dist/claude/index.js +1 -1
  32. package/dist/{index-bKBBCBK6.cjs → index-BEWVLFfB.cjs} +2 -2
  33. package/dist/index-BEWVLFfB.cjs.map +1 -0
  34. package/dist/{index-CX6Z4bED.js → index-C6XYFOmh.js} +2 -2
  35. package/dist/index-C6XYFOmh.js.map +1 -0
  36. package/dist/{index-CKabCJyV.cjs → index-D0FsXP3Y.cjs} +2 -2
  37. package/dist/index-D0FsXP3Y.cjs.map +1 -0
  38. package/dist/{index-B4aC3vjH.js → index-Rg7SWjvl.js} +2 -2
  39. package/dist/index-Rg7SWjvl.js.map +1 -0
  40. package/dist/index.cjs +13 -5
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.js +13 -5
  43. package/dist/index.js.map +1 -1
  44. package/dist/mcp/favicon.d.ts +3 -1
  45. package/dist/mcp/index.cjs +90 -49
  46. package/dist/mcp/index.cjs.map +1 -1
  47. package/dist/mcp/index.d.ts +2 -2
  48. package/dist/mcp/index.js +90 -49
  49. package/dist/mcp/index.js.map +1 -1
  50. package/dist/mcp/production-server.d.ts +7 -1
  51. package/dist/mcp/types.d.ts +32 -1
  52. package/dist/simulator/hosts.d.ts +11 -2
  53. package/dist/simulator/iframe-resource.d.ts +8 -1
  54. package/dist/simulator/index.cjs +1 -1
  55. package/dist/simulator/index.js +1 -1
  56. package/dist/simulator/mcp-app-host.d.ts +17 -0
  57. package/dist/simulator/sandbox-proxy.d.ts +38 -0
  58. package/dist/simulator/simple-sidebar.d.ts +3 -1
  59. package/dist/simulator/simulator.d.ts +7 -1
  60. package/dist/simulator/use-simulator-state.d.ts +2 -4
  61. package/dist/{simulator-D8t-r7HH.js → simulator-B-CrMHVs.js} +504 -192
  62. package/dist/simulator-B-CrMHVs.js.map +1 -0
  63. package/dist/{simulator-FFNttkqL.cjs → simulator-Gc6n_fT4.cjs} +503 -191
  64. package/dist/simulator-Gc6n_fT4.cjs.map +1 -0
  65. package/dist/style.css +18 -0
  66. package/package.json +25 -1
  67. package/template/.sunpeak/dev.tsx +9 -3
  68. package/template/README.md +24 -2
  69. package/template/_gitignore +1 -0
  70. package/template/package.json +3 -2
  71. package/template/playwright.config.ts +34 -6
  72. package/template/src/server.ts +16 -2
  73. package/template/src/tools/show-albums.ts +17 -0
  74. package/template/tests/e2e/albums.spec.ts +37 -5
  75. package/template/tests/e2e/carousel.spec.ts +6 -6
  76. package/template/tests/e2e/global-setup.ts +6 -21
  77. package/template/tests/e2e/map.spec.ts +11 -11
  78. package/template/tests/e2e/review.spec.ts +24 -24
  79. package/template/tests/live/albums.spec.ts +53 -0
  80. package/template/tests/live/carousel.spec.ts +52 -0
  81. package/template/tests/live/map.spec.ts +31 -0
  82. package/template/tests/live/playwright.config.ts +3 -0
  83. package/template/tests/live/review.spec.ts +54 -0
  84. package/template/vitest.config.ts +1 -1
  85. package/dist/index-B4aC3vjH.js.map +0 -1
  86. package/dist/index-CKabCJyV.cjs.map +0 -1
  87. package/dist/index-CX6Z4bED.js.map +0 -1
  88. package/dist/index-bKBBCBK6.cjs.map +0 -1
  89. package/dist/simulator-D8t-r7HH.js.map +0 -1
  90. package/dist/simulator-FFNttkqL.cjs.map +0 -1
@@ -0,0 +1,70 @@
1
+ import { readFileSync, rmSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { createRequire } from 'module';
4
+
5
+ /**
6
+ * Recursively remove a directory (rm -rf equivalent).
7
+ */
8
+ export function rimrafSync(dir) {
9
+ rmSync(dir, { recursive: true, force: true });
10
+ }
11
+
12
+ /**
13
+ * Browser launch args that bypass Cloudflare/ChatGPT bot detection.
14
+ * Used in browser-auth, global-setup, and live-config.
15
+ */
16
+ export const ANTI_BOT_ARGS = [
17
+ '--disable-blink-features=AutomationControlled',
18
+ '--no-first-run',
19
+ '--no-default-browser-check',
20
+ ];
21
+
22
+ /**
23
+ * Real Chrome user agent string to avoid Cloudflare challenges.
24
+ * Update periodically to match the Playwright-bundled Chromium version.
25
+ */
26
+ export const CHROME_USER_AGENT =
27
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
28
+
29
+ /**
30
+ * Resolve @playwright/test from the user's project via CJS require.
31
+ * Use for non-test code (global-setup, config) where duplicate instances don't matter.
32
+ */
33
+ export function resolvePlaywright(projectRoot) {
34
+ const require = createRequire(join(projectRoot, 'package.json'));
35
+ return require('@playwright/test');
36
+ }
37
+
38
+ /**
39
+ * Resolve @playwright/test ESM module from the user's project.
40
+ * Use for test fixtures that call test.extend() — avoids CJS/ESM duplicate module issues.
41
+ * Cached: safe to call multiple times.
42
+ */
43
+ let _cachedPlaywright = null;
44
+ let _cachedProjectRoot = null;
45
+ export async function resolvePlaywrightESM(projectRoot) {
46
+ if (_cachedPlaywright && _cachedProjectRoot === projectRoot) return _cachedPlaywright;
47
+ const require = createRequire(join(projectRoot, 'package.json'));
48
+ const playwrightPath = require.resolve('@playwright/test');
49
+ const mod = await import(playwrightPath);
50
+ // Dynamic import() of @playwright/test (CJS) puts the test function at mod.default.
51
+ // The test function doubles as the module namespace — test.extend, test.expect, etc.
52
+ // Normalize so callers can destructure { test, expect } directly.
53
+ const pw = mod.default || mod;
54
+ _cachedPlaywright = { test: pw, expect: pw.expect };
55
+ _cachedProjectRoot = projectRoot;
56
+ return _cachedPlaywright;
57
+ }
58
+
59
+ /**
60
+ * Read the app name from the project's package.json.
61
+ * Cached: safe to call multiple times.
62
+ */
63
+ let _cachedAppName = null;
64
+ let _cachedAppNameRoot = null;
65
+ export function getAppName(projectRoot) {
66
+ if (_cachedAppName && _cachedAppNameRoot === projectRoot) return _cachedAppName;
67
+ _cachedAppName = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8')).name;
68
+ _cachedAppNameRoot = projectRoot;
69
+ return _cachedAppName;
70
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Separate-origin sandbox server for the simulator's double-iframe architecture.
3
+ *
4
+ * Real hosts (ChatGPT, Claude) run the sandbox proxy iframe on a separate origin
5
+ * (e.g., web-sandbox.oaiusercontent.com). This server replicates that by serving
6
+ * the proxy HTML on a different localhost port, giving real origin isolation.
7
+ *
8
+ * This means:
9
+ * - window.top.location access from inside the iframe is blocked (cross-origin)
10
+ * - document.referrer is empty (cross-origin navigation)
11
+ * - The iframe sandbox attribute behaves identically to production
12
+ *
13
+ * The server is started by `sunpeak dev` and its URL is injected into the Simulator
14
+ * via `__SUNPEAK_SANDBOX_URL__`.
15
+ *
16
+ * NOTE: The proxy HTML and mock openai script are duplicated from sandbox-proxy.ts
17
+ * and mock-openai-runtime.ts because this file runs in Node.js at dev time and
18
+ * cannot import TypeScript modules. Keep them in sync when making changes.
19
+ */
20
+ import { createServer } from 'http';
21
+ import { getPort } from './get-port.mjs';
22
+
23
+ /**
24
+ * Start the sandbox proxy server on a separate port.
25
+ *
26
+ * @param {Object} options
27
+ * @param {number} [options.preferredPort=24680] - Port to try first
28
+ * @returns {Promise<{ url: string, port: number, close: () => Promise<void> }>}
29
+ */
30
+ export async function startSandboxServer({ preferredPort = 24680 } = {}) {
31
+ const port = await getPort(preferredPort);
32
+
33
+ const server = createServer((req, res) => {
34
+ const url = new URL(req.url, `http://localhost:${port}`);
35
+
36
+ if (url.pathname === '/proxy') {
37
+ const theme = url.searchParams.get('theme') || 'dark';
38
+ const platform = url.searchParams.get('platform') || '';
39
+ const html = generateProxyHtml(theme, platform);
40
+
41
+ res.writeHead(200, {
42
+ 'Content-Type': 'text/html; charset=utf-8',
43
+ 'Cache-Control': 'no-cache',
44
+ // No CORS headers needed — iframe src loads don't check CORS.
45
+ // PostMessage works cross-origin by design.
46
+ });
47
+ res.end(html);
48
+ return;
49
+ }
50
+
51
+ // Health check
52
+ if (url.pathname === '/health') {
53
+ res.writeHead(200, { 'Content-Type': 'application/json' });
54
+ res.end('{"status":"ok"}');
55
+ return;
56
+ }
57
+
58
+ res.writeHead(404);
59
+ res.end('Not found');
60
+ });
61
+
62
+ await new Promise((resolve, reject) => {
63
+ server.listen(port, () => resolve());
64
+ server.on('error', reject);
65
+ });
66
+
67
+ const sandboxUrl = `http://localhost:${port}`;
68
+
69
+ return {
70
+ url: sandboxUrl,
71
+ port,
72
+ close: () => new Promise((resolve) => server.close(resolve)),
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Generate the sandbox proxy HTML.
78
+ *
79
+ * This is the same proxy logic as sandbox-proxy.ts but as a plain string
80
+ * (no TypeScript imports needed at dev server runtime). The proxy:
81
+ * 1. Signals readiness via `ui/notifications/sandbox-proxy-ready`
82
+ * 2. Listens for resource content or URL to load into the inner iframe
83
+ * 3. Relays all PostMessage between parent and inner iframe
84
+ * 4. Optionally injects platform runtime scripts (e.g., mock window.openai)
85
+ */
86
+ function generateProxyHtml(theme, platform) {
87
+ const colorScheme = theme === 'light' ? 'light' : 'dark';
88
+
89
+ // Platform-specific runtime script (injected into the inner iframe)
90
+ let platformScript = 'null';
91
+ if (platform === 'chatgpt') {
92
+ platformScript = JSON.stringify(MOCK_OPENAI_SCRIPT);
93
+ }
94
+
95
+ return `<!DOCTYPE html>
96
+ <html style="color-scheme:${colorScheme}">
97
+ <head>
98
+ <meta name="color-scheme" content="${colorScheme}" />
99
+ <style>
100
+ html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
101
+ iframe { border: none; width: 100%; height: 100%; display: block; }
102
+ </style>
103
+ </head>
104
+ <body>
105
+ <script>
106
+ (function() {
107
+ var innerFrame = null;
108
+ var innerWindow = null;
109
+ var platformScript = ${platformScript};
110
+
111
+ // Relay messages between parent (host) and inner iframe (app)
112
+ window.addEventListener('message', function(event) {
113
+ var data = event.data;
114
+ if (!data || typeof data !== 'object') return;
115
+
116
+ if (event.source === window.parent) {
117
+ // sandbox-resource-ready: load HTML into inner iframe (scriptSrc/prod mode)
118
+ if (data.method === 'ui/notifications/sandbox-resource-ready' && data.params) {
119
+ createInnerFrame(data.params);
120
+ return;
121
+ }
122
+
123
+ // sunpeak/sandbox-load-src: load URL into inner iframe (src/dev mode)
124
+ if (data.method === 'sunpeak/sandbox-load-src' && data.params) {
125
+ createInnerFrameWithSrc(data.params);
126
+ return;
127
+ }
128
+
129
+ // Handle paint fence. Forward to the inner iframe and wait for its ack.
130
+ // The inner iframe has a fence responder (injected by the Vite dev page
131
+ // or embedded in the production HTML). If the ack arrives, relay it to
132
+ // the host. If not (cross-origin injection failed), fall back to a
133
+ // timeout-based ack after 150ms.
134
+ if (data.method === 'sunpeak/fence' && data.params) {
135
+ var fenceId = data.params.fenceId;
136
+ var acked = false;
137
+
138
+ // Listen for the inner iframe's fence-ack
139
+ var onAck = function(e) {
140
+ if (e.source !== innerWindow) return;
141
+ if (e.data && e.data.method === 'sunpeak/fence-ack' &&
142
+ e.data.params && e.data.params.fenceId === fenceId) {
143
+ acked = true;
144
+ window.removeEventListener('message', onAck);
145
+ window.parent.postMessage(e.data, '*');
146
+ }
147
+ };
148
+ window.addEventListener('message', onAck);
149
+
150
+ // Forward fence to inner iframe
151
+ if (innerWindow) {
152
+ try { innerWindow.postMessage(data, '*'); } catch(e) {}
153
+ }
154
+
155
+ // Fallback: if no ack within 150ms (fence responder not available),
156
+ // ack from the proxy after allowing time for the inner iframe to
157
+ // process the preceding hostContext change.
158
+ setTimeout(function() {
159
+ if (!acked) {
160
+ window.removeEventListener('message', onAck);
161
+ window.parent.postMessage({
162
+ jsonrpc: '2.0',
163
+ method: 'sunpeak/fence-ack',
164
+ params: { fenceId: fenceId }
165
+ }, '*');
166
+ }
167
+ }, 150);
168
+ return;
169
+ }
170
+
171
+ // Forward all other messages to the inner iframe
172
+ if (innerWindow) {
173
+ try { innerWindow.postMessage(data, '*'); } catch(e) { /* detached */ }
174
+ }
175
+ } else if (innerWindow && event.source === innerWindow) {
176
+ // Messages from the app -> forward to host
177
+ try { window.parent.postMessage(data, '*'); } catch(e) { /* detached */ }
178
+ }
179
+ });
180
+
181
+ function createInnerFrame(params) {
182
+ if (innerFrame) innerFrame.remove();
183
+
184
+ innerFrame = document.createElement('iframe');
185
+ innerFrame.sandbox = params.sandbox ||
186
+ 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
187
+ if (params.allow) innerFrame.allow = params.allow;
188
+ document.body.appendChild(innerFrame);
189
+ innerWindow = innerFrame.contentWindow;
190
+
191
+ var doc = innerFrame.contentDocument;
192
+ if (doc && params.html) {
193
+ doc.open();
194
+ doc.write(params.html);
195
+ doc.close();
196
+ }
197
+ }
198
+
199
+ function createInnerFrameWithSrc(params) {
200
+ if (innerFrame) innerFrame.remove();
201
+
202
+ innerFrame = document.createElement('iframe');
203
+ innerFrame.sandbox =
204
+ 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
205
+ if (params.allow) innerFrame.allow = params.allow;
206
+ innerFrame.src = params.src;
207
+ innerFrame.style.height = '100%';
208
+
209
+ if (params.theme) {
210
+ innerFrame.style.colorScheme = params.theme;
211
+ }
212
+
213
+ innerFrame.addEventListener('load', function() {
214
+ innerWindow = innerFrame.contentWindow;
215
+
216
+ // Inject platform runtime (e.g., mock window.openai for ChatGPT)
217
+ if (platformScript && innerWindow) {
218
+ try {
219
+ var pScript = innerFrame.contentDocument.createElement('script');
220
+ pScript.textContent = platformScript;
221
+ innerFrame.contentDocument.head.appendChild(pScript);
222
+ } catch(e) { /* cross-origin */ }
223
+ }
224
+
225
+ // Inject paint fence responder
226
+ try {
227
+ var fenceScript = innerFrame.contentDocument.createElement('script');
228
+ fenceScript.setAttribute('data-sunpeak-fence', '');
229
+ fenceScript.textContent = PAINT_FENCE_SCRIPT;
230
+ innerFrame.contentDocument.head.appendChild(fenceScript);
231
+ } catch(e) { /* cross-origin */ }
232
+
233
+ // Inject background rule
234
+ if (params.theme) {
235
+ try {
236
+ innerFrame.contentDocument.documentElement.style.colorScheme = params.theme;
237
+ var bgStyle = innerFrame.contentDocument.createElement('style');
238
+ bgStyle.setAttribute('data-sunpeak-bg', '');
239
+ bgStyle.textContent = 'html { background-color: var(--color-background-primary, Canvas); }';
240
+ innerFrame.contentDocument.head.appendChild(bgStyle);
241
+ } catch(e) { /* cross-origin */ }
242
+ }
243
+
244
+ // Inject style variables
245
+ if (params.styleVars) {
246
+ try {
247
+ var root = innerFrame.contentDocument.documentElement;
248
+ for (var key in params.styleVars) {
249
+ if (params.styleVars[key]) root.style.setProperty(key, params.styleVars[key]);
250
+ }
251
+ } catch(e) { /* cross-origin */ }
252
+ }
253
+
254
+ innerFrame.style.opacity = '1';
255
+ innerFrame.style.transition = 'opacity 100ms';
256
+ });
257
+
258
+ innerFrame.style.opacity = '0';
259
+ document.body.appendChild(innerFrame);
260
+ innerWindow = innerFrame.contentWindow;
261
+ }
262
+
263
+ var PAINT_FENCE_SCRIPT = 'window.addEventListener("message",function(e){' +
264
+ 'if(e.data&&e.data.method==="sunpeak/fence"){' +
265
+ 'var fid=e.data.params&&e.data.params.fenceId;' +
266
+ 'requestAnimationFrame(function(){' +
267
+ 'e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");' +
268
+ '});}});';
269
+
270
+ // Signal readiness to the host
271
+ setTimeout(function() {
272
+ window.parent.postMessage({
273
+ jsonrpc: '2.0',
274
+ method: 'ui/notifications/sandbox-proxy-ready',
275
+ params: {}
276
+ }, '*');
277
+ }, 0);
278
+ })();
279
+ </script>
280
+ </body>
281
+ </html>`;
282
+ }
283
+
284
+ /**
285
+ * Mock OpenAI runtime script — same as mock-openai-runtime.ts MOCK_OPENAI_RUNTIME_SCRIPT.
286
+ * Duplicated here to avoid TypeScript import in the Node.js dev server.
287
+ */
288
+ const MOCK_OPENAI_SCRIPT = [
289
+ 'window.openai={',
290
+ 'uploadFile:function(f){console.log("[Simulator] uploadFile:",f.name);',
291
+ 'return Promise.resolve({fileId:"sim_file_"+Date.now()})},',
292
+ 'getFileDownloadUrl:function(p){console.log("[Simulator] getFileDownloadUrl:",p.fileId);',
293
+ 'return Promise.resolve({downloadUrl:"https://simulator.local/files/"+p.fileId})},',
294
+ 'requestModal:function(p){console.log("[Simulator] requestModal:",JSON.stringify(p));',
295
+ 'return Promise.resolve()},',
296
+ 'requestCheckout:function(s){console.log("[Simulator] requestCheckout:",JSON.stringify(s));',
297
+ 'return Promise.resolve({id:"sim_order_"+Date.now(),checkout_session_id:s.id||"sim_session",status:"completed"})},',
298
+ 'requestClose:function(){console.log("[Simulator] requestClose")},',
299
+ 'requestDisplayMode:function(p){console.log("[Simulator] requestDisplayMode:",p.mode);',
300
+ 'return Promise.resolve()},',
301
+ 'sendFollowUpMessage:function(p){console.log("[Simulator] sendFollowUpMessage:",p.prompt)},',
302
+ 'openExternal:function(p){console.log("[Simulator] openExternal:",p.href);window.open(p.href,"_blank")}',
303
+ '};',
304
+ ].join('');
package/bin/sunpeak.js CHANGED
@@ -104,7 +104,7 @@ Usage:
104
104
  sunpeak --version Show version number
105
105
 
106
106
  Resources: ${resources.join(', ')} (comma/space separated)
107
- Example: sunpeak new my-app "${resources.slice(0, 2).join(',')}"
107
+ Example: sunpeak new sunpeak-app "${resources.slice(0, 2).join(',')}"
108
108
  `);
109
109
  }
110
110
  break;
@@ -11,14 +11,10 @@ interface ConversationProps {
11
11
  appName?: string;
12
12
  appIcon?: string;
13
13
  userMessage?: string;
14
- /**
15
- * Whether the content is transitioning between display modes.
16
- * When true, the content area is hidden (opacity 0) to prevent the pip
17
- * border from flashing at a stale height before the iframe resizes.
18
- */
19
- isTransitioning?: boolean;
20
14
  /** Optional action element rendered in the conversation header (e.g., Run button) */
21
15
  headerAction?: React.ReactNode;
16
+ /** Called when the content container width changes */
17
+ onContentWidthChange?: (width: number) => void;
22
18
  }
23
19
  /**
24
20
  * Conversation layout that renders children (iframe) at a stable tree position.
@@ -33,5 +29,5 @@ interface ConversationProps {
33
29
  * - **fullscreen**: content wrapper becomes `position: fixed` covering the viewport;
34
30
  * fullscreen chrome (header/footer) rendered as a separate fixed overlay
35
31
  */
36
- export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, headerAction, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, headerAction, onContentWidthChange, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
37
33
  export {};
@@ -151,6 +151,7 @@
151
151
  --tw-backdrop-saturate: initial;
152
152
  --tw-backdrop-sepia: initial;
153
153
  --tw-duration: initial;
154
+ --tw-ease: initial;
154
155
  }
155
156
  }
156
157
  }
@@ -191,6 +192,7 @@
191
192
  --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
192
193
  --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
193
194
  --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
195
+ --ease-out: cubic-bezier(0, 0, .2, 1);
194
196
  --animate-spin: spin 1s linear infinite;
195
197
  --blur-sm: 8px;
196
198
  --default-transition-duration: .15s;
@@ -1847,6 +1849,11 @@
1847
1849
  transition-duration: .2s;
1848
1850
  }
1849
1851
 
1852
+ .ease-out {
1853
+ --tw-ease: var(--ease-out);
1854
+ transition-timing-function: var(--ease-out);
1855
+ }
1856
+
1850
1857
  .outline-none {
1851
1858
  --tw-outline-style: none;
1852
1859
  outline-style: none;
@@ -2037,6 +2044,12 @@
2037
2044
  }
2038
2045
  }
2039
2046
 
2047
+ @media (min-width: 1440px) {
2048
+ .min-\[1440px\]\:max-w-\[48rem\] {
2049
+ max-width: 48rem;
2050
+ }
2051
+ }
2052
+
2040
2053
  @media (min-width: 40rem) {
2041
2054
  .sm\:start-0 {
2042
2055
  inset-inline-start: calc(var(--spacing) * 0);
@@ -2679,6 +2692,11 @@
2679
2692
  inherits: false
2680
2693
  }
2681
2694
 
2695
+ @property --tw-ease {
2696
+ syntax: "*";
2697
+ inherits: false
2698
+ }
2699
+
2682
2700
  @keyframes spin {
2683
2701
  to {
2684
2702
  transform: rotate(360deg);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulator = require("../simulator-FFNttkqL.cjs");
3
+ const simulator = require("../simulator-Gc6n_fT4.cjs");
4
4
  const simulatorUrl = require("../simulator-url-DcSYRl-P.cjs");
5
5
  const discovery = require("../discovery-D1gpaVz4.cjs");
6
6
  exports.IframeResource = simulator.IframeResource;
@@ -1,4 +1,4 @@
1
- import { I, M, a, S, T, j, m, n } from "../simulator-D8t-r7HH.js";
1
+ import { I, M, a, S, T, j, m, n } from "../simulator-B-CrMHVs.js";
2
2
  import { c } from "../simulator-url-j_XV3EoP.js";
3
3
  import { b, a as a2, c as c2, d, e, f, g, h, i, t } from "../discovery-BVqD-JsT.js";
4
4
  export {
@@ -11,9 +11,10 @@ interface ClaudeConversationProps {
11
11
  appName?: string;
12
12
  appIcon?: string;
13
13
  userMessage?: string;
14
- isTransitioning?: boolean;
15
14
  /** Optional action element rendered in the conversation header (e.g., Run button) */
16
15
  headerAction?: React.ReactNode;
16
+ /** Called when the content container width changes */
17
+ onContentWidthChange?: (width: number) => void;
17
18
  }
18
19
  /**
19
20
  * Claude conversation shell — mimics Claude's chat UI chrome.
@@ -21,5 +22,5 @@ interface ClaudeConversationProps {
21
22
  * All three display modes (inline, pip, fullscreen) share the same React tree
22
23
  * shape so that the iframe never unmounts when switching modes.
23
24
  */
24
- export declare function ClaudeConversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, headerAction, }: ClaudeConversationProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function ClaudeConversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, headerAction, onContentWidthChange, }: ClaudeConversationProps): import("react/jsx-runtime").JSX.Element;
25
26
  export {};
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulator = require("../simulator-FFNttkqL.cjs");
3
+ const simulator = require("../simulator-Gc6n_fT4.cjs");
4
4
  exports.Simulator = simulator.Simulator;
5
5
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { S } from "../simulator-D8t-r7HH.js";
1
+ import { S } from "../simulator-B-CrMHVs.js";
2
2
  export {
3
3
  S as Simulator
4
4
  };
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const simulator = require("./simulator-FFNttkqL.cjs");
2
+ const simulator = require("./simulator-Gc6n_fT4.cjs");
3
3
  const simulatorUrl = require("./simulator-url-DcSYRl-P.cjs");
4
4
  const discovery = require("./discovery-D1gpaVz4.cjs");
5
5
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -25,4 +25,4 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
25
25
  useThemeContext: simulator.useThemeContext
26
26
  }, Symbol.toStringTag, { value: "Module" }));
27
27
  exports.index = index;
28
- //# sourceMappingURL=index-bKBBCBK6.cjs.map
28
+ //# sourceMappingURL=index-BEWVLFfB.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BEWVLFfB.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { I as IframeResource, M as McpAppHost, a as SCREEN_WIDTHS, S as Simulator, T as ThemeProvider, j as extractResourceCSP, m as resolveServerToolResult, n as useThemeContext } from "./simulator-D8t-r7HH.js";
1
+ import { I as IframeResource, M as McpAppHost, a as SCREEN_WIDTHS, S as Simulator, T as ThemeProvider, j as extractResourceCSP, m as resolveServerToolResult, n as useThemeContext } from "./simulator-B-CrMHVs.js";
2
2
  import { c as createSimulatorUrl } from "./simulator-url-j_XV3EoP.js";
3
3
  import { b as buildDevSimulations, a as buildResourceMap, c as buildSimulations, d as createResourceExports, e as extractResourceKey, f as extractSimulationKey, g as findResourceDirs, h as findResourceKey, i as getComponentName, t as toPascalCase } from "./discovery-BVqD-JsT.js";
4
4
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -26,4 +26,4 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
26
26
  export {
27
27
  index as i
28
28
  };
29
- //# sourceMappingURL=index-CX6Z4bED.js.map
29
+ //# sourceMappingURL=index-C6XYFOmh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-C6XYFOmh.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const simulator = require("./simulator-FFNttkqL.cjs");
2
+ const simulator = require("./simulator-Gc6n_fT4.cjs");
3
3
  const simulatorUrl = require("./simulator-url-DcSYRl-P.cjs");
4
4
  const discovery = require("./discovery-D1gpaVz4.cjs");
5
5
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -37,4 +37,4 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
37
37
  useThemeContext: simulator.useThemeContext
38
38
  }, Symbol.toStringTag, { value: "Module" }));
39
39
  exports.index = index;
40
- //# sourceMappingURL=index-CKabCJyV.cjs.map
40
+ //# sourceMappingURL=index-D0FsXP3Y.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-D0FsXP3Y.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { I as IframeResource, M as McpAppHost, a as SCREEN_WIDTHS, b as SidebarCheckbox, c as SidebarCollapsibleControl, d as SidebarControl, e as SidebarInput, f as SidebarSelect, g as SidebarTextarea, h as SidebarToggle, i as SimpleSidebar, S as Simulator, T as ThemeProvider, j as extractResourceCSP, k as getHostShell, l as getRegisteredHosts, r as registerHostShell, m as resolveServerToolResult, u as useSimulatorState, n as useThemeContext } from "./simulator-D8t-r7HH.js";
1
+ import { I as IframeResource, M as McpAppHost, a as SCREEN_WIDTHS, b as SidebarCheckbox, c as SidebarCollapsibleControl, d as SidebarControl, e as SidebarInput, f as SidebarSelect, g as SidebarTextarea, h as SidebarToggle, i as SimpleSidebar, S as Simulator, T as ThemeProvider, j as extractResourceCSP, k as getHostShell, l as getRegisteredHosts, r as registerHostShell, m as resolveServerToolResult, u as useSimulatorState, n as useThemeContext } from "./simulator-B-CrMHVs.js";
2
2
  import { c as createSimulatorUrl } from "./simulator-url-j_XV3EoP.js";
3
3
  import { b as buildDevSimulations, a as buildResourceMap, c as buildSimulations, d as createResourceExports, e as extractResourceKey, f as extractSimulationKey, g as findResourceDirs, h as findResourceKey, i as getComponentName, t as toPascalCase } from "./discovery-BVqD-JsT.js";
4
4
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -38,4 +38,4 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
38
38
  export {
39
39
  index as i
40
40
  };
41
- //# sourceMappingURL=index-B4aC3vjH.js.map
41
+ //# sourceMappingURL=index-Rg7SWjvl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-Rg7SWjvl.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.cjs CHANGED
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const useApp = require("./use-app-D09O2swh.cjs");
4
4
  const host_index = require("./host/index.cjs");
5
- const simulator_index = require("./index-CKabCJyV.cjs");
6
- const chatgpt_index = require("./index-bKBBCBK6.cjs");
5
+ const simulator_index = require("./index-D0FsXP3Y.cjs");
6
+ const chatgpt_index = require("./index-BEWVLFfB.cjs");
7
7
  const jsxRuntime = require("react/jsx-runtime");
8
8
  const React = require("react");
9
- const simulator = require("./simulator-FFNttkqL.cjs");
9
+ const simulator = require("./simulator-Gc6n_fT4.cjs");
10
10
  const discovery = require("./discovery-D1gpaVz4.cjs");
11
11
  const protocol = require("./protocol-DkDHRwOW.cjs");
12
12
  function _interopNamespaceDefault(e) {
@@ -177,6 +177,7 @@ function getRegistry$1(app) {
177
177
  if (ctx?.styles?.css?.fonts) {
178
178
  useApp.TQ(ctx.styles.css.fonts);
179
179
  }
180
+ let debounceTimer = null;
180
181
  app.onhostcontextchanged = () => {
181
182
  const ctx2 = app.getHostContext();
182
183
  if (ctx2?.theme) {
@@ -186,7 +187,11 @@ function getRegistry$1(app) {
186
187
  if (ctx2?.styles?.css?.fonts) {
187
188
  useApp.TQ(ctx2.styles.css.fonts);
188
189
  }
189
- for (const fn of subs) fn();
190
+ if (debounceTimer) clearTimeout(debounceTimer);
191
+ debounceTimer = setTimeout(() => {
192
+ debounceTimer = null;
193
+ for (const fn of subs) fn();
194
+ }, 50);
190
195
  };
191
196
  }
192
197
  return subs;
@@ -373,7 +378,7 @@ const SafeArea = React.forwardRef(function SafeArea2({ children, style, ...props
373
378
  const viewport = useViewport();
374
379
  const displayMode = useDisplayMode();
375
380
  const isFullscreen = displayMode === "fullscreen";
376
- const height = viewport?.height ?? (isFullscreen ? "100dvh" : void 0);
381
+ const height = isFullscreen ? "100dvh" : void 0;
377
382
  return /* @__PURE__ */ jsxRuntime.jsx(
378
383
  "div",
379
384
  {
@@ -386,7 +391,10 @@ const SafeArea = React.forwardRef(function SafeArea2({ children, style, ...props
386
391
  paddingLeft: safeArea.left || void 0,
387
392
  paddingRight: safeArea.right || void 0,
388
393
  height,
394
+ // overflow:hidden ensures content doesn't escape the maxHeight boundary,
395
+ // which also lets apps fill the space with their own scrollable container.
389
396
  maxHeight: viewport?.maxHeight,
397
+ overflow: viewport?.maxHeight != null ? "hidden" : void 0,
390
398
  width: viewport?.width,
391
399
  maxWidth: viewport?.maxWidth,
392
400
  ...style