sunpeak 0.17.2 → 0.17.4

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.
@@ -5,6 +5,7 @@ import { createRequire } from 'module';
5
5
  import { pathToFileURL } from 'url';
6
6
  import { toPascalCase } from '../lib/patterns.mjs';
7
7
  import { extractResourceExport } from '../lib/extract-resource.mjs';
8
+ import { lightningcssConfig } from '../lib/css.mjs';
8
9
 
9
10
  /**
10
11
  * Resolve the ESM entry point for a package from a specific project directory.
@@ -237,6 +238,9 @@ export async function build(projectRoot = process.cwd(), { quiet = false } = {})
237
238
  define: {
238
239
  'process.env.NODE_ENV': JSON.stringify('production'),
239
240
  },
241
+ css: {
242
+ lightningcss: lightningcssConfig,
243
+ },
240
244
  resolve: {
241
245
  conditions: ['style', 'import', 'module', 'browser', 'default'],
242
246
  alias: {
@@ -8,6 +8,7 @@ import { pathToFileURL } from 'url';
8
8
  import { spawn } from 'child_process';
9
9
  import { getPort } from '../lib/get-port.mjs';
10
10
  import { startSandboxServer } from '../lib/sandbox-server.mjs';
11
+ import { lightningcssConfig } from '../lib/css.mjs';
11
12
  import { inspectServer } from './inspect.mjs';
12
13
 
13
14
  /**
@@ -422,6 +423,7 @@ if (import.meta.hot) {
422
423
  root: projectRoot,
423
424
  cacheDir: 'node_modules/.vite-mcp',
424
425
  plugins: [react(), tailwindcss(), sunpeakEntryPlugin()],
426
+ css: { lightningcss: lightningcssConfig },
425
427
  resolve: {
426
428
  alias: {
427
429
  '@': path.resolve(projectRoot, 'src'),
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared lightningcss configuration for all Vite instances that process Tailwind CSS.
3
+ *
4
+ * Tailwind v4's `@source` directive is consumed by `@tailwindcss/vite` but may
5
+ * still be visible to lightningcss during parsing/minification, producing
6
+ * "Unknown at rule: @source" warnings. Declaring it as a custom at-rule
7
+ * tells lightningcss the rule is intentional.
8
+ */
9
+ export const lightningcssConfig = {
10
+ customAtRules: {
11
+ source: { prelude: '<string>' },
12
+ },
13
+ };
@@ -1,88 +1,3 @@
1
- @import "tailwindcss";
2
-
3
- /* Scan simulator source files for Tailwind classes */
4
- @source "./**/*.{ts,tsx}";
5
-
6
- /* Also scan host-specific shell components */
7
- @source "../chatgpt/**/*.{ts,tsx}";
8
- @source "../claude/**/*.{ts,tsx}";
9
-
10
- /* Configure dark mode to use data-theme attribute */
11
- @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
12
-
13
- /* Sidebar utility — host-overridable via --sim-bg-sidebar */
14
- @utility bg-sidebar {
15
- background-color: var(--sim-bg-sidebar, var(--color-background-secondary));
16
- }
17
-
18
- /* Sidebar form elements — match SDK look */
19
- .sunpeak-simulator-root select,
20
- .sunpeak-simulator-root input:not([type="checkbox"]),
21
- .sunpeak-simulator-root textarea {
22
- border: 0;
23
- box-shadow: inset 0 0 0 1px var(--color-border-primary);
24
- transition: box-shadow 150ms ease, color 150ms ease, background-color 150ms ease;
25
- }
26
- .sunpeak-simulator-root select:hover,
27
- .sunpeak-simulator-root input:not([type="checkbox"]):hover,
28
- .sunpeak-simulator-root textarea:hover {
29
- box-shadow: inset 0 0 0 1px var(--color-border-secondary);
30
- }
31
- .sunpeak-simulator-root select:focus-visible,
32
- .sunpeak-simulator-root input:not([type="checkbox"]):focus-visible,
33
- .sunpeak-simulator-root textarea:focus-visible {
34
- box-shadow: inset 0 0 0 1px var(--color-border-secondary);
35
- outline: 2px solid var(--color-ring-primary);
36
- outline-offset: -1px;
37
- }
38
- .sunpeak-simulator-root button:focus-visible {
39
- outline: 2px solid var(--color-ring-primary);
40
- outline-offset: -1px;
41
- }
42
-
43
- /* Custom checkbox */
44
- .sunpeak-simulator-root input[type="checkbox"] {
45
- appearance: none;
46
- -webkit-appearance: none;
47
- width: 16px;
48
- height: 16px;
49
- border-radius: 4px;
50
- border: 1px solid var(--color-border-secondary);
51
- background-color: transparent;
52
- background-size: 10px;
53
- background-position: center;
54
- background-repeat: no-repeat;
55
- cursor: pointer;
56
- flex-shrink: 0;
57
- transition: border-color 150ms ease, background-color 150ms ease;
58
- }
59
- .sunpeak-simulator-root input[type="checkbox"]:hover {
60
- border-color: var(--color-text-tertiary);
61
- }
62
- .sunpeak-simulator-root input[type="checkbox"]:checked {
63
- border-color: var(--color-background-inverse);
64
- background-color: var(--color-background-inverse);
65
- background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M2 5L4.25 7L8 3' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
66
- }
67
- [data-theme="dark"] .sunpeak-simulator-root input[type="checkbox"]:checked {
68
- background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M2 5L4.25 7L8 3' stroke='%23111' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
69
- }
70
- .sunpeak-simulator-root input[type="checkbox"]:focus-visible {
71
- outline: 2px solid var(--color-ring-primary);
72
- outline-offset: 2px;
73
- }
74
-
75
- /* Hide native number input spinners */
76
- .sunpeak-simulator-root input[type="number"]::-webkit-inner-spin-button,
77
- .sunpeak-simulator-root input[type="number"]::-webkit-outer-spin-button {
78
- -webkit-appearance: none;
79
- margin: 0;
80
- }
81
- .sunpeak-simulator-root input[type="number"] {
82
- -moz-appearance: textfield;
83
- }
84
-
85
- /* Bundled component styles */
86
1
  /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
