sunpeak 0.14.3 → 0.15.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.
Files changed (125) hide show
  1. package/README.md +34 -30
  2. package/bin/commands/build.mjs +2 -1
  3. package/bin/commands/dev.mjs +72 -2
  4. package/dist/chatgpt/{conversation.d.ts → chatgpt-conversation.d.ts} +1 -1
  5. package/dist/chatgpt/chatgpt-host.d.ts +1 -0
  6. package/dist/chatgpt/globals.css +618 -6156
  7. package/dist/chatgpt/index.cjs +11 -8
  8. package/dist/chatgpt/index.cjs.map +1 -1
  9. package/dist/chatgpt/index.d.ts +10 -32
  10. package/dist/chatgpt/index.js +15 -12
  11. package/dist/chatgpt/index.js.map +1 -1
  12. package/dist/claude/claude-conversation.d.ts +23 -0
  13. package/dist/claude/claude-host.d.ts +1 -0
  14. package/dist/claude/index.cjs +5 -0
  15. package/dist/claude/index.cjs.map +1 -0
  16. package/dist/claude/index.d.ts +1 -0
  17. package/dist/claude/index.js +5 -0
  18. package/dist/claude/index.js.map +1 -0
  19. package/dist/{discovery-COZUnY6a.js → discovery-DzV3HLXs.js} +5 -5
  20. package/dist/{discovery-COZUnY6a.js.map → discovery-DzV3HLXs.js.map} +1 -1
  21. package/dist/hooks/index.d.ts +4 -0
  22. package/dist/hooks/use-app-tools.d.ts +44 -0
  23. package/dist/hooks/use-update-model-context.d.ts +29 -0
  24. package/dist/index-B0dxRJvS.cjs +42 -0
  25. package/dist/index-B0dxRJvS.cjs.map +1 -0
  26. package/dist/index-Ce_5ZIdJ.js +512 -0
  27. package/dist/index-Ce_5ZIdJ.js.map +1 -0
  28. package/dist/index-Cngntkp2.cjs +527 -0
  29. package/dist/index-Cngntkp2.cjs.map +1 -0
  30. package/dist/index-CutQgPzR.js +43 -0
  31. package/dist/index-CutQgPzR.js.map +1 -0
  32. package/dist/index.cjs +1705 -1647
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.js +2732 -2675
  36. package/dist/index.js.map +1 -1
  37. package/dist/lib/default-style-variables.d.ts +2 -0
  38. package/dist/lib/discovery-cli.js +1 -1
  39. package/dist/mcp/index.cjs +86 -27
  40. package/dist/mcp/index.cjs.map +1 -1
  41. package/dist/mcp/index.d.ts +3 -1
  42. package/dist/mcp/index.js +85 -26
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/server.d.ts +4 -3
  45. package/dist/mcp/types.d.ts +11 -0
  46. package/dist/platform/chatgpt/index.cjs +1 -1
  47. package/dist/platform/chatgpt/index.js +1 -1
  48. package/dist/platform/index.cjs +3 -0
  49. package/dist/platform/index.cjs.map +1 -1
  50. package/dist/platform/index.d.ts +3 -1
  51. package/dist/platform/index.js +3 -0
  52. package/dist/platform/index.js.map +1 -1
  53. package/dist/{protocol-BQCnIrc9.js → protocol-DFbsCx7E.js} +29 -29
  54. package/dist/{protocol-BQCnIrc9.js.map → protocol-DFbsCx7E.js.map} +1 -1
  55. package/dist/simulator/host-styles.d.ts +5 -0
  56. package/dist/simulator/hosts.d.ts +73 -0
  57. package/dist/{chatgpt → simulator}/iframe-resource.d.ts +27 -3
  58. package/dist/simulator/index.cjs +39 -0
  59. package/dist/simulator/index.cjs.map +1 -0
  60. package/dist/simulator/index.d.ts +18 -0
  61. package/dist/simulator/index.js +39 -0
  62. package/dist/simulator/index.js.map +1 -0
  63. package/dist/{chatgpt → simulator}/mcp-app-host.d.ts +8 -1
  64. package/dist/simulator/mock-openai-runtime.d.ts +20 -0
  65. package/dist/{chatgpt → simulator}/simulator-url.d.ts +8 -1
  66. package/dist/simulator/simulator.d.ts +12 -0
  67. package/dist/{chatgpt → simulator}/theme-provider.d.ts +3 -1
  68. package/dist/simulator/use-simulator-state.d.ts +91 -0
  69. package/dist/simulator-CxrtnguM.js +8764 -0
  70. package/dist/simulator-CxrtnguM.js.map +1 -0
  71. package/dist/simulator-DcfQBRXE.cjs +8779 -0
  72. package/dist/simulator-DcfQBRXE.cjs.map +1 -0
  73. package/dist/simulator-url-CuLqtnSS.js +48 -0
  74. package/dist/simulator-url-CuLqtnSS.js.map +1 -0
  75. package/dist/simulator-url-rgg_KYOg.cjs +47 -0
  76. package/dist/simulator-url-rgg_KYOg.cjs.map +1 -0
  77. package/dist/style.css +558 -6143
  78. package/dist/types/runtime.d.ts +1 -1
  79. package/dist/{use-app-D7kRAPSG.cjs → use-app-BnoSPiUT.cjs} +2 -1
  80. package/dist/{use-app-D7kRAPSG.cjs.map → use-app-BnoSPiUT.cjs.map} +1 -1
  81. package/dist/{use-app-Dvr4LKs2.js → use-app-D_TeaMFG.js} +4 -3
  82. package/dist/{use-app-Dvr4LKs2.js.map → use-app-D_TeaMFG.js.map} +1 -1
  83. package/package.json +18 -3
  84. package/template/.sunpeak/dev.tsx +4 -4
  85. package/template/.sunpeak/resource-loader.html +1 -1
  86. package/template/README.md +1 -1
  87. package/template/node_modules/.bin/nodemon +2 -2
  88. package/template/node_modules/.bin/playwright +2 -2
  89. package/template/node_modules/.bin/sunpeak +2 -2
  90. package/template/node_modules/.bin/tsc +2 -2
  91. package/template/node_modules/.bin/tsserver +2 -2
  92. package/template/node_modules/.bin/tsx +2 -2
  93. package/template/node_modules/.bin/vite +2 -2
  94. package/template/src/components/avatar.tsx +4 -1
  95. package/template/src/components/button.tsx +17 -20
  96. package/template/src/resources/albums/albums-resource.tsx +2 -3
  97. package/template/src/resources/albums/components/album-card.tsx +4 -2
  98. package/template/src/resources/albums/components/film-strip.test.tsx +2 -2
  99. package/template/src/resources/albums/components/film-strip.tsx +2 -2
  100. package/template/src/resources/albums/components/fullscreen-viewer.tsx +7 -5
  101. package/template/src/resources/carousel/carousel-resource.tsx +2 -3
  102. package/template/src/resources/carousel/components/card.test.tsx +3 -2
  103. package/template/src/resources/carousel/components/card.tsx +8 -4
  104. package/template/src/resources/map/components/map-view.tsx +1 -1
  105. package/template/src/resources/map/components/map.tsx +1 -1
  106. package/template/src/resources/map/components/place-card.tsx +8 -4
  107. package/template/src/resources/map/components/place-carousel.tsx +1 -1
  108. package/template/src/resources/map/components/place-inspector.tsx +15 -7
  109. package/template/src/resources/map/components/place-list.tsx +7 -4
  110. package/template/src/resources/map/map-resource.tsx +0 -2
  111. package/template/src/resources/review/review-resource.tsx +61 -27
  112. package/template/tests/e2e/albums.spec.ts +118 -102
  113. package/template/tests/e2e/carousel.spec.ts +103 -100
  114. package/template/tests/e2e/map.spec.ts +220 -181
  115. package/template/tests/e2e/review.spec.ts +224 -198
  116. package/dist/_commonjsHelpers-Bc2YnDe1.cjs +0 -8
  117. package/dist/_commonjsHelpers-Bc2YnDe1.cjs.map +0 -1
  118. package/dist/_commonjsHelpers-DWwsNxpa.js +0 -9
  119. package/dist/_commonjsHelpers-DWwsNxpa.js.map +0 -1
  120. package/dist/index-CJ3jfcjj.js +0 -15131
  121. package/dist/index-CJ3jfcjj.js.map +0 -1
  122. package/dist/index-Cdeg96So.cjs +0 -15147
  123. package/dist/index-Cdeg96So.cjs.map +0 -1
  124. /package/dist/{chatgpt → simulator}/simple-sidebar.d.ts +0 -0
  125. /package/dist/{chatgpt/chatgpt-simulator-types.d.ts → simulator/simulator-types.d.ts} +0 -0
