@fragments-sdk/cli 0.9.0 → 0.9.1

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 (123) hide show
  1. package/dist/bin.js +83 -33
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-WI6SLMSO.js → chunk-5GT62FCB.js} +2 -2
  4. package/dist/{chunk-CJEGT3WD.js → chunk-BW3ZATBW.js} +20 -3
  5. package/dist/chunk-BW3ZATBW.js.map +1 -0
  6. package/dist/{chunk-2JIKCJX3.js → chunk-D7372LQX.js} +13 -6
  7. package/dist/chunk-D7372LQX.js.map +1 -0
  8. package/dist/chunk-EZYXYWNF.js +131 -0
  9. package/dist/chunk-EZYXYWNF.js.map +1 -0
  10. package/dist/{chunk-NGIMCIK2.js → chunk-GF6OVPIN.js} +2 -2
  11. package/dist/{chunk-GOVI6COW.js → chunk-NVSPGSKB.js} +12 -4
  12. package/dist/chunk-NVSPGSKB.js.map +1 -0
  13. package/dist/core/index.d.ts +105 -3
  14. package/dist/core/index.js +12 -2
  15. package/dist/{defineFragment-D0UTve-I.d.ts → defineFragment-CBMS7Bab.d.ts} +21 -1
  16. package/dist/generate-LQA2R7FN.js +461 -0
  17. package/dist/generate-LQA2R7FN.js.map +1 -0
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +5 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-KSAAS7X3.js → init-2GEGVIUQ.js} +13 -75
  22. package/dist/init-2GEGVIUQ.js.map +1 -0
  23. package/dist/mcp-bin.js +4 -3
  24. package/dist/mcp-bin.js.map +1 -1
  25. package/dist/{scan-65RH3QMM.js → scan-JGS65S7P.js} +6 -5
  26. package/dist/{service-A5GIGGGK.js → service-XP2EAJXD.js} +4 -3
  27. package/dist/{static-viewer-NSODM5VX.js → static-viewer-XCS7UJTO.js} +4 -3
  28. package/dist/storyFilters-3LUYAFZF.js +15 -0
  29. package/dist/storyFilters-3LUYAFZF.js.map +1 -0
  30. package/dist/{test-RPWZAYSJ.js → test-TD6TJNVY.js} +3 -3
  31. package/dist/{tokens-NIXSZRX7.js → tokens-2EXPCVP3.js} +5 -4
  32. package/dist/{tokens-NIXSZRX7.js.map → tokens-2EXPCVP3.js.map} +1 -1
  33. package/dist/{viewer-SBTJDMP7.js → viewer-RFA2KVBG.js} +243 -18
  34. package/dist/viewer-RFA2KVBG.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/build.ts +12 -2
  37. package/src/commands/build.ts +16 -2
  38. package/src/commands/generate.ts +383 -68
  39. package/src/commands/init.ts +9 -51
  40. package/src/core/config.ts +15 -2
  41. package/src/core/generators/typescript-extractor.ts +10 -0
  42. package/src/core/index.ts +15 -0
  43. package/src/core/schema.ts +10 -2
  44. package/src/core/storyFilters.test.ts +350 -0
  45. package/src/core/storyFilters.ts +253 -0
  46. package/src/core/types.ts +22 -0
  47. package/src/migrate/converter.ts +9 -1
  48. package/src/migrate/parser.ts +2 -0
  49. package/src/migrate/types.ts +2 -0
  50. package/src/setup.ts +69 -24
  51. package/src/viewer/__tests__/viewer-integration.test.ts +1 -1
  52. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  53. package/src/viewer/components/ActionsPanel.tsx +31 -29
  54. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  55. package/src/viewer/components/App.tsx +187 -740
  56. package/src/viewer/components/BottomPanel.tsx +228 -132
  57. package/src/viewer/components/CodePanel.tsx +1 -1
  58. package/src/viewer/components/CommandPalette.tsx +7 -10
  59. package/src/viewer/components/ComponentDocView.tsx +164 -0
  60. package/src/viewer/components/ComponentGraph.tsx +111 -142
  61. package/src/viewer/components/ContractPanel.tsx +6 -6
  62. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  63. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  64. package/src/viewer/components/FragmentEditor.tsx +92 -115
  65. package/src/viewer/components/HeaderSearch.tsx +24 -0
  66. package/src/viewer/components/HealthDashboard.tsx +16 -2
  67. package/src/viewer/components/Icons.tsx +9 -0
  68. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  69. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  70. package/src/viewer/components/LandingPage.tsx +3 -3
  71. package/src/viewer/components/LeftSidebar.tsx +141 -63
  72. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  73. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  74. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  75. package/src/viewer/components/PanelShell.tsx +161 -0
  76. package/src/viewer/components/PerformancePanel.tsx +31 -28
  77. package/src/viewer/components/PreviewArea.tsx +1 -1
  78. package/src/viewer/components/PreviewAside.tsx +168 -0
  79. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  80. package/src/viewer/components/PropsEditor.tsx +70 -156
  81. package/src/viewer/components/ResizablePanel.tsx +103 -263
  82. package/src/viewer/components/RightSidebar.tsx +3 -9
  83. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  84. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  85. package/src/viewer/components/TopToolbar.tsx +159 -0
  86. package/src/viewer/components/VariantMatrix.tsx +42 -86
  87. package/src/viewer/components/VariantTabs.tsx +3 -3
  88. package/src/viewer/components/ViewerHeader.tsx +69 -0
  89. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  90. package/src/viewer/components/viewer-utils.ts +16 -0
  91. package/src/viewer/entry.tsx +5 -0
  92. package/src/viewer/hooks/useAppState.ts +27 -4
  93. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  94. package/src/viewer/preview-frame.html +6 -12
  95. package/src/viewer/server.ts +169 -2
  96. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  97. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  98. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  99. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  100. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  101. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  102. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  103. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  104. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  105. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +122 -0
  106. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  107. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  108. package/src/viewer/vendor/shared/src/index.ts +8 -0
  109. package/src/viewer/vendor/shared/src/types.ts +12 -0
  110. package/src/viewer/vite-plugin.ts +109 -4
  111. package/dist/chunk-2JIKCJX3.js.map +0 -1
  112. package/dist/chunk-CJEGT3WD.js.map +0 -1
  113. package/dist/chunk-GOVI6COW.js.map +0 -1
  114. package/dist/generate-35OIMW4Y.js +0 -252
  115. package/dist/generate-35OIMW4Y.js.map +0 -1
  116. package/dist/init-KSAAS7X3.js.map +0 -1
  117. package/dist/viewer-SBTJDMP7.js.map +0 -1
  118. /package/dist/{chunk-WI6SLMSO.js.map → chunk-5GT62FCB.js.map} +0 -0
  119. /package/dist/{chunk-NGIMCIK2.js.map → chunk-GF6OVPIN.js.map} +0 -0
  120. /package/dist/{scan-65RH3QMM.js.map → scan-JGS65S7P.js.map} +0 -0
  121. /package/dist/{service-A5GIGGGK.js.map → service-XP2EAJXD.js.map} +0 -0
  122. /package/dist/{static-viewer-NSODM5VX.js.map → static-viewer-XCS7UJTO.js.map} +0 -0
  123. /package/dist/{test-RPWZAYSJ.js.map → test-TD6TJNVY.js.map} +0 -0
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { useState, useMemo, useRef, useCallback } from "react";
14
14
  import { useVirtualizer } from "@tanstack/react-virtual";
15
+ import { Box, Stack, Text, Button, Badge, EmptyState } from "@fragments-sdk/ui";
15
16
  import type { FragmentVariant } from "../../core/index.js";
16
17
  import { ErrorBoundary } from "./ErrorBoundary.js";
17
18
  import { FragmentRenderer, LoaderIndicator } from "./FragmentRenderer.js";
@@ -65,7 +66,6 @@ export function VariantMatrix({
65
66
  }: VariantMatrixProps) {
66
67
  const [gridSize, setGridSize] = useState<GridSize>("medium");
67
68
  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
68
- const [hoveredButton, setHoveredButton] = useState<GridSize | null>(null);
69
69
  const scrollRef = useRef<HTMLDivElement>(null);
70
70
 
71
71
  const gridConfig = GRID_SIZES[gridSize];
@@ -90,79 +90,43 @@ export function VariantMatrix({
90
90
 
91
91
  if (variants.length === 0) {
92
92
  return (
93
- <div style={{
94
- display: 'flex',
95
- alignItems: 'center',
96
- justifyContent: 'center',
97
- height: '100%',
98
- color: 'var(--text-muted)',
99
- }}>
100
- No variants to display
101
- </div>
93
+ <EmptyState style={{ height: '100%' }}>
94
+ <EmptyState.Title>No variants to display</EmptyState.Title>
95
+ </EmptyState>
102
96
  );
103
97
  }
104
98
 
105
99
  return (
106
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
100
+ <Stack style={{ height: '100%' }}>
107
101
  {/* Toolbar */}
108
- <div style={{
109
- flexShrink: 0,
110
- padding: '8px 16px',
111
- borderBottom: '1px solid var(--border)',
112
- background: 'var(--bg-secondary)',
113
- display: 'flex',
114
- alignItems: 'center',
115
- justifyContent: 'space-between',
116
- }}>
117
- <div style={{ fontSize: 14, color: 'var(--text-secondary)' }}>
118
- {variants.length} variant{variants.length !== 1 ? "s" : ""}
119
- {useVirtualization && (
120
- <span style={{ marginLeft: 8, fontSize: 12, color: 'var(--text-tertiary)' }}>(virtualized)</span>
121
- )}
122
- </div>
123
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
124
- <span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Grid size:</span>
125
- <div style={{
126
- display: 'flex',
127
- borderRadius: 6,
128
- border: '1px solid var(--border)',
129
- overflow: 'hidden',
130
- }}>
131
- {(["small", "medium", "large"] as GridSize[]).map((size) => (
132
- <button
133
- key={size}
134
- onClick={() => setGridSize(size)}
135
- onMouseEnter={() => setHoveredButton(size)}
136
- onMouseLeave={() => setHoveredButton(null)}
137
- style={{
138
- padding: '4px 8px',
139
- fontSize: 12,
140
- textTransform: 'capitalize',
141
- transition: 'background-color 0.15s, color 0.15s',
142
- border: 'none',
143
- cursor: 'pointer',
144
- background: gridSize === size
145
- ? 'var(--bg-hover)'
146
- : hoveredButton === size
147
- ? 'var(--bg-hover)'
148
- : 'transparent',
149
- color: gridSize === size
150
- ? 'var(--text-primary)'
151
- : hoveredButton === size
152
- ? 'var(--text-secondary)'
153
- : 'var(--text-tertiary)',
154
- }}
155
- >
156
- {size}
157
- </button>
158
- ))}
159
- </div>
160
- </div>
161
- </div>
102
+ <Box paddingX="md" paddingY="sm" borderBottom background="secondary" style={{ flexShrink: 0 }}>
103
+ <Stack direction="row" align="center" justify="between">
104
+ <Text size="sm" color="secondary">
105
+ {variants.length} variant{variants.length !== 1 ? "s" : ""}
106
+ {useVirtualization && <Text as="span" size="xs" color="tertiary"> (virtualized)</Text>}
107
+ </Text>
108
+ <Stack direction="row" align="center" gap="sm">
109
+ <Text size="xs" color="tertiary">Grid size:</Text>
110
+ <Stack direction="row" style={{ borderRadius: 6, border: '1px solid var(--border)', overflow: 'hidden' }}>
111
+ {(["small", "medium", "large"] as GridSize[]).map((size) => (
112
+ <Button
113
+ key={size}
114
+ variant={gridSize === size ? "secondary" : "ghost"}
115
+ size="sm"
116
+ onClick={() => setGridSize(size)}
117
+ style={{ textTransform: 'capitalize', borderRadius: 0 }}
118
+ >
119
+ {size}
120
+ </Button>
121
+ ))}
122
+ </Stack>
123
+ </Stack>
124
+ </Stack>
125
+ </Box>
162
126
 
163
127
  {/* Grid - Virtualized or Regular */}
164
128
  {useVirtualization ? (
165
- <div ref={scrollRef} style={{ flex: 1, overflow: 'auto', padding: 16 }}>
129
+ <Box ref={scrollRef} overflow="auto" padding="md" style={{ flex: 1 }}>
166
130
  <div
167
131
  style={{
168
132
  height: `${rowVirtualizer.getTotalSize()}px`,
@@ -217,9 +181,9 @@ export function VariantMatrix({
217
181
  );
218
182
  })}
219
183
  </div>
220
- </div>
184
+ </Box>
221
185
  ) : (
222
- <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
186
+ <Box overflow="auto" padding="md" style={{ flex: 1 }}>
223
187
  <div style={{
224
188
  display: 'grid',
225
189
  gap: 16,
@@ -243,9 +207,9 @@ export function VariantMatrix({
243
207
  />
244
208
  ))}
245
209
  </div>
246
- </div>
210
+ </Box>
247
211
  )}
248
- </div>
212
+ </Stack>
249
213
  );
250
214
  }
251
215
 
@@ -298,7 +262,7 @@ function VariantCard({
298
262
  onMouseLeave={onLeave}
299
263
  onClick={onClick}
300
264
  >
301
- {/* Header */}
265
+ {/* Header overlay - keep inline styles (CSS art) */}
302
266
  <div style={{
303
267
  position: 'absolute',
304
268
  top: 0,
@@ -384,25 +348,25 @@ function VariantCard({
384
348
  <ErrorBoundary
385
349
  componentName={componentName}
386
350
  fallback={
387
- <div style={{ fontSize: 12, color: '#ef4444', padding: 8 }}>
351
+ <Text size="xs" style={{ color: 'var(--color-danger)' }}>
388
352
  Error rendering variant
389
- </div>
353
+ </Text>
390
354
  }
391
355
  >
392
356
  <FragmentRenderer variant={variant}>
393
357
  {(content, isLoading, error) => {
394
358
  if (isLoading) {
395
359
  return (
396
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }}>
360
+ <Stack align="center" justify="center" style={{ padding: 16 }}>
397
361
  <LoaderIndicator />
398
- </div>
362
+ </Stack>
399
363
  );
400
364
  }
401
365
  if (error) {
402
366
  return (
403
- <div style={{ fontSize: 12, color: '#ef4444', padding: 8 }}>
367
+ <Text size="xs" style={{ color: 'var(--color-danger)', padding: 8 }}>
404
368
  {error.message}
405
- </div>
369
+ </Text>
406
370
  );
407
371
  }
408
372
  return content;
@@ -416,15 +380,7 @@ function VariantCard({
416
380
  {/* Tags/badges */}
417
381
  {variant.hasPlayFunction && (
418
382
  <div style={{ position: 'absolute', bottom: 8, right: 8, zIndex: 10 }}>
419
- <span style={{
420
- padding: '2px 6px',
421
- fontSize: 10,
422
- background: '#9333ea',
423
- color: '#ffffff',
424
- borderRadius: 4,
425
- }}>
426
- play
427
- </span>
383
+ <Badge variant="info" size="sm">play</Badge>
428
384
  </div>
429
385
  )}
430
386
  </div>
@@ -1,4 +1,4 @@
1
- import { Tabs } from '@fragments-sdk/ui';
1
+ import { Tabs, Stack } from '@fragments-sdk/ui';
2
2
  import type { FragmentVariant } from '../../core/index.js';
3
3
  import { PlayIcon } from './Icons.js';
4
4
 
@@ -24,14 +24,14 @@ export function VariantTabs({ variants, activeIndex, onSelect }: VariantTabsProp
24
24
  <Tabs.List variant="pills">
25
25
  {variants.map((variant) => (
26
26
  <Tabs.Tab key={variant.name} value={variant.name}>
27
- <span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
27
+ <Stack direction="row" align="center" gap="xs" as="span">
28
28
  {variant.name}
29
29
  {variant.hasPlayFunction && (
30
30
  <span style={{ display: 'inline-flex', width: '12px', height: '12px', color: 'var(--color-accent)' }}>
31
31
  <PlayIcon />
32
32
  </span>
33
33
  )}
34
- </span>
34
+ </Stack>
35
35
  </Tabs.Tab>
36
36
  ))}
37
37
  </Tabs.List>
@@ -0,0 +1,69 @@
1
+ import type { RefObject } from "react";
2
+ import { BRAND } from "../../core/index.js";
3
+ import {
4
+ Header,
5
+ Stack,
6
+ Text,
7
+ Button,
8
+ ThemeToggle,
9
+ FragmentsLogo,
10
+ } from "@fragments-sdk/ui";
11
+ import { GitHubIcon } from "./Icons.js";
12
+ import { HeaderSearch } from "./HeaderSearch.js";
13
+ import { useTheme } from "./ThemeProvider.js";
14
+ import { WebMCPStatusIndicator } from "./WebMCPStatusIndicator.js";
15
+
16
+ interface ViewerHeaderProps {
17
+ showHealth: boolean;
18
+ searchQuery: string;
19
+ onSearchChange: (value: string) => void;
20
+ searchInputRef: RefObject<HTMLInputElement>;
21
+ }
22
+
23
+ export function ViewerHeader({
24
+ showHealth,
25
+ searchQuery,
26
+ onSearchChange,
27
+ searchInputRef,
28
+ }: ViewerHeaderProps) {
29
+ const { setTheme, resolvedTheme } = useTheme();
30
+ return (
31
+ <Header aria-label="Fragments viewer header">
32
+ <Header.Trigger />
33
+ <Header.Brand>
34
+ <Stack direction="row" gap="sm" align="center">
35
+ <FragmentsLogo size={20} />
36
+ <Text weight="medium" size="sm">
37
+ {BRAND.name}
38
+ </Text>
39
+ <Text size="xs" color="tertiary">
40
+ {showHealth ? "health dashboard" : "preview"}
41
+ </Text>
42
+ </Stack>
43
+ </Header.Brand>
44
+ <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
45
+ <Header.Spacer />
46
+ <Header.Actions>
47
+ <WebMCPStatusIndicator />
48
+ <ThemeToggle
49
+ size="sm"
50
+ value={resolvedTheme}
51
+ onValueChange={(value) => setTheme(value)}
52
+ aria-label={`Theme: ${resolvedTheme}`}
53
+ />
54
+ <Button
55
+ as="a"
56
+ variant="ghost"
57
+ size="sm"
58
+ icon
59
+ href="https://github.com/ConanMcN/fragments"
60
+ target="_blank"
61
+ rel="noopener noreferrer"
62
+ aria-label="View on GitHub"
63
+ >
64
+ <GitHubIcon />
65
+ </Button>
66
+ </Header.Actions>
67
+ </Header>
68
+ );
69
+ }
@@ -190,17 +190,15 @@ export function WebMCPDevTools() {
190
190
  ([name, prop]) => (
191
191
  <Box
192
192
  key={name}
193
- style={{
194
- padding: "6px 8px",
195
- borderRadius: "var(--radius-sm)",
196
- backgroundColor: "var(--bg-secondary)",
197
- }}
193
+ padding="sm"
194
+ rounded="sm"
195
+ background="secondary"
198
196
  >
199
197
  <Stack direction="row" gap="xs" align="center">
200
198
  <Text
201
199
  size="xs"
202
200
  weight="medium"
203
- style={{ fontFamily: "monospace" }}
201
+ font="mono"
204
202
  >
205
203
  {name}
206
204
  </Text>
@@ -219,7 +217,7 @@ export function WebMCPDevTools() {
219
217
  </Text>
220
218
  )}
221
219
  {prop.enum && (
222
- <Text size="2xs" color="tertiary" style={{ marginTop: "2px", fontFamily: "monospace" }}>
220
+ <Text size="2xs" color="tertiary" font="mono" style={{ marginTop: "2px" }}>
223
221
  enum: {prop.enum.join(" | ")}
224
222
  </Text>
225
223
  )}
@@ -292,9 +290,9 @@ export function WebMCPDevTools() {
292
290
  />
293
291
  ) : (
294
292
  <Box
293
+ padding="sm"
294
+ rounded="md"
295
295
  style={{
296
- padding: "12px",
297
- borderRadius: "var(--radius-md)",
298
296
  backgroundColor: "var(--bg-danger, #fef2f2)",
299
297
  }}
300
298
  >
@@ -425,14 +423,12 @@ export function WebMCPDevTools() {
425
423
  .map(([name, stat]) => (
426
424
  <Box
427
425
  key={name}
428
- style={{
429
- padding: "8px 10px",
430
- borderRadius: "var(--radius-sm)",
431
- backgroundColor: "var(--bg-secondary)",
432
- }}
426
+ padding="sm"
427
+ rounded="sm"
428
+ background="secondary"
433
429
  >
434
- <Stack direction="row" gap="sm" align="center" style={{ justifyContent: "space-between" }}>
435
- <Text size="xs" weight="medium" style={{ fontFamily: "monospace" }}>
430
+ <Stack direction="row" gap="sm" align="center" justify="between">
431
+ <Text size="xs" weight="medium" font="mono">
436
432
  {name}
437
433
  </Text>
438
434
  <Stack direction="row" gap="xs">
@@ -461,14 +457,12 @@ export function WebMCPDevTools() {
461
457
  {summary.commonFlows.map((flow, i) => (
462
458
  <Box
463
459
  key={i}
464
- style={{
465
- padding: "6px 8px",
466
- borderRadius: "var(--radius-sm)",
467
- backgroundColor: "var(--bg-secondary)",
468
- }}
460
+ padding="sm"
461
+ rounded="sm"
462
+ background="secondary"
469
463
  >
470
464
  <Stack direction="row" gap="xs" align="center">
471
- <Text size="2xs" style={{ fontFamily: "monospace" }}>
465
+ <Text size="2xs" font="mono">
472
466
  {flow.sequence.join(' → ')}
473
467
  </Text>
474
468
  <Badge size="sm" variant="info">{flow.count}x</Badge>
@@ -486,10 +480,10 @@ export function WebMCPDevTools() {
486
480
 
487
481
  {/* Protocol Status Footer */}
488
482
  <Box
483
+ borderTop
489
484
  style={{
490
485
  marginTop: "auto",
491
486
  paddingTop: "12px",
492
- borderTop: "1px solid var(--border-subtle)",
493
487
  }}
494
488
  >
495
489
  <Stack direction="row" gap="sm" align="center">
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared utility functions for viewer components.
3
+ */
4
+
5
+ export function normalizeAnchorSegment(value: string): string {
6
+ const normalized = value
7
+ .toLowerCase()
8
+ .trim()
9
+ .replace(/[^a-z0-9]+/g, "-")
10
+ .replace(/^-+|-+$/g, "");
11
+ return normalized || "variant";
12
+ }
13
+
14
+ export function getVariantSectionId(componentName: string, variantName: string): string {
15
+ return `preview-${normalizeAnchorSegment(componentName)}-${normalizeAnchorSegment(variantName)}`;
16
+ }
@@ -137,6 +137,7 @@ declare global {
137
137
  fragment: import("../core/index.js").FragmentDefinition;
138
138
  }>;
139
139
  __FRAGMENTS_CONFIG__?: import("../core/index.js").FragmentsConfig;
140
+ __FRAGMENTS_PACKAGE_NAME__?: string | null;
140
141
  __FRAGMENTS_ERROR__?: string;
141
142
  }
142
143
  }
@@ -175,6 +176,10 @@ async function loadFragmentsFromVirtualModule(): Promise<void> {
175
176
  fragments = virtualModule.fragments;
176
177
  }
177
178
  fragments = filterValidFragments(fragments);
179
+ // Store consumer package name for import path display
180
+ if (virtualModule.projectPackageName) {
181
+ window.__FRAGMENTS_PACKAGE_NAME__ = virtualModule.projectPackageName;
182
+ }
178
183
  } catch (e) {
179
184
  const error = e as Error;
180
185
  loadError = error.message || "Failed to load fragments";
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { useReducer, useCallback, useMemo } from 'react';
7
7
 
8
- export type ActivePanel = 'code' | 'styles' | 'accessibility' | 'interactions' | 'actions' | 'graph' | 'contract' | 'performance';
8
+ export type ActivePanel = 'styles' | 'accessibility' | 'interactions' | 'graph' | 'performance';
9
9
 
10
10
  interface AppUIState {
11
11
  activePanel: ActivePanel;
@@ -16,6 +16,7 @@ interface AppUIState {
16
16
  showMatrixView: boolean;
17
17
  showCommandPalette: boolean;
18
18
  showMultiViewport: boolean;
19
+ showAside: boolean;
19
20
  linkCopied: boolean;
20
21
  previewKey: number;
21
22
  }
@@ -34,17 +35,28 @@ type AppUIAction =
34
35
  | { type: 'SET_MULTI_VIEWPORT'; payload: boolean }
35
36
  | { type: 'SET_LINK_COPIED'; payload: boolean }
36
37
  | { type: 'INCREMENT_PREVIEW_KEY' }
38
+ | { type: 'TOGGLE_ASIDE' }
39
+ | { type: 'SET_ASIDE'; payload: boolean }
37
40
  | { type: 'CLOSE_ALL_MODALS' };
38
41
 
42
+ function getInitialAsideState(): boolean {
43
+ try {
44
+ const stored = localStorage.getItem('fragments-aside-visible');
45
+ if (stored !== null) return stored === 'true';
46
+ } catch { /* noop */ }
47
+ return true;
48
+ }
49
+
39
50
  const initialState: AppUIState = {
40
- activePanel: 'code',
41
- panelOpen: true,
51
+ activePanel: 'accessibility',
52
+ panelOpen: false,
42
53
  showHealthDashboard: false,
43
- showComparison: true,
54
+ showComparison: false,
44
55
  showShortcutsHelp: false,
45
56
  showMatrixView: false,
46
57
  showCommandPalette: false,
47
58
  showMultiViewport: false,
59
+ showAside: getInitialAsideState(),
48
60
  linkCopied: false,
49
61
  previewKey: 0,
50
62
  };
@@ -87,6 +99,15 @@ function appUIReducer(state: AppUIState, action: AppUIAction): AppUIState {
87
99
  return { ...state, linkCopied: action.payload };
88
100
  case 'INCREMENT_PREVIEW_KEY':
89
101
  return { ...state, previewKey: state.previewKey + 1 };
102
+ case 'TOGGLE_ASIDE': {
103
+ const next = !state.showAside;
104
+ try { localStorage.setItem('fragments-aside-visible', String(next)); } catch { /* noop */ }
105
+ return { ...state, showAside: next };
106
+ }
107
+ case 'SET_ASIDE': {
108
+ try { localStorage.setItem('fragments-aside-visible', String(action.payload)); } catch { /* noop */ }
109
+ return { ...state, showAside: action.payload };
110
+ }
90
111
  case 'CLOSE_ALL_MODALS':
91
112
  return {
92
113
  ...state,
@@ -115,6 +136,8 @@ export function useAppState() {
115
136
  setMultiViewport: (show: boolean) => dispatch({ type: 'SET_MULTI_VIEWPORT', payload: show }),
116
137
  setLinkCopied: (copied: boolean) => dispatch({ type: 'SET_LINK_COPIED', payload: copied }),
117
138
  incrementPreviewKey: () => dispatch({ type: 'INCREMENT_PREVIEW_KEY' }),
139
+ toggleAside: () => dispatch({ type: 'TOGGLE_ASIDE' }),
140
+ setAside: (show: boolean) => dispatch({ type: 'SET_ASIDE', payload: show }),
118
141
  closeAllModals: () => dispatch({ type: 'CLOSE_ALL_MODALS' }),
119
142
  }), []);
120
143
 
@@ -57,8 +57,8 @@ export function usePreviewBridge(iframeRef: RefObject<HTMLIFrameElement | null>)
57
57
  const initSentRef = useRef(false);
58
58
  const currentRenderIdRef = useRef<string | null>(null);
59
59
 
60
- // Render timeout duration (5 seconds)
61
- const RENDER_TIMEOUT = 5000;
60
+ // Render timeout duration (30 seconds)
61
+ const RENDER_TIMEOUT = 30000;
62
62
 
63
63
  // Listen for messages from iframe
64
64
  useEffect(() => {
@@ -28,24 +28,18 @@
28
28
 
29
29
  #preview-root {
30
30
  width: 100%;
31
- min-height: 100vh;
31
+ height: 100%;
32
+ min-height: 400px;
33
+ padding: 16px;
32
34
  display: flex;
33
- align-items: center;
34
- justify-content: center;
35
- padding: 24px;
35
+ flex-direction: column;
36
36
  box-sizing: border-box;
37
37
  overflow: auto;
38
38
  }
39
39
 
40
- body[data-preview-mode='full-bleed'] #preview-root {
41
- align-items: flex-start;
42
- justify-content: flex-start;
43
- padding: 0;
44
- min-height: 100vh;
45
- }
46
-
47
- body[data-preview-mode='full-bleed'] #preview-root > * {
40
+ #preview-root > * {
48
41
  width: 100%;
42
+ flex: 1;
49
43
  }
50
44
 
51
45
  /* Hide scrollbars but allow scrolling */