87
2
  @layer properties {
88
3
  @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "Local-first MCP Apps framework. Quickstart, build, test, and ship your Claude Connector or ChatGPT App!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -12,5 +12,5 @@
12
12
  }
13
13
  },
14
14
  "name": "albums",
15
- "uri": "ui://albums-mn3eflsg"
15
+ "uri": "ui://albums-mn3jnkfe"
16
16
  }
@@ -12,5 +12,5 @@
12
12
  }
13
13
  },
14
14
  "name": "carousel",
15
- "uri": "ui://carousel-mn3eflsg"
15
+ "uri": "ui://carousel-mn3jnkfe"
16
16
  }
@@ -18,5 +18,5 @@
18
18
  }
19
19
  },
20
20
  "name": "map",
21
- "uri": "ui://map-mn3eflsg"
21
+ "uri": "ui://map-mn3jnkfe"
22
22
  }
@@ -12,5 +12,5 @@
12
12
  }
13
13
  },
14
14
  "name": "review",
15
- "uri": "ui://review-mn3eflsg"
15
+ "uri": "ui://review-mn3jnkfe"
16
16
  }
@@ -1,30 +1,9 @@
1
1
  import { defineConfig, devices } from '@playwright/test';
2
- import { execSync } from 'child_process';
3
2
 