@@ -17,9 +17,8 @@ export const resource: ResourceConfig = {
17
17
  mimeType: 'text/html;profile=mcp-app',
18
18
  _meta: {
19
19
  ui: {
20
- domain: 'https://sunpeak.ai',
21
20
  csp: {
22
- resourceDomains: ['https://cdn.sunpeak.ai', 'https://cdn.openai.com'],
21
+ resourceDomains: ['https://cdn.sunpeak.ai'],
23
22
  },
24
23
  },
25
24
  },
@@ -150,16 +149,26 @@ function DetailsSection({ content }: { content: Detail[] }) {
150
149
  <div
151
150
  key={i}
152
151
  className={`flex justify-between items-start gap-4 ${
153
- detail.emphasis ? 'font-semibold pt-2 border-t border-subtle' : ''
152
+ detail.emphasis
153
+ ? 'font-semibold pt-2 border-t border-[var(--color-border-tertiary)]'
154
+ : ''
154
155
  }`}
155
156
  >
156
157
  <div className="flex-1 min-w-0">
157
- <span className={detail.emphasis ? 'text-primary' : 'text-secondary'}>
158
+ <span
159
+ className={
160
+ detail.emphasis
161
+ ? 'text-[var(--color-text-primary)]'
162
+ : 'text-[var(--color-text-secondary)]'
163
+ }
164
+ >
158
165
  {detail.label}
159
166
  </span>
160
- {detail.sublabel && <p className="text-xs text-secondary mt-0.5">{detail.sublabel}</p>}
167
+ {detail.sublabel && (
168
+ <p className="text-xs text-[var(--color-text-secondary)] mt-0.5">{detail.sublabel}</p>
169
+ )}
161
170
  </div>
162
- <span className="text-primary flex-shrink-0">{detail.value}</span>
171
+ <span className="text-[var(--color-text-primary)] flex-shrink-0">{detail.value}</span>
163
172
  </div>
164
173
  ))}
