@fragments-sdk/cli 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -68,38 +68,80 @@ const DeviceMockup = memo(function DeviceMockup({ type, width, children }: Devic
68
68
  const screenHeight = frameHeight - (isMobile ? 80 : 48);
69
69
 
70
70
  return (
71
- <div className="relative flex-shrink-0" style={{ width: `${frameWidth}px` }}>
71
+ <div style={{ position: 'relative', flexShrink: 0, width: `${frameWidth}px` }}>
72
72
  <div
73
- className="relative rounded-[40px] bg-[#1a1a1a] p-3 shadow-2xl"
74
73
  style={{
74
+ position: 'relative',
75
+ borderRadius: '40px',
76
+ background: '#1a1a1a',
77
+ padding: '12px',
75
78
  boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255,255,255,0.1)',
76
79
  }}
77
80
  >
78
81
  {isMobile && (
79
82
  <>
80
- <div className="absolute -left-[3px] top-24 w-[3px] h-8 bg-[#2a2a2a] rounded-l" />
81
- <div className="absolute -left-[3px] top-36 w-[3px] h-12 bg-[#2a2a2a] rounded-l" />
82
- <div className="absolute -left-[3px] top-52 w-[3px] h-12 bg-[#2a2a2a] rounded-l" />
83
- <div className="absolute -right-[3px] top-32 w-[3px] h-16 bg-[#2a2a2a] rounded-r" />
83
+ <div style={{ position: 'absolute', left: '-3px', top: '96px', width: '3px', height: '32px', background: '#2a2a2a', borderRadius: '4px 0 0 4px' }} />
84
+ <div style={{ position: 'absolute', left: '-3px', top: '144px', width: '3px', height: '48px', background: '#2a2a2a', borderRadius: '4px 0 0 4px' }} />
85
+ <div style={{ position: 'absolute', left: '-3px', top: '208px', width: '3px', height: '48px', background: '#2a2a2a', borderRadius: '4px 0 0 4px' }} />
86
+ <div style={{ position: 'absolute', right: '-3px', top: '128px', width: '3px', height: '64px', background: '#2a2a2a', borderRadius: '0 4px 4px 0' }} />
84
87
  </>
85
88
  )}
86
89
 
87
90
  <div
88
- className="relative rounded-[32px] overflow-hidden bg-white"
89
- style={{ height: `${screenHeight}px` }}
91
+ style={{
92
+ position: 'relative',
93
+ borderRadius: '32px',
94
+ overflow: 'hidden',
95
+ background: 'white',
96
+ height: `${screenHeight}px`,
97
+ }}
90
98
  >
91
99
  {isMobile ? (
92
- <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[120px] h-[30px] bg-[#1a1a1a] rounded-b-2xl z-10 flex items-center justify-center gap-2">
93
- <div className="w-2 h-2 rounded-full bg-[#2a2a2a]" />
94
- <div className="w-12 h-1.5 rounded-full bg-[#2a2a2a]" />
100
+ <div style={{
101
+ position: 'absolute',
102
+ top: 0,
103
+ left: '50%',
104
+ transform: 'translateX(-50%)',
105
+ width: '120px',
106
+ height: '30px',
107
+ background: '#1a1a1a',
108
+ borderRadius: '0 0 16px 16px',
109
+ zIndex: 10,
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ justifyContent: 'center',
113
+ gap: '8px',
114
+ }}>
115
+ <div style={{ width: '8px', height: '8px', borderRadius: '50%', background: '#2a2a2a' }} />
116
+ <div style={{ width: '48px', height: '6px', borderRadius: '9999px', background: '#2a2a2a' }} />
95
117
  </div>
96
118
  ) : (
97
- <div className="absolute top-2 left-1/2 -translate-x-1/2 w-3 h-3 rounded-full bg-[#2a2a2a] z-10" />
119
+ <div style={{
120
+ position: 'absolute',
121
+ top: '8px',
122
+ left: '50%',
123
+ transform: 'translateX(-50%)',
124
+ width: '12px',
125
+ height: '12px',
126
+ borderRadius: '50%',
127
+ background: '#2a2a2a',
128
+ zIndex: 10,
129
+ }} />
98
130
  )}
99
131
 
100
- <div className="w-full h-full overflow-auto">{children}</div>
132
+ <div style={{ width: '100%', height: '100%', overflow: 'auto' }}>{children}</div>
101
133
 
102
- <div className="absolute bottom-2 left-1/2 -translate-x-1/2 w-[100px] h-1 bg-black/20 rounded-full z-10" />
134
+ <div style={{
135
+ position: 'absolute',
136
+ bottom: '8px',
137
+ left: '50%',
138
+ transform: 'translateX(-50%)',
139
+ width: '100px',
140
+ height: '4px',
141
+ background: 'rgba(0, 0, 0, 0.2)',
142
+ borderRadius: '9999px',
143
+ zIndex: 10,
144
+ }} />
103
145
  </div>
104
146
  </div>
105
147
  </div>
@@ -119,14 +161,16 @@ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, backgr
119
161
  <div
120
162
  data-preview-container="true"
121
163
  data-theme={previewTheme}
122
- className="w-full h-full overflow-auto"
123
164
  style={{
165
+ width: '100%',
166
+ height: '100%',
167
+ overflow: 'auto',
124
168
  backgroundColor: background === 'transparent' ? 'transparent' : undefined,
125
169
  }}
126
170
  >
127
171
  <div
128
- className="p-6"
129
172
  style={{
173
+ padding: '24px',
130
174
  transform: `scale(${zoom / 100})`,
131
175
  transformOrigin: 'top left',
132
176
  width: zoom !== 100 ? `${100 / (zoom / 100)}%` : '100%',
@@ -205,11 +249,11 @@ export function PreviewArea({
205
249
 
206
250
  if (showComparison && figmaUrl) {
207
251
  return (
208
- <div className="min-h-full flex flex-col p-6">
209
- <div className="flex gap-4 flex-1">
210
- <div className="flex-1 flex flex-col">
211
- <div className="text-xs font-medium text-tertiary mb-2 text-center">Rendered</div>
212
- <div className="flex-1 flex items-center justify-center" style={backgroundStyle}>
252
+ <div style={{ minHeight: '100%', display: 'flex', flexDirection: 'column', padding: '24px' }}>
253
+ <div style={{ display: 'flex', gap: '16px', flex: 1 }}>
254
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
255
+ <div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Rendered</div>
256
+ <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', ...backgroundStyle }}>
213
257
  <DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
214
258
  {useIframeIsolation ? (
215
259
  <IsolatedPreviewFrame
@@ -231,14 +275,19 @@ export function PreviewArea({
231
275
  </div>
232
276
  </div>
233
277
 
234
- <div className="flex-1 flex flex-col">
235
- <div className="text-xs font-medium text-tertiary mb-2 text-center">Figma Design</div>
278
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
279
+ <div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Figma Design</div>
236
280
  <FigmaEmbed
237
281
  figmaUrl={figmaUrl}
238
282
  allFigmaUrls={allFigmaUrls}
239
283
  zoom={zoom}
240
- className="flex-1 rounded-lg border border-[--border] overflow-hidden"
241
- style={backgroundStyle}
284
+ style={{
285
+ flex: 1,
286
+ borderRadius: '8px',
287
+ border: '1px solid var(--border)',
288
+ overflow: 'hidden',
289
+ ...backgroundStyle,
290
+ }}
242
291
  />
243
292
  </div>
244
293
  </div>
@@ -247,7 +296,7 @@ export function PreviewArea({
247
296
  }
248
297
 
249
298
  return (
250
- <div className="min-h-full flex items-center justify-center p-8">
299
+ <div style={{ minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
251
300
  <DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
252
301
  {useIframeIsolation ? (
253
302
  <IsolatedPreviewFrame
@@ -273,13 +322,18 @@ export function PreviewArea({
273
322
  // Side-by-side comparison view
274
323
  if (showComparison && figmaUrl && variant) {
275
324
  return (
276
- <div className="min-h-full flex flex-col p-6">
277
- <div className="flex gap-4 flex-1">
278
- <div className="flex-1 flex flex-col">
279
- <div className="text-xs font-medium text-tertiary mb-2 text-center">Rendered</div>
325
+ <div style={{ minHeight: '100%', display: 'flex', flexDirection: 'column', padding: '24px' }}>
326
+ <div style={{ display: 'flex', gap: '16px', flex: 1 }}>
327
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
328
+ <div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Rendered</div>
280
329
  <div
281
- className="flex-1 rounded-lg border border-[--border] overflow-auto"
282
- style={backgroundStyle}
330
+ style={{
331
+ flex: 1,
332
+ borderRadius: '8px',
333
+ border: '1px solid var(--border)',
334
+ overflow: 'auto',
335
+ ...backgroundStyle,
336
+ }}
283
337
  >
284
338
  {useIframeIsolation ? (
285
339
  <IsolatedPreviewFrame
@@ -293,7 +347,12 @@ export function PreviewArea({
293
347
  />
294
348
  ) : (
295
349
  <div
296
- className="flex items-center justify-center p-8"
350
+ style={{
351
+ display: 'flex',
352
+ alignItems: 'center',
353
+ justifyContent: 'center',
354
+ padding: '32px',
355
+ }}
297
356
  data-preview-container="true"
298
357
  data-theme={previewTheme}
299
358
  >
@@ -314,14 +373,19 @@ export function PreviewArea({
314
373
  </div>
315
374
  </div>
316
375
 
317
- <div className="flex-1 flex flex-col">
318
- <div className="text-xs font-medium text-tertiary mb-2 text-center">Figma Design</div>
376
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
377
+ <div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Figma Design</div>
319
378
  <FigmaEmbed
320
379
  figmaUrl={figmaUrl}
321
380
  allFigmaUrls={allFigmaUrls}
322
381
  zoom={zoom}
323
- className="flex-1 rounded-lg border border-[--border] overflow-hidden"
324
- style={backgroundStyle}
382
+ style={{
383
+ flex: 1,
384
+ borderRadius: '8px',
385
+ border: '1px solid var(--border)',
386
+ overflow: 'hidden',
387
+ ...backgroundStyle,
388
+ }}
325
389
  />
326
390
  </div>
327
391
  </div>
@@ -336,10 +400,14 @@ export function PreviewArea({
336
400
  const isFullWidth = !viewportWidth;
337
401
 
338
402
  return (
339
- <div className={isFullWidth ? "h-full flex flex-col" : "min-h-full flex items-center justify-center p-6"}>
403
+ <div style={isFullWidth
404
+ ? { height: '100%', display: 'flex', flexDirection: 'column' }
405
+ : { minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }
406
+ }>
340
407
  <div
341
- className="relative transition-all duration-200"
342
408
  style={{
409
+ position: 'relative',
410
+ transition: 'all 200ms',
343
411
  width: viewportWidth ? `${viewportWidth}px` : '100%',
344
412
  maxWidth: viewportWidth ? undefined : '100%',
345
413
  height: isFullWidth ? '100%' : undefined,
@@ -367,12 +435,11 @@ export function PreviewArea({
367
435
 
368
436
  // Fallback: Direct rendering without iframe isolation
369
437
  return (
370
- <div className="min-h-full flex items-center justify-center p-6">
438
+ <div style={{ minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }}>
371
439
  <div
372
- className="relative transition-all duration-200"
373
- data-preview-container="true"
374
- data-theme={previewTheme}
375
440
  style={{
441
+ position: 'relative',
442
+ transition: 'all 200ms',
376
443
  width: viewportWidth ? `${viewportWidth}px` : '100%',
377
444
  maxWidth: viewportWidth ? undefined : '100%',
378
445
  minHeight: viewportHeight ? `${viewportHeight}px` : '100%',
@@ -382,10 +449,12 @@ export function PreviewArea({
382
449
  boxShadow: '0 0 0 1px var(--border), 0 4px 12px rgba(0,0,0,0.15)',
383
450
  }),
384
451
  }}
452
+ data-preview-container="true"
453
+ data-theme={previewTheme}
385
454
  >
386
455
  <div
387
- className="p-8"
388
456
  style={{
457
+ padding: '32px',
389
458
  transform: `scale(${zoom / 100})`,
390
459
  transformOrigin: 'top left',
391
460
  width: zoom !== 100 ? `${100 / (zoom / 100)}%` : '100%',
@@ -24,6 +24,7 @@ interface SegmentDefinition {
24
24
  meta: {
25
25
  name: string;
26
26
  description?: string;
27
+ category?: string;
27
28
  };
28
29
  variants?: SegmentVariant[];
29
30
  }
@@ -82,12 +83,29 @@ function findVariant(segment: SegmentDefinition, variantName: string): SegmentVa
82
83
  return segment.variants?.find(v => v.name === variantName);
83
84
  }
84
85
 
86
+ type PreviewMode = 'centered' | 'full-bleed';
87
+
88
+ function resolvePreviewMode(segment: SegmentDefinition): PreviewMode {
89
+ const name = segment.meta.name.toLowerCase();
90
+ const category = (segment.meta.category || '').toLowerCase();
91
+
92
+ if (category === 'layout' || category === 'navigation') {
93
+ return 'full-bleed';
94
+ }
95
+
96
+ if (name.includes('appshell') || name.includes('sidebar') || name.includes('header') || name.includes('layout')) {
97
+ return 'full-bleed';
98
+ }
99
+
100
+ return 'centered';
101
+ }
102
+
85
103
  /**
86
104
  * Error boundary for catching render errors
87
105
  */
88
106
  function ErrorDisplay({ message, stack }: { message: string; stack?: string }) {
89
107
  return (
90
- <div className="preview-error">
108
+ <div style={{ padding: '16px', color: '#dc2626', background: 'rgba(254, 242, 242, 0.95)', borderRadius: '8px', margin: '16px' }}>
91
109
  <div style={{ fontWeight: 500, marginBottom: 8 }}>Render Error</div>
92
110
  <div>{message}</div>
93
111
  {stack && (
@@ -104,8 +122,9 @@ function ErrorDisplay({ message, stack }: { message: string; stack?: string }) {
104
122
  */
105
123
  function LoadingIndicator() {
106
124
  return (
107
- <div className="preview-loading">
108
- <div className="spinner" />
125
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px', gap: '8px', color: '#6b7280' }}>
126
+ <div style={{ width: '16px', height: '16px', border: '2px solid #e5e7eb', borderTopColor: '#3b82f6', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />
127
+ <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
109
128
  <span>Loading component...</span>
110
129
  </div>
111
130
  );
@@ -117,11 +136,13 @@ function LoadingIndicator() {
117
136
  function VariantRenderer({
118
137
  variant,
119
138
  props,
139
+ mode,
120
140
  onRendered,
121
141
  onError,
122
142
  }: {
123
143
  variant: SegmentVariant;
124
144
  props?: Record<string, unknown>;
145
+ mode: PreviewMode;
125
146
  onRendered: (width: number, height: number) => void;
126
147
  onError: (message: string, stack?: string) => void;
127
148
  }) {
@@ -206,9 +227,11 @@ function VariantRenderer({
206
227
  return (
207
228
  <div
208
229
  ref={containerRef}
209
- className="transition-opacity duration-150"
210
230
  style={{
211
- display: 'inline-block',
231
+ display: mode === 'full-bleed' ? 'block' : 'inline-block',
232
+ width: mode === 'full-bleed' ? '100%' : undefined,
233
+ minHeight: mode === 'full-bleed' ? '100vh' : undefined,
234
+ transition: 'opacity 150ms',
212
235
  opacity: content ? 1 : 0,
213
236
  }}
214
237
  >
@@ -226,6 +249,7 @@ export function PreviewFrameHost() {
226
249
  const [loadError, setLoadError] = useState<string | null>(null);
227
250
  const [currentVariant, setCurrentVariant] = useState<SegmentVariant | null>(null);
228
251
  const [currentProps, setCurrentProps] = useState<Record<string, unknown> | undefined>(undefined);
252
+ const [previewMode, setPreviewMode] = useState<PreviewMode>('centered');
229
253
 
230
254
  // Apply theme to document
231
255
  useEffect(() => {
@@ -236,6 +260,10 @@ export function PreviewFrameHost() {
236
260
  }
237
261
  }, [theme]);
238
262
 
263
+ useEffect(() => {
264
+ document.body.setAttribute('data-preview-mode', previewMode);
265
+ }, [previewMode]);
266
+
239
267
  // Load segments on mount
240
268
  useEffect(() => {
241
269
  loadSegments()
@@ -272,6 +300,7 @@ export function PreviewFrameHost() {
272
300
  return;
273
301
  }
274
302
 
303
+ setPreviewMode(resolvePreviewMode(segmentItem.segment));
275
304
  setCurrentVariant(variant);
276
305
  setCurrentProps(props);
277
306
  }, [renderRequest, segments, notifyError]);
@@ -289,7 +318,7 @@ export function PreviewFrameHost() {
289
318
  // Show waiting state
290
319
  if (!currentVariant) {
291
320
  return (
292
- <div className="preview-loading">
321
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px', gap: '8px', color: '#6b7280' }}>
293
322
  <span>Waiting for render request...</span>
294
323
  </div>
295
324
  );
@@ -301,6 +330,7 @@ export function PreviewFrameHost() {
301
330
  key={`${renderRequest?.segmentPath}-${renderRequest?.variantName}`}
302
331
  variant={currentVariant}
303
332
  props={currentProps}
333
+ mode={previewMode}
304
334
  onRendered={notifyRendered}
305
335
  onError={notifyError}
306
336
  />
@@ -130,7 +130,6 @@ export function PreviewPane({ children, className, style, includeComponentStyles
130
130
  return (
131
131
  <div
132
132
  ref={containerRef}
133
- className={className}
134
133
  style={{ minHeight: '120px', ...style }}
135
134
  data-preview-wrapper="true"
136
135
  />
@@ -141,9 +140,9 @@ export function PreviewPane({ children, className, style, includeComponentStyles
141
140
  * SimplePreviewPane - A simpler preview without Shadow DOM isolation.
142
141
  * Use this when full isolation isn't needed.
143
142
  */
144
- export function SimplePreviewPane({ children, className, style }: PreviewPaneProps) {
143
+ export function SimplePreviewPane({ children, style }: PreviewPaneProps) {
145
144
  return (
146
- <div className={className} style={style}>
145
+ <div style={style}>
147
146
  {children}
148
147
  </div>
149
148
  );