4
- /**
5
- * Find an available port synchronously.
6
- * Spawns a tiny Node script that binds, prints the port, and exits.
7
- */
8
- function getPortSync(preferred: number): number {
9
- const script = `
10
- const s = require("net").createServer();
11
- s.listen(${preferred}, () => {
12
- process.stdout.write(String(s.address().port));
13
- s.close();
14
- });
15
- s.on("error", () => {
16
- const f = require("net").createServer();
17
- f.listen(0, () => {
18
- process.stdout.write(String(f.address().port));
19
- f.close();
20
- });
21
- });
22
- `;
23
- return Number(execSync(`node -e '${script}'`, { encoding: 'utf-8' }).trim());
24
- }
25
-
26
- const port = Number(process.env.SUNPEAK_TEST_PORT) || getPortSync(6776);
27
- const sandboxPort = Number(process.env.SUNPEAK_SANDBOX_PORT) || getPortSync(24680);
3
+ // Use fixed preferred ports. If in use, `reuseExistingServer` (local) reuses
4
+ // the running server. In CI, validate.mjs assigns unique ports via env vars.
5
+ const port = Number(process.env.SUNPEAK_TEST_PORT) || 6776;
6
+ const sandboxPort = Number(process.env.SUNPEAK_SANDBOX_PORT) || 24680;
28
7
 
29
8
  export default defineConfig({
30
9
  globalSetup: './tests/e2e/global-setup.ts',
@@ -318,7 +318,9 @@ for (const host of hosts) {
318
318
  const iframe = page.frameLocator('iframe').frameLocator('iframe');
319
319
  const publishButton = iframe.locator('button:has-text("Publish")');
320
320
  await expect(publishButton).toBeVisible();
321
- await publishButton.click();
321
+ // Use evaluate to dispatch click directly — Playwright's coordinate-based
322
+ // click can miss the target inside the double cross-origin iframe.
323
+ await publishButton.evaluate((el) => (el as HTMLElement).click());
322
324
 
323
325
  // Should show the server's success message from serverTools mock
324
326
  await expect(iframe.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
@@ -338,7 +340,7 @@ for (const host of hosts) {
338
340
  const iframe = page.frameLocator('iframe').frameLocator('iframe');
339
341
  const cancelButton = iframe.locator('button:has-text("Cancel")');
340
342
  await expect(cancelButton).toBeVisible();
341
- await cancelButton.click();
343
+ await cancelButton.evaluate((el) => (el as HTMLElement).click());
342
344
 
343
345
  // Server returned cancelled status via serverTools when condition
344
346
  await expect(iframe.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });
@@ -378,7 +380,7 @@ for (const host of hosts) {
378
380
  const iframe = page.frameLocator('iframe').frameLocator('iframe');
379
381
  const placeOrderButton = iframe.locator('button:has-text("Place Order")');
380
382
  await expect(placeOrderButton).toBeVisible();
381
- await placeOrderButton.click();
383
+ await placeOrderButton.evaluate((el) => (el as HTMLElement).click());
382
384
 
383
385
  // After server responds, should show what the user clicked and the server result
384
386
  await expect(iframe.locator('text=Placing order...')).toBeVisible({ timeout: 10000 });
@@ -399,7 +401,7 @@ for (const host of hosts) {
399
401
  const iframe = page.frameLocator('iframe').frameLocator('iframe');
400
402
  const applyButton = iframe.locator('button:has-text("Apply Changes")');
401
403
  await expect(applyButton).toBeVisible();
402
- await applyButton.click();
404
+ await applyButton.evaluate((el) => (el as HTMLElement).click());
403
405
 
404
406
  // Should show the decision label and server response
405
407
  await expect(iframe.locator('text=Applying changes...')).toBeVisible({ timeout: 10000 });
@@ -418,7 +420,7 @@ for (const host of hosts) {
418
420
  const iframe = page.frameLocator('iframe').frameLocator('iframe');
419
421
  const cancelButton = iframe.locator('button:has-text("Cancel")');
420
422
  await expect(cancelButton).toBeVisible();
421
- await cancelButton.click();
423
+ await cancelButton.evaluate((el) => (el as HTMLElement).click());
422
424
 
423
425
  // Server returned cancelled status via when condition matching
424
426
  await expect(iframe.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });