sunpeak 0.16.21 → 0.16.24

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 (68) hide show
  1. package/README.md +4 -3
  2. package/bin/commands/dev.mjs +22 -3
  3. package/bin/commands/new.mjs +6 -2
  4. package/bin/commands/start.mjs +4 -0
  5. package/bin/lib/get-port.mjs +60 -0
  6. package/bin/lib/live/browser-auth.mjs +125 -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 +150 -0
  13. package/bin/lib/live/host-fixtures.mjs +61 -0
  14. package/bin/lib/live/host-page.mjs +242 -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/sunpeak.js +1 -1
  24. package/dist/chatgpt/index.cjs +1 -1
  25. package/dist/chatgpt/index.js +1 -1
  26. package/dist/claude/index.cjs +1 -1
  27. package/dist/claude/index.js +1 -1
  28. package/dist/{index-CX6Z4bED.js → index-B7Qw3Vhh.js} +2 -2
  29. package/dist/index-B7Qw3Vhh.js.map +1 -0
  30. package/dist/{index-B4aC3vjH.js → index-BEHP_bM8.js} +2 -2
  31. package/dist/index-BEHP_bM8.js.map +1 -0
  32. package/dist/{index-bKBBCBK6.cjs → index-SfudQ9Y_.cjs} +2 -2
  33. package/dist/index-SfudQ9Y_.cjs.map +1 -0
  34. package/dist/{index-CKabCJyV.cjs → index-XKHXfBiD.cjs} +2 -2
  35. package/dist/index-XKHXfBiD.cjs.map +1 -0
  36. package/dist/index.cjs +13 -5
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.js +13 -5
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp/index.cjs +38 -13
  41. package/dist/mcp/index.cjs.map +1 -1
  42. package/dist/mcp/index.js +38 -13
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/types.d.ts +2 -0
  45. package/dist/simulator/index.cjs +1 -1
  46. package/dist/simulator/index.js +1 -1
  47. package/dist/simulator/simple-sidebar.d.ts +3 -1
  48. package/dist/{simulator-D8t-r7HH.js → simulator-BCq2iOT-.js} +67 -27
  49. package/dist/simulator-BCq2iOT-.js.map +1 -0
  50. package/dist/{simulator-FFNttkqL.cjs → simulator-DRUsm6IZ.cjs} +67 -27
  51. package/dist/simulator-DRUsm6IZ.cjs.map +1 -0
  52. package/package.json +25 -1
  53. package/template/README.md +24 -2
  54. package/template/_gitignore +1 -0
  55. package/template/package.json +3 -2
  56. package/template/playwright.config.ts +24 -1
  57. package/template/tests/live/albums.spec.ts +53 -0
  58. package/template/tests/live/carousel.spec.ts +52 -0
  59. package/template/tests/live/map.spec.ts +31 -0
  60. package/template/tests/live/playwright.config.ts +3 -0
  61. package/template/tests/live/review.spec.ts +54 -0
  62. package/template/vitest.config.ts +1 -1
  63. package/dist/index-B4aC3vjH.js.map +0 -1
  64. package/dist/index-CKabCJyV.cjs.map +0 -1
  65. package/dist/index-CX6Z4bED.js.map +0 -1
  66. package/dist/index-bKBBCBK6.cjs.map +0 -1
  67. package/dist/simulator-D8t-r7HH.js.map +0 -1
  68. package/dist/simulator-FFNttkqL.cjs.map +0 -1
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Host-agnostic Playwright fixtures for live testing.
3
+ *
4
+ * Users import from 'sunpeak/test' and get a `live` fixture that
5
+ * automatically resolves the correct host page object based on the
6
+ * Playwright project name. Adding a new host never changes user imports.
7
+ *
8
+ * Usage:
9
+ * import { test, expect } from 'sunpeak/test';
10
+ *
11
+ * test('my resource renders', async ({ live }) => {
12
+ * const app = await live.invoke('show me something');
13
+ * await expect(app.locator('img').first()).toBeVisible();
14
+ * });
15
+ */
16
+ import { resolvePlaywrightESM, getAppName } from './utils.mjs';
17
+
18
+ const projectRoot = process.env.SUNPEAK_PROJECT_ROOT || process.cwd();
19
+ const { test: base, expect } = await resolvePlaywrightESM(projectRoot);
20
+ const appName = getAppName(projectRoot);
21
+
22
+ /**
23
+ * Registry of host page classes and their message formatters.
24
+ * Classes are lazy-loaded and cached to avoid importing all hosts when only one is used.
25
+ */
26
+ const HOST_REGISTRY = {
27
+ chatgpt: {
28
+ _cached: null,
29
+ load() {
30
+ this._cached ??= import('./chatgpt-page.mjs').then((m) => m.ChatGPTPage);
31
+ return this._cached;
32
+ },
33
+ formatMessage: (name, text) => `/${name} ${text}`,
34
+ },
35
+ // Future: claude: { _cached: null, load() { ... }, formatMessage: ... }
36
+ };
37
+
38
+ /**
39
+ * Resolve the host ID from the current Playwright project name.
40
+ * Project names match the host ID directly (e.g., 'chatgpt').
41
+ */
42
+ function resolveHostId(projectName) {
43
+ if (!projectName) return 'chatgpt';
44
+ for (const hostId of Object.keys(HOST_REGISTRY)) {
45
+ if (projectName.startsWith(hostId)) return hostId;
46
+ }
47
+ return 'chatgpt';
48
+ }
49
+
50
+ const test = base.extend({
51
+ live: async ({ page }, use, testInfo) => {
52
+ const hostId = resolveHostId(testInfo.project.name);
53
+ const hostEntry = HOST_REGISTRY[hostId];
54
+ if (!hostEntry) {
55
+ throw new Error(`Unknown live test host: "${hostId}". Supported: ${Object.keys(HOST_REGISTRY).join(', ')}`);
56
+ }
57
+
58
+ const HostPageClass = await hostEntry.load();
59
+ const hostPage = new HostPageClass(page);
60
+ await hostPage.verifyLoggedIn();
61
+
62
+ const { formatMessage } = hostEntry;
63
+ const fixture = Object.create(hostPage);
64
+ fixture.page = page;
65
+
66
+ if (formatMessage) {
67
+ fixture.sendMessage = async (text) => hostPage.sendMessage(formatMessage(appName, text));
68
+ }
69
+ fixture.sendRawMessage = hostPage.sendMessage.bind(hostPage);
70
+ fixture.invoke = async (prompt, options) => {
71
+ await hostPage.startNewChat();
72
+ const message = formatMessage ? formatMessage(appName, prompt) : prompt;
73
+ await hostPage.sendMessage(message);
74
+ return hostPage.waitForAppIframe(options);
75
+ };
76
+
77
+ /**
78
+ * Switch the browser's color scheme and wait for the app to apply the new theme.
79
+ * Use this to test both light and dark mode within a single test after invoke(),
80
+ * avoiding a second tool invocation and resource refresh.
81
+ *
82
+ * @param {'light'|'dark'} scheme
83
+ * @param {object} [appFrame] - FrameLocator returned by invoke(). When provided,
84
+ * waits for data-theme on the app's <html> to confirm the theme propagated.
85
+ */
86
+ fixture.setColorScheme = async (scheme, appFrame) => {
87
+ await page.emulateMedia({ colorScheme: scheme });
88
+ if (appFrame) {
89
+ try {
90
+ await appFrame.locator(`html[data-theme="${scheme}"]`).waitFor({ timeout: 10_000 });
91
+ } catch {
92
+ // App may not set data-theme; fall back to a short settle wait
93
+ await page.waitForTimeout(1_500);
94
+ }
95
+ }
96
+ };
97
+
98
+ await use(fixture);
99
+ },
100
+ });
101
+
102
+ export { test, expect };
@@ -0,0 +1,10 @@
1
+ import type { PlaywrightTestConfig } from '@playwright/test';
2
+ import type { LiveConfigOptions } from './live-config.d.mts';
3
+
4
+ export interface TestConfigOptions extends LiveConfigOptions {
5
+ /** Hosts to test against. Default: ['chatgpt'] */
6
+ hosts?: string[];
7
+ }
8
+
9
+ /** Create a complete Playwright config with one project per host. */
10
+ export declare function defineLiveConfig(options?: TestConfigOptions): PlaywrightTestConfig;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Host-agnostic Playwright config factory for live tests.
3
+ *
4
+ * Generates one Playwright project per host. Tests switch color scheme,
5
+ * viewport, and other host state internally via live.setColorScheme() and
6
+ * live.page so each resource is only invoked once per host.
7
+ *
8
+ * Usage in playwright.config.ts:
9
+ * import { defineLiveConfig } from 'sunpeak/test/config';
10
+ * export default defineLiveConfig(); // ChatGPT
11
+ * export default defineLiveConfig({ hosts: ['chatgpt', 'claude'] }); // Both hosts
12
+ */
13
+ import { createLiveConfig } from './live-config.mjs';
14
+
15
+ /** Default hosts to test against. */
16
+ const DEFAULT_HOSTS = ['chatgpt'];
17
+
18
+ /**
19
+ * Create a complete Playwright config with one project per host.
20
+ *
21
+ * @param {Object} [options]
22
+ * @param {string[]} [options.hosts=['chatgpt']] - Hosts to test against
23
+ * @param {import('./live-config.d.mts').LiveConfigOptions} [options] - All other options passed to createLiveConfig
24
+ */
25
+ export function defineLiveConfig(options = {}) {
26
+ const { hosts = DEFAULT_HOSTS, ...configOptions } = options;
27
+
28
+ // Use the first host for the base config (shared settings like webServer, globalSetup)
29
+ const baseConfig = createLiveConfig({ hostId: hosts[0] }, configOptions);
30
+
31
+ return {
32
+ ...baseConfig,
33
+ projects: hosts.map((host) => ({ name: host })),
34
+ };
35
+ }
@@ -0,0 +1,54 @@
1
+ /** Playwright Locator subset used in live test assertions. */
2
+ export interface Locator {
3
+ first(): Locator;
4
+ nth(index: number): Locator;
5
+ last(): Locator;
6
+ count(): Promise<number>;
7
+ click(options?: Record<string, any>): Promise<void>;
8
+ isVisible(options?: Record<string, any>): Promise<boolean>;
9
+ waitFor(options?: Record<string, any>): Promise<void>;
10
+ evaluate<R>(fn: (el: HTMLElement) => R): Promise<R>;
11
+ textContent(): Promise<string | null>;
12
+ innerText(): Promise<string>;
13
+ getAttribute(name: string): Promise<string | null>;
14
+ }
15
+
16
+ /** Playwright FrameLocator subset used in live test assertions. */
17
+ export interface FrameLocator {
18
+ locator(selector: string): Locator;
19
+ getByText(text: string | RegExp, options?: Record<string, any>): Locator;
20
+ getByRole(role: string, options?: Record<string, any>): Locator;
21
+ getByTestId(testId: string): Locator;
22
+ }
23
+
24
+ /** Common fixture interface shared by all host-specific and generic live fixtures. */
25
+ export interface LiveFixture {
26
+ /**
27
+ * The underlying Playwright Page. Exposed for advanced host-state changes
28
+ * (e.g., page.setViewportSize(), page.emulateMedia()) that don't have
29
+ * dedicated fixture helpers yet.
30
+ */
31
+ page: import('@playwright/test').Page;
32
+ /** Start a new chat, send the prompt, and return the app FrameLocator. */
33
+ invoke(prompt: string, options?: { timeout?: number }): Promise<FrameLocator>;
34
+ /** Start a new conversation. */
35
+ startNewChat(): Promise<void>;
36
+ /** Send a message (with host-appropriate formatting). */
37
+ sendMessage(text: string): Promise<void>;
38
+ /** Send a message without any prefix. */
39
+ sendRawMessage(text: string): Promise<void>;
40
+ /** Wait for the MCP app iframe to render and return a FrameLocator. */
41
+ waitForAppIframe(options?: { timeout?: number }): Promise<FrameLocator>;
42
+ /** Get the app iframe FrameLocator. */
43
+ getAppIframe(): FrameLocator;
44
+ /**
45
+ * Switch the browser's color scheme and wait for the app to apply the new theme.
46
+ * Use this to test both light and dark mode within a single test after invoke(),
47
+ * avoiding a second tool invocation and resource refresh.
48
+ *
49
+ * @param scheme - 'light' or 'dark'
50
+ * @param appFrame - FrameLocator returned by invoke(). When provided, waits for
51
+ * data-theme on the app's <html> element to confirm the theme propagated.
52
+ */
53
+ setColorScheme(scheme: 'light' | 'dark', appFrame?: FrameLocator): Promise<void>;
54
+ }
@@ -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
+ }
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;
@@ -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-DRUsm6IZ.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-BCq2iOT-.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 {
@@ -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-DRUsm6IZ.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-BCq2iOT-.js";
2
2
  export {
3
3
  S as Simulator
4
4
  };
@@ -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-BCq2iOT-.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-B7Qw3Vhh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-B7Qw3Vhh.js","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-BCq2iOT-.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-BEHP_bM8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BEHP_bM8.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-DRUsm6IZ.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-SfudQ9Y_.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-SfudQ9Y_.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const simulator = require("./simulator-FFNttkqL.cjs");
2
+ const simulator = require("./simulator-DRUsm6IZ.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-XKHXfBiD.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-XKHXfBiD.cjs","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-XKHXfBiD.cjs");
6
+ const chatgpt_index = require("./index-SfudQ9Y_.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-DRUsm6IZ.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 ? viewport?.height ?? "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