165
174
  </div>
@@ -170,7 +179,10 @@ function ItemsSection({ content }: { content: Item[] }) {
170
179
  return (
171
180
  <div className="space-y-3">
172
181
  {content.map((item) => (
173
- <div key={item.id} className="flex items-center gap-3 p-2 rounded-lg bg-surface-secondary">
182
+ <div
183
+ key={item.id}
184
+ className="flex items-center gap-3 p-2 rounded-lg bg-[var(--color-background-secondary)]"
185
+ >
174
186
  {item.image && (
175
187
  <img
176
188
  src={item.image}
@@ -180,17 +192,23 @@ function ItemsSection({ content }: { content: Item[] }) {
180
192
  )}
181
193
  <div className="flex-1 min-w-0">
182
194
  <div className="flex items-center gap-2">
183
- <span className="text-sm font-medium text-primary truncate">{item.title}</span>
195
+ <span className="text-sm font-medium text-[var(--color-text-primary)] truncate">
196
+ {item.title}
197
+ </span>
184
198
  {item.badge && (
185
- <span className="px-1.5 py-0.5 text-xs rounded bg-primary text-on-primary">
199
+ <span className="px-1.5 py-0.5 text-xs rounded bg-[var(--color-ring-primary)] text-white">
186
200
  {item.badge}
187
201
  </span>
188
202
  )}
189
203
  </div>
190
- {item.subtitle && <p className="text-xs text-secondary truncate">{item.subtitle}</p>}
204
+ {item.subtitle && (
205
+ <p className="text-xs text-[var(--color-text-secondary)] truncate">{item.subtitle}</p>
206
+ )}
191
207
  </div>
192
208
  {item.value && (
193
- <span className="text-sm font-medium text-primary flex-shrink-0">{item.value}</span>
209
+ <span className="text-sm font-medium text-[var(--color-text-primary)] flex-shrink-0">
210
+ {item.value}
211
+ </span>
194
212
  )}
195
213
  </div>
196
214
  ))}
@@ -206,7 +224,7 @@ function ChangesSection({ content }: { content: Change[] }) {
206
224
  return (
207
225
  <li
208
226
  key={change.id}
209
- className="rounded-lg border border-subtle p-3"
227
+ className="rounded-lg border border-[var(--color-border-tertiary)] p-3"
210
228
  style={{ backgroundColor: config.bg }}
211
229
  >
212
230
  <div className="flex items-start gap-3">
@@ -223,12 +241,16 @@ function ChangesSection({ content }: { content: Change[] }) {
223
241
  </span>
224
242
  <div className="flex-1 min-w-0">
225
243
  {change.path && (
226
- <code className="block text-xs text-secondary font-mono truncate mb-1">
244
+ <code className="block text-xs text-[var(--color-text-secondary)] font-mono truncate mb-1">
227
245
  {change.path}
228
246
  </code>
229
247
  )}
230
248
  <p className="text-sm text-[#000000]">{change.description}</p>
231
- {change.details && <p className="mt-1 text-xs text-secondary">{change.details}</p>}
249
+ {change.details && (
250
+ <p className="mt-1 text-xs text-[var(--color-text-secondary)]">
251
+ {change.details}
252
+ </p>
253
+ )}
232
254
  </div>
233
255
  </div>
234
256
  </li>
@@ -240,24 +262,34 @@ function ChangesSection({ content }: { content: Change[] }) {
240
262
 
241
263
  function PreviewSection({ content }: { content: string }) {
242
264
  return (
243
- <div className="p-4 rounded-lg bg-surface-secondary border border-subtle">
244
- <p className="text-sm text-primary whitespace-pre-wrap">{content}</p>
265
+ <div className="p-4 rounded-lg bg-[var(--color-background-secondary)] border border-[var(--color-border-tertiary)]">
266
+ <p className="text-sm text-[var(--color-text-primary)] whitespace-pre-wrap">{content}</p>
245
267
  </div>
246
268
  );
247
269
  }
248
270
 
249
271
  function SummarySection({ content }: { content: Detail[] }) {
250
272
  return (
251
- <div className="p-3 rounded-lg bg-surface-secondary space-y-1">
273
+ <div className="p-3 rounded-lg bg-[var(--color-background-secondary)] space-y-1">
252
274
  {content.map((item, i) => (
253
275
  <div
254
276
  key={i}
255
277
  className={`flex justify-between items-center ${
256
- item.emphasis ? 'font-semibold text-lg pt-2 border-t border-subtle mt-2' : 'text-sm'
278
+ item.emphasis
279
+ ? 'font-semibold text-lg pt-2 border-t border-[var(--color-border-tertiary)] mt-2'
280
+ : 'text-sm'
257
281
  }`}
258
282
  >
259
- <span className={item.emphasis ? 'text-primary' : 'text-secondary'}>{item.label}</span>
260
- <span className="text-primary">{item.value}</span>
283
+ <span
284
+ className={
285
+ item.emphasis
286
+ ? 'text-[var(--color-text-primary)]'
287
+ : 'text-[var(--color-text-secondary)]'
288
+ }
289
+ >
290
+ {item.label}
291
+ </span>
292
+ <span className="text-[var(--color-text-primary)]">{item.value}</span>
261
293
  </div>
262
294
  ))}
263
295
  </div>
@@ -285,7 +317,7 @@ function SectionRenderer({ section }: { section: Section }) {
285
317
  return (
286
318
  <div className="space-y-2">
287
319
  {section.title && (
288
- <h2 className="text-sm font-medium text-secondary uppercase tracking-wide">
320
+ <h2 className="text-sm font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
289
321
  {section.title}
290
322
  </h2>
291
323
  )}
@@ -395,11 +427,13 @@ export function ReviewResource() {
395
427
  return (
396
428
  <SafeArea className="flex flex-col">
397
429
  {/* Header */}
398
- <div className="px-4 pt-4 pb-3 border-b border-subtle">
430
+ <div className="px-4 pt-4 pb-3 border-b border-[var(--color-border-tertiary)]">
399
431
  <div className="flex items-start justify-between gap-2">
400
432
  <div className="flex-1 min-w-0">
401
- <h1 className="text-xl font-semibold text-primary">{data.title}</h1>
402
- {data.description && <p className="mt-1 text-sm text-secondary">{data.description}</p>}
433
+ <h1 className="text-xl font-semibold text-[var(--color-text-primary)]">{data.title}</h1>
434
+ {data.description && (
435
+ <p className="mt-1 text-sm text-[var(--color-text-secondary)]">{data.description}</p>
436
+ )}
403
437
  </div>
404
438
  {!isFullscreen && (
405
439
  <Button
@@ -431,7 +465,7 @@ export function ReviewResource() {
431
465
  {sections.length === 0 ? (
432
466
  // Note: Apps cannot distinguish between "still loading" and "empty response".
433
467
  // We show a loading state as the optimistic assumption.
434
- <div className="flex items-center justify-center gap-2 py-8 text-secondary">
468
+ <div className="flex items-center justify-center gap-2 py-8 text-[var(--color-text-secondary)]">
435
469
  <div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
436
470
  <span>Loading...</span>
437
471
  </div>
@@ -441,7 +475,7 @@ export function ReviewResource() {
441
475
  </div>
442
476
 
443
477
  {/* Footer with Actions */}
444
- <div className="px-4 py-3 border-t border-subtle bg-surface">
478
+ <div className="px-4 py-3 border-t border-[var(--color-border-tertiary)] bg-[var(--color-background-primary)]">
445
479
  {decision === null ? (
446
480
  <div className="flex gap-3">
447
481
  <Button
@@ -475,7 +509,7 @@ export function ReviewResource() {
475
509
  </span>
476
510
  </div>
477
511
  {state.decidedAt && (
478
- <span className="text-xs text-secondary">
512
+ <span className="text-xs text-[var(--color-text-secondary)]">
479
513
  {new Date(state.decidedAt).toLocaleString()}
480
514
  </span>
481
515
  )}
@@ -1,125 +1,141 @@
1
1
  import { test, expect } from '@playwright/test';
2
2
  import { createSimulatorUrl } from 'sunpeak/chatgpt';
3
3
 
4
- test.describe('Albums Resource', () => {
5
- test.describe('Light Mode', () => {
6
- test('should render album cards with correct styles', async ({ page }) => {
7
- await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
8
-
9
- const iframe = page.frameLocator('iframe');
10
- const albumCard = iframe.locator('button:has-text("Summer Slice")');
11
- await expect(albumCard).toBeVisible();
12
-
13
- // Verify album card unique styles
14
- const styles = await albumCard.evaluate((el) => {
15
- const computed = window.getComputedStyle(el);
16
- return {
17
- cursor: computed.cursor,
18
- borderRadius: computed.borderRadius,
19
- };
4
+ const hosts = ['chatgpt', 'claude'] as const;
5
+
6
+ for (const host of hosts) {
7
+ test.describe(`Albums Resource [${host}]`, () => {
8
+ test.describe('Light Mode', () => {
9
+ test('should render album cards with correct styles', async ({ page }) => {
10
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light', host }));
11
+
12
+ const iframe = page.frameLocator('iframe');
13
+ const albumCard = iframe.locator('button:has-text("Summer Slice")');
14
+ await expect(albumCard).toBeVisible();
15
+
16
+ // Verify album card unique styles
17
+ const styles = await albumCard.evaluate((el) => {
18
+ const computed = window.getComputedStyle(el);
19
+ return {
20
+ cursor: computed.cursor,
21
+ borderRadius: computed.borderRadius,
22
+ };
23
+ });
24
+
25
+ expect(styles.cursor).toBe('pointer');
26
+ expect(styles.borderRadius).toBe('12px'); // rounded-xl
20
27
  });
21
28
 
22
- expect(styles.cursor).toBe('pointer');
23
- expect(styles.borderRadius).toBe('12px'); // rounded-xl
29
+ test('should have album image with correct aspect ratio', async ({ page }) => {
30
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light', host }));
31
+
32
+ const iframe = page.frameLocator('iframe');
33
+ const albumImage = iframe.locator('button:has-text("Summer Slice") img').first();
34
+ await expect(albumImage).toBeVisible();
35
+
36
+ // Verify aspect-[4/3] container
37
+ const imageContainer = iframe.locator(
38
+ 'button:has-text("Summer Slice") .aspect-\\[4\\/3\\]'
39
+ );
40
+ await expect(imageContainer).toBeVisible();
41
+
42
+ const containerStyles = await imageContainer.evaluate((el) => {
43
+ const computed = window.getComputedStyle(el);
44
+ return {
45
+ borderRadius: computed.borderRadius,
46
+ overflow: computed.overflow,
47
+ };
48
+ });
49
+
50
+ expect(containerStyles.borderRadius).toBe('12px'); // rounded-xl
51
+ expect(containerStyles.overflow).toBe('hidden');
52
+ });
24
53
  });
25
54
 
26
- test('should have album image with correct aspect ratio', async ({ page }) => {
27
- await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
55
+ test.describe('Dark Mode', () => {
56
+ test('should render album cards with correct styles', async ({ page }) => {
57
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark', host }));
28
58
 
29
- const iframe = page.frameLocator('iframe');
30
- const albumImage = iframe.locator('button:has-text("Summer Slice") img').first();
31
- await expect(albumImage).toBeVisible();
59
+ const iframe = page.frameLocator('iframe');
60
+ const albumCard = iframe.locator('button:has-text("Summer Slice")');
61
+ await expect(albumCard).toBeVisible();
32
62
 
33
- // Verify aspect-[4/3] container
34
- const imageContainer = iframe.locator('button:has-text("Summer Slice") .aspect-\\[4\\/3\\]');
35
- await expect(imageContainer).toBeVisible();
63
+ const styles = await albumCard.evaluate((el) => {
64
+ const computed = window.getComputedStyle(el);
65
+ return {
66
+ cursor: computed.cursor,
67
+ borderRadius: computed.borderRadius,
68
+ };
69
+ });
36
70
 
37
- const containerStyles = await imageContainer.evaluate((el) => {
38
- const computed = window.getComputedStyle(el);
39
- return {
40
- borderRadius: computed.borderRadius,
41
- overflow: computed.overflow,
42
- };
71
+ expect(styles.cursor).toBe('pointer');
72
+ expect(styles.borderRadius).toBe('12px'); // rounded-xl
43
73
  });
44
74
 
45
- expect(containerStyles.borderRadius).toBe('12px'); // rounded-xl
46
- expect(containerStyles.overflow).toBe('hidden');
47
- });
48
- });
75
+ test('should have text with appropriate contrast', async ({ page }) => {
76
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark', host }));
49
77
 
50
- test.describe('Dark Mode', () => {
51
- test('should render album cards with correct styles', async ({ page }) => {
52
- await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
78
+ const iframe = page.frameLocator('iframe');
79
+ const albumTitle = iframe.locator('button:has-text("Summer Slice") div').first();
80
+ await expect(albumTitle).toBeVisible();
53
81
 
54
- const iframe = page.frameLocator('iframe');
55
- const albumCard = iframe.locator('button:has-text("Summer Slice")');
56
- await expect(albumCard).toBeVisible();
82
+ // In dark mode, text should be light colored for contrast
83
+ const titleStyles = await albumTitle.evaluate((el) => {
84
+ const computed = window.getComputedStyle(el);
85
+ return {
86
+ color: computed.color,
87
+ };
88
+ });
57
89
 
58
- const styles = await albumCard.evaluate((el) => {
59
- const computed = window.getComputedStyle(el);
60
- return {
61
- cursor: computed.cursor,
62
- borderRadius: computed.borderRadius,
63
- };
90
+ // Verify the text color exists (should be a light color in dark mode)
91
+ expect(titleStyles.color).toBeTruthy();
64
92
  });
65
-
66
- expect(styles.cursor).toBe('pointer');
67
- expect(styles.borderRadius).toBe('12px'); // rounded-xl
68
93
  });
69
94
 
70
- test('should have text with appropriate contrast', async ({ page }) => {
71
- await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
72
-
73
- const iframe = page.frameLocator('iframe');
74
- const albumTitle = iframe.locator('button:has-text("Summer Slice") .text-primary').first();
75
- await expect(albumTitle).toBeVisible();
76
-
77
- // In dark mode, text-primary should be light colored for contrast
78
- const titleStyles = await albumTitle.evaluate((el) => {
79
- const computed = window.getComputedStyle(el);
80
- return {
81
- color: computed.color,
82
- };
95
+ test.describe('Fullscreen Mode', () => {
96
+ test('should render correctly in fullscreen displayMode', async ({ page }) => {
97
+ await page.goto(
98
+ createSimulatorUrl({
99
+ simulation: 'albums-show',
100
+ theme: 'light',
101
+ displayMode: 'fullscreen',
102
+ host,
103
+ })
104
+ );
105
+
106
+ // Wait for content to load
107
+ await page.waitForLoadState('networkidle');
108
+
109
+ // The root container should be present
110
+ const root = page.locator('#root');
111
+ await expect(root).not.toBeEmpty();
83
112
  });
84
113
 
85
- // Verify the text color exists (should be a light color in dark mode)
86
- expect(titleStyles.color).toBeTruthy();
87
- });
88
- });
89
-
90
- test.describe('Fullscreen Mode', () => {
91
- test('should render correctly in fullscreen displayMode', async ({ page }) => {
92
- await page.goto(
93
- createSimulatorUrl({ simulation: 'albums-show', theme: 'light', displayMode: 'fullscreen' })
94
- );
95
-
96
- // Wait for content to load
97
- await page.waitForLoadState('networkidle');
98
-
99
- // The root container should be present
100
- const root = page.locator('#root');
101
- await expect(root).not.toBeEmpty();
102
- });
103
-
104
- test('should maintain album card styles in fullscreen', async ({ page }) => {
105
- await page.goto(
106
- createSimulatorUrl({ simulation: 'albums-show', theme: 'dark', displayMode: 'fullscreen' })
107
- );
108
-
109
- const iframe = page.frameLocator('iframe');
110
- const albumCard = iframe.locator('button:has-text("Summer Slice")');
111
- await expect(albumCard).toBeVisible();
112
-
113
- const styles = await albumCard.evaluate((el) => {
114
- const computed = window.getComputedStyle(el);
115
- return {
116
- cursor: computed.cursor,
117
- borderRadius: computed.borderRadius,
118
- };
114
+ test('should maintain album card styles in fullscreen', async ({ page }) => {
115
+ await page.goto(
116
+ createSimulatorUrl({
117
+ simulation: 'albums-show',
118
+ theme: 'dark',
119
+ displayMode: 'fullscreen',
120
+ host,
121
+ })
122
+ );
123
+
124
+ const iframe = page.frameLocator('iframe');
125
+ const albumCard = iframe.locator('button:has-text("Summer Slice")');
126
+ await expect(albumCard).toBeVisible();
127
+
128
+ const styles = await albumCard.evaluate((el) => {
129
+ const computed = window.getComputedStyle(el);
130
+ return {
131
+ cursor: computed.cursor,
132
+ borderRadius: computed.borderRadius,
133
+ };
134
+ });
135
+
136
+ expect(styles.cursor).toBe('pointer');
137
+ expect(styles.borderRadius).toBe('12px');
119
138
  });
120
-
121
- expect(styles.cursor).toBe('pointer');
122
- expect(styles.borderRadius).toBe('12px');
123
139
  });
124
140
  });
125
- });
141
+ }