@fragments-sdk/cli 0.6.0 → 0.7.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 (178) hide show
  1. package/dist/bin.js +529 -285
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-F7ITZPDJ.js → chunk-32VIEOQY.js} +18 -18
  4. package/dist/chunk-32VIEOQY.js.map +1 -0
  5. package/dist/{chunk-SSLQXHNX.js → chunk-5ITIP3ES.js} +27 -27
  6. package/dist/chunk-5ITIP3ES.js.map +1 -0
  7. package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
  8. package/dist/chunk-DQHWLAUV.js.map +1 -0
  9. package/dist/{chunk-Q7GOHVOK.js → chunk-GCZMFLDI.js} +67 -32
  10. package/dist/chunk-GCZMFLDI.js.map +1 -0
  11. package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
  12. package/dist/chunk-GHYYFAQN.js.map +1 -0
  13. package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
  14. package/dist/chunk-GKX2HPZ6.js.map +1 -0
  15. package/dist/{chunk-D35RGPAG.js → chunk-U6VTHBNI.js} +499 -83
  16. package/dist/chunk-U6VTHBNI.js.map +1 -0
  17. package/dist/{core-SKRPJQZG.js → core-SFHPYR5H.js} +24 -26
  18. package/dist/{generate-7AF7WRVK.js → generate-54GJAWUY.js} +5 -5
  19. package/dist/generate-54GJAWUY.js.map +1 -0
  20. package/dist/index.d.ts +23 -27
  21. package/dist/index.js +10 -10
  22. package/dist/{init-WKGDPYI4.js → init-EIM5WNMP.js} +5 -5
  23. package/dist/{init-WKGDPYI4.js.map → init-EIM5WNMP.js.map} +1 -1
  24. package/dist/mcp-bin.js +73 -73
  25. package/dist/mcp-bin.js.map +1 -1
  26. package/dist/scan-KQBKUS64.js +12 -0
  27. package/dist/{service-F3E4JJM7.js → service-ED2LNCTU.js} +6 -6
  28. package/dist/{static-viewer-4LQZ5AGA.js → static-viewer-Q4F4QP5M.js} +4 -4
  29. package/dist/{test-CJDNJTPZ.js → test-6VN2DA3S.js} +19 -19
  30. package/dist/test-6VN2DA3S.js.map +1 -0
  31. package/dist/{tokens-JAJABYXP.js → tokens-P2B7ZAM3.js} +5 -5
  32. package/dist/{viewer-R3Q6WAMJ.js → viewer-GM7IQPPB.js} +199 -199
  33. package/dist/viewer-GM7IQPPB.js.map +1 -0
  34. package/package.json +2 -2
  35. package/src/ai.ts +5 -5
  36. package/src/analyze.ts +11 -11
  37. package/src/bin.ts +24 -1
  38. package/src/build.ts +64 -21
  39. package/src/commands/a11y.ts +6 -6
  40. package/src/commands/add.ts +11 -11
  41. package/src/commands/audit.ts +4 -4
  42. package/src/commands/baseline.ts +3 -3
  43. package/src/commands/build.ts +8 -8
  44. package/src/commands/compare.ts +20 -20
  45. package/src/commands/context.ts +16 -16
  46. package/src/commands/enhance.ts +36 -36
  47. package/src/commands/generate.ts +1 -1
  48. package/src/commands/graph.ts +274 -0
  49. package/src/commands/init.ts +1 -1
  50. package/src/commands/link/figma.ts +82 -82
  51. package/src/commands/link/index.ts +3 -3
  52. package/src/commands/link/storybook.ts +9 -9
  53. package/src/commands/list.ts +2 -2
  54. package/src/commands/reset.ts +15 -15
  55. package/src/commands/scan.ts +27 -27
  56. package/src/commands/storygen.ts +24 -24
  57. package/src/commands/validate.ts +2 -2
  58. package/src/commands/verify.ts +8 -8
  59. package/src/core/auto-props.ts +4 -4
  60. package/src/core/composition.test.ts +36 -36
  61. package/src/core/composition.ts +83 -20
  62. package/src/core/config.ts +6 -6
  63. package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
  64. package/src/core/discovery.ts +6 -6
  65. package/src/core/figma.ts +2 -2
  66. package/src/core/graph-extractor.test.ts +542 -0
  67. package/src/core/graph-extractor.ts +601 -0
  68. package/src/core/importAnalyzer.ts +6 -1
  69. package/src/core/index.ts +22 -23
  70. package/src/core/loader.ts +22 -22
  71. package/src/core/node.ts +5 -5
  72. package/src/core/parser.ts +31 -31
  73. package/src/core/previewLoader.ts +1 -1
  74. package/src/core/schema.ts +16 -16
  75. package/src/core/storyAdapter.test.ts +87 -87
  76. package/src/core/storyAdapter.ts +16 -16
  77. package/src/core/types.ts +21 -26
  78. package/src/diff.ts +22 -22
  79. package/src/index.ts +2 -2
  80. package/src/mcp/server.ts +80 -80
  81. package/src/migrate/__tests__/utils/utils.test.ts +3 -3
  82. package/src/migrate/bin.ts +4 -4
  83. package/src/migrate/converter.ts +16 -16
  84. package/src/migrate/index.ts +3 -3
  85. package/src/migrate/migrate.ts +3 -3
  86. package/src/migrate/parser.ts +8 -8
  87. package/src/migrate/report.ts +2 -2
  88. package/src/migrate/types.ts +4 -4
  89. package/src/screenshot.ts +22 -22
  90. package/src/service/__tests__/props-extractor.test.ts +15 -15
  91. package/src/service/analytics.ts +39 -39
  92. package/src/service/enhance/codebase-scanner.ts +1 -1
  93. package/src/service/enhance/index.ts +1 -1
  94. package/src/service/enhance/props-extractor.ts +2 -2
  95. package/src/service/enhance/types.ts +2 -2
  96. package/src/service/index.ts +2 -2
  97. package/src/service/metrics-store.ts +1 -1
  98. package/src/service/patch-generator.ts +1 -1
  99. package/src/setup.ts +52 -52
  100. package/src/shared/dev-server-client.ts +7 -7
  101. package/src/shared/fragment-loader.ts +59 -0
  102. package/src/shared/index.ts +1 -1
  103. package/src/shared/types.ts +4 -4
  104. package/src/static-viewer.ts +35 -35
  105. package/src/test/discovery.ts +6 -6
  106. package/src/test/index.ts +5 -5
  107. package/src/test/reporters/console.ts +1 -1
  108. package/src/test/reporters/junit.ts +1 -1
  109. package/src/test/runner.ts +7 -7
  110. package/src/test/types.ts +3 -3
  111. package/src/test/watch.ts +9 -9
  112. package/src/validators.ts +26 -26
  113. package/src/viewer/__tests__/render-utils.test.ts +28 -28
  114. package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
  115. package/src/viewer/cli/health.ts +26 -26
  116. package/src/viewer/components/App.tsx +201 -103
  117. package/src/viewer/components/BottomPanel.tsx +17 -17
  118. package/src/viewer/components/CodePanel.tsx +3 -3
  119. package/src/viewer/components/CommandPalette.tsx +11 -11
  120. package/src/viewer/components/ComponentGraph.tsx +28 -28
  121. package/src/viewer/components/ComponentHeader.tsx +2 -2
  122. package/src/viewer/components/ContractPanel.tsx +6 -6
  123. package/src/viewer/components/FigmaEmbed.tsx +9 -9
  124. package/src/viewer/components/HealthDashboard.tsx +17 -17
  125. package/src/viewer/components/Icons.tsx +53 -1
  126. package/src/viewer/components/InteractionsPanel.tsx +2 -2
  127. package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
  128. package/src/viewer/components/IsolatedRender.tsx +10 -10
  129. package/src/viewer/components/Layout.tsx +7 -3
  130. package/src/viewer/components/LeftSidebar.tsx +92 -114
  131. package/src/viewer/components/MultiViewportPreview.tsx +14 -14
  132. package/src/viewer/components/PreviewArea.tsx +11 -11
  133. package/src/viewer/components/PreviewFrameHost.tsx +77 -48
  134. package/src/viewer/components/PreviewToolbar.tsx +57 -10
  135. package/src/viewer/components/RightSidebar.tsx +9 -9
  136. package/src/viewer/components/Sidebar.tsx +17 -17
  137. package/src/viewer/components/StoryRenderer.tsx +2 -2
  138. package/src/viewer/components/TokenStylePanel.tsx +1 -1
  139. package/src/viewer/components/UsageSection.tsx +2 -2
  140. package/src/viewer/components/VariantMatrix.tsx +11 -11
  141. package/src/viewer/components/VariantRenderer.tsx +3 -3
  142. package/src/viewer/components/VariantTabs.tsx +2 -2
  143. package/src/viewer/components/ViewportSelector.tsx +56 -45
  144. package/src/viewer/components/_future/CreatePage.tsx +6 -6
  145. package/src/viewer/composition-renderer.ts +11 -11
  146. package/src/viewer/constants/ui.ts +4 -4
  147. package/src/viewer/entry.tsx +40 -40
  148. package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
  149. package/src/viewer/hooks/usePreviewBridge.ts +5 -5
  150. package/src/viewer/hooks/useUrlState.ts +6 -6
  151. package/src/viewer/index.ts +2 -2
  152. package/src/viewer/intelligence/healthReport.ts +17 -17
  153. package/src/viewer/intelligence/styleDrift.ts +1 -1
  154. package/src/viewer/intelligence/usageScanner.ts +1 -1
  155. package/src/viewer/preview-frame.html +22 -13
  156. package/src/viewer/render-template.html +1 -1
  157. package/src/viewer/render-utils.ts +21 -21
  158. package/src/viewer/server.ts +18 -18
  159. package/src/viewer/styles/globals.css +42 -81
  160. package/src/viewer/utils/detectRelationships.ts +22 -22
  161. package/src/viewer/vite-plugin.ts +213 -213
  162. package/dist/chunk-6JBGU74P.js.map +0 -1
  163. package/dist/chunk-D35RGPAG.js.map +0 -1
  164. package/dist/chunk-F7ITZPDJ.js.map +0 -1
  165. package/dist/chunk-NWQ4CJOQ.js.map +0 -1
  166. package/dist/chunk-Q7GOHVOK.js.map +0 -1
  167. package/dist/chunk-RVRTRESS.js.map +0 -1
  168. package/dist/chunk-SSLQXHNX.js.map +0 -1
  169. package/dist/generate-7AF7WRVK.js.map +0 -1
  170. package/dist/scan-K6JNMCGM.js +0 -12
  171. package/dist/test-CJDNJTPZ.js.map +0 -1
  172. package/dist/viewer-R3Q6WAMJ.js.map +0 -1
  173. package/src/shared/segment-loader.ts +0 -59
  174. /package/dist/{core-SKRPJQZG.js.map → core-SFHPYR5H.js.map} +0 -0
  175. /package/dist/{scan-K6JNMCGM.js.map → scan-KQBKUS64.js.map} +0 -0
  176. /package/dist/{service-F3E4JJM7.js.map → service-ED2LNCTU.js.map} +0 -0
  177. /package/dist/{static-viewer-4LQZ5AGA.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
  178. /package/dist/{tokens-JAJABYXP.js.map → tokens-P2B7ZAM3.js.map} +0 -0
@@ -3,8 +3,8 @@
3
3
  * Refactored for better performance and maintainability.
4
4
  */
5
5
 
6
- import { useState, useMemo, useEffect, useCallback, useRef } from "react";
7
- import type { SegmentDefinition } from "../../core/index.js";
6
+ import { useState, useMemo, useEffect, useCallback, useRef, type RefObject } from "react";
7
+ import { BRAND, type FragmentDefinition } from "../../core/index.js";
8
8
 
9
9
  // Layout & Navigation
10
10
  import { Layout } from "./Layout.js";
@@ -28,10 +28,10 @@ import { useAllFigmaUrls } from "./FigmaEmbed.js";
28
28
  import { ActionCapture } from "./ActionCapture.js";
29
29
 
30
30
  // Fragments UI
31
- import { Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert } from "@fragments/ui";
31
+ import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input } from "@fragments/ui";
32
32
 
33
33
  // Icons
34
- import { EmptyIcon, ExternalLinkIcon, CameraIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
34
+ import { EmptyIcon, ExternalLinkIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
35
35
 
36
36
  // Hooks
37
37
  import { useAppState } from "../hooks/useAppState.js";
@@ -39,7 +39,7 @@ import { useViewSettings } from "../hooks/useViewSettings.js";
39
39
  import { useFigmaIntegration } from "../hooks/useFigmaIntegration.js";
40
40
  import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
41
41
  import { useActions } from "../hooks/useActions.js";
42
- import { useUrlState, findSegmentByName, findVariantIndex } from "../hooks/useUrlState.js";
42
+ import { useUrlState, findFragmentByName, findVariantIndex } from "../hooks/useUrlState.js";
43
43
  import { usePanelDock } from "./ResizablePanel.js";
44
44
  import { useTheme } from "./ThemeProvider.js";
45
45
 
@@ -47,10 +47,10 @@ import { useTheme } from "./ThemeProvider.js";
47
47
  import { ScreenshotButton } from "./ScreenshotButton.js";
48
48
 
49
49
  interface AppProps {
50
- segments: Array<{ path: string; segment: SegmentDefinition }>;
50
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
51
51
  }
52
52
 
53
- export function App({ segments }: AppProps) {
53
+ export function App({ fragments }: AppProps) {
54
54
  // URL state management
55
55
  const { state: urlState, setComponent: setUrlComponent, setVariant: setUrlVariant, setViewSettings: setUrlViewSettings, copyUrl } = useUrlState();
56
56
 
@@ -81,38 +81,40 @@ export function App({ segments }: AppProps) {
81
81
  const { resolvedTheme } = useTheme();
82
82
 
83
83
  // Toast notifications (via Fragments UI ToastProvider)
84
- const { toast, info, success } = useToast();
84
+ const { info, success } = useToast();
85
85
 
86
86
  // Navigation state
87
- const [activeSegmentPath, setActiveSegmentPath] = useState<string | null>(() => {
87
+ const [activeFragmentPath, setActiveFragmentPath] = useState<string | null>(() => {
88
88
  if (urlState.component) {
89
- const found = findSegmentByName(segments, urlState.component);
90
- return found?.path ?? segments[0]?.path ?? null;
89
+ const found = findFragmentByName(fragments, urlState.component);
90
+ return found?.path ?? fragments[0]?.path ?? null;
91
91
  }
92
- return segments[0]?.path ?? null;
92
+ return fragments[0]?.path ?? null;
93
93
  });
94
94
 
95
95
  const [activeVariantIndex, setActiveVariantIndex] = useState<number>(() => {
96
- const segment = segments.find(s => s.path === activeSegmentPath);
97
- if (urlState.variant && segment?.segment.variants) {
98
- return findVariantIndex(segment.segment.variants, urlState.variant);
96
+ const fragment = fragments.find(s => s.path === activeFragmentPath);
97
+ if (urlState.variant && fragment?.fragment.variants) {
98
+ return findVariantIndex(fragment.fragment.variants, urlState.variant);
99
99
  }
100
100
  return 0;
101
101
  });
102
+ const [searchQuery, setSearchQuery] = useState('');
103
+ const searchInputRef = useRef<HTMLInputElement>(null);
102
104
 
103
105
  // Derived values
104
- const activeSegment = useMemo(
105
- () => segments.find((s) => s.path === activeSegmentPath),
106
- [segments, activeSegmentPath]
106
+ const activeFragment = useMemo(
107
+ () => fragments.find((s) => s.path === activeFragmentPath),
108
+ [fragments, activeFragmentPath]
107
109
  );
108
- const activeVariant = activeSegment?.segment.variants?.[activeVariantIndex];
109
- const figmaUrl = activeVariant?.figma || activeSegment?.segment.meta.figma;
110
+ const activeVariant = activeFragment?.fragment.variants?.[activeVariantIndex];
111
+ const figmaUrl = activeVariant?.figma || activeFragment?.fragment.meta.figma;
110
112
 
111
113
  // Figma integration
112
114
  const figmaIntegration = useFigmaIntegration({
113
115
  figmaUrl,
114
116
  showComparison: uiState.showComparison,
115
- dependencies: [activeSegmentPath, activeVariantIndex],
117
+ dependencies: [activeFragmentPath, activeVariantIndex],
116
118
  });
117
119
 
118
120
  // Actions logging
@@ -121,12 +123,12 @@ export function App({ segments }: AppProps) {
121
123
  useActionsRef.current = { logAction };
122
124
 
123
125
  // Figma URLs for preloading
124
- const allFigmaUrls = useAllFigmaUrls(activeSegment?.segment);
126
+ const allFigmaUrls = useAllFigmaUrls(activeFragment?.fragment);
125
127
 
126
128
  // Reset action logs on variant change
127
129
  useEffect(() => {
128
130
  clearActionLogs();
129
- }, [activeSegmentPath, activeVariantIndex, clearActionLogs]);
131
+ }, [activeFragmentPath, activeVariantIndex, clearActionLogs]);
130
132
 
131
133
  // Extract rendered styles after component renders
132
134
  useEffect(() => {
@@ -139,14 +141,14 @@ export function App({ segments }: AppProps) {
139
141
  // Sync URL state on browser navigation
140
142
  useEffect(() => {
141
143
  if (urlState.component) {
142
- const found = findSegmentByName(segments, urlState.component);
143
- if (found && found.path !== activeSegmentPath) {
144
- setActiveSegmentPath(found.path);
145
- const variantIndex = findVariantIndex(found.segment.variants, urlState.variant);
144
+ const found = findFragmentByName(fragments, urlState.component);
145
+ if (found && found.path !== activeFragmentPath) {
146
+ setActiveFragmentPath(found.path);
147
+ const variantIndex = findVariantIndex(found.fragment.variants, urlState.variant);
146
148
  setActiveVariantIndex(variantIndex);
147
149
  }
148
150
  }
149
- }, [urlState.component, urlState.variant, segments, activeSegmentPath]);
151
+ }, [urlState.component, urlState.variant, fragments, activeFragmentPath]);
150
152
 
151
153
  // HMR toast notifications
152
154
  useEffect(() => {
@@ -165,22 +167,22 @@ export function App({ segments }: AppProps) {
165
167
  }, [info]);
166
168
 
167
169
  // Navigation handlers
168
- const handleSelectSegment = useCallback((path: string) => {
169
- const segment = segments.find((s) => s.path === path);
170
- const componentName = segment?.segment.meta.name || path;
171
- const firstVariant = segment?.segment.variants?.[0]?.name;
170
+ const handleSelectFragment = useCallback((path: string) => {
171
+ const fragment = fragments.find((s) => s.path === path);
172
+ const componentName = fragment?.fragment.meta.name || path;
173
+ const firstVariant = fragment?.fragment.variants?.[0]?.name;
172
174
 
173
- setActiveSegmentPath(path);
175
+ setActiveFragmentPath(path);
174
176
  setActiveVariantIndex(0);
175
177
  uiActions.setHealthDashboard(false);
176
178
  setUrlComponent(componentName, firstVariant);
177
- }, [segments, setUrlComponent, uiActions]);
179
+ }, [fragments, setUrlComponent, uiActions]);
178
180
 
179
181
  const handleSelectVariant = useCallback((index: number) => {
180
- const variantName = activeSegment?.segment.variants?.[index]?.name;
182
+ const variantName = activeFragment?.fragment.variants?.[index]?.name;
181
183
  setActiveVariantIndex(index);
182
184
  setUrlVariant(variantName || null);
183
- }, [activeSegment, setUrlVariant]);
185
+ }, [activeFragment, setUrlVariant]);
184
186
 
185
187
  // Copy link handler
186
188
  const handleCopyLink = useCallback(async () => {
@@ -192,27 +194,32 @@ export function App({ segments }: AppProps) {
192
194
  }
193
195
  }, [copyUrl, success, uiActions]);
194
196
 
195
- // Sorted segment paths for keyboard navigation
196
- const sortedSegmentPaths = useMemo(() => {
197
- return [...segments]
198
- .filter(s => s.segment?.meta?.name)
199
- .sort((a, b) => a.segment.meta.name.localeCompare(b.segment.meta.name))
197
+ const focusSearchInput = useCallback(() => {
198
+ searchInputRef.current?.focus();
199
+ searchInputRef.current?.select();
200
+ }, []);
201
+
202
+ // Sorted fragment paths for keyboard navigation
203
+ const sortedFragmentPaths = useMemo(() => {
204
+ return [...fragments]
205
+ .filter(s => s.fragment?.meta?.name)
206
+ .sort((a, b) => a.fragment.meta.name.localeCompare(b.fragment.meta.name))
200
207
  .map(s => s.path);
201
- }, [segments]);
208
+ }, [fragments]);
202
209
 
203
- const currentSegmentIndex = sortedSegmentPaths.indexOf(activeSegmentPath || '');
204
- const variantCount = activeSegment?.segment.variants?.length || 0;
210
+ const currentFragmentIndex = sortedFragmentPaths.indexOf(activeFragmentPath || '');
211
+ const variantCount = activeFragment?.fragment.variants?.length || 0;
205
212
 
206
213
  // Keyboard shortcuts
207
214
  useKeyboardShortcuts(
208
215
  {
209
216
  nextComponent: () => {
210
- const nextIndex = currentSegmentIndex < sortedSegmentPaths.length - 1 ? currentSegmentIndex + 1 : 0;
211
- handleSelectSegment(sortedSegmentPaths[nextIndex]);
217
+ const nextIndex = currentFragmentIndex < sortedFragmentPaths.length - 1 ? currentFragmentIndex + 1 : 0;
218
+ handleSelectFragment(sortedFragmentPaths[nextIndex]);
212
219
  },
213
220
  prevComponent: () => {
214
- const prevIndex = currentSegmentIndex > 0 ? currentSegmentIndex - 1 : sortedSegmentPaths.length - 1;
215
- handleSelectSegment(sortedSegmentPaths[prevIndex]);
221
+ const prevIndex = currentFragmentIndex > 0 ? currentFragmentIndex - 1 : sortedFragmentPaths.length - 1;
222
+ handleSelectFragment(sortedFragmentPaths[prevIndex]);
216
223
  },
217
224
  nextVariant: () => handleSelectVariant(activeVariantIndex < variantCount - 1 ? activeVariantIndex + 1 : 0),
218
225
  prevVariant: () => handleSelectVariant(activeVariantIndex > 0 ? activeVariantIndex - 1 : variantCount - 1),
@@ -221,8 +228,18 @@ export function App({ segments }: AppProps) {
221
228
  togglePanel: uiActions.togglePanel,
222
229
  copyLink: handleCopyLink,
223
230
  showHelp: uiActions.toggleShortcutsHelp,
224
- openSearch: () => uiActions.setCommandPalette(true),
225
- escape: uiActions.closeAllModals,
231
+ openSearch: focusSearchInput,
232
+ escape: () => {
233
+ if (document.activeElement === searchInputRef.current) {
234
+ if (searchQuery) {
235
+ setSearchQuery('');
236
+ } else {
237
+ searchInputRef.current.blur();
238
+ }
239
+ return;
240
+ }
241
+ uiActions.closeAllModals();
242
+ },
226
243
  },
227
244
  { enabled: !uiState.showShortcutsHelp, variantCount }
228
245
  );
@@ -252,7 +269,7 @@ export function App({ segments }: AppProps) {
252
269
  }, []);
253
270
 
254
271
  if (isIsolated) {
255
- return <IsolatedRender segments={segments} />;
272
+ return <IsolatedRender fragments={fragments} />;
256
273
  }
257
274
 
258
275
  return (
@@ -261,24 +278,49 @@ export function App({ segments }: AppProps) {
261
278
  <CommandPalette
262
279
  isOpen={uiState.showCommandPalette}
263
280
  onClose={() => uiActions.setCommandPalette(false)}
264
- segments={segments}
265
- onSelectComponent={handleSelectSegment}
281
+ fragments={fragments}
282
+ onSelectComponent={handleSelectFragment}
266
283
  onSelectVariant={(path, variantIndex) => {
267
- handleSelectSegment(path);
284
+ handleSelectFragment(path);
268
285
  setTimeout(() => handleSelectVariant(variantIndex), 0);
269
286
  }}
270
287
  />
271
288
 
272
289
  <Layout
290
+ header={
291
+ activeFragment && !uiState.showHealthDashboard ? (
292
+ <TopToolbar
293
+ fragment={activeFragment}
294
+ variant={activeVariant}
295
+ viewSettings={viewSettings}
296
+ uiState={uiState}
297
+ uiActions={uiActions}
298
+ figmaUrl={figmaUrl}
299
+ linkCopied={uiState.linkCopied}
300
+ onCopyLink={handleCopyLink}
301
+ searchQuery={searchQuery}
302
+ onSearchChange={setSearchQuery}
303
+ searchInputRef={searchInputRef}
304
+ />
305
+ ) : (
306
+ <ViewerHeader
307
+ showHealth={uiState.showHealthDashboard}
308
+ searchQuery={searchQuery}
309
+ onSearchChange={setSearchQuery}
310
+ searchInputRef={searchInputRef}
311
+ />
312
+ )
313
+ }
273
314
  leftSidebar={
274
315
  <LeftSidebar
275
- segments={segments}
276
- activeSegment={uiState.showHealthDashboard ? null : activeSegmentPath}
277
- onSelect={handleSelectSegment}
316
+ fragments={fragments}
317
+ activeFragment={uiState.showHealthDashboard ? null : activeFragmentPath}
318
+ searchQuery={searchQuery}
319
+ onSelect={handleSelectFragment}
278
320
  showHealth={uiState.showHealthDashboard}
279
321
  onHealthClick={() => {
280
322
  uiActions.setHealthDashboard(true);
281
- setActiveSegmentPath(null);
323
+ setActiveFragmentPath(null);
282
324
  }}
283
325
  />
284
326
  }
@@ -287,37 +329,25 @@ export function App({ segments }: AppProps) {
287
329
  <div style={{ height: '100%', overflow: 'auto', backgroundColor: 'var(--bg-primary)' }}>
288
330
  <Box padding="lg" style={{ maxWidth: '896px', margin: '0 auto' }}>
289
331
  <HealthDashboard
290
- segments={segments}
332
+ fragments={fragments}
291
333
  onNavigate={(componentName) => {
292
- const target = segments.find(s => s.segment.meta.name === componentName);
334
+ const target = fragments.find(s => s.fragment.meta.name === componentName);
293
335
  if (target) {
294
336
  uiActions.setHealthDashboard(false);
295
- handleSelectSegment(target.path);
337
+ handleSelectFragment(target.path);
296
338
  }
297
339
  }}
298
340
  />
299
341
  </Box>
300
342
  </div>
301
- ) : activeSegment ? (
343
+ ) : activeFragment ? (
302
344
  <div style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
303
345
  {/* Main Content Area */}
304
346
  <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
305
- {/* Top Toolbar */}
306
- <TopToolbar
307
- segment={activeSegment}
308
- variant={activeVariant}
309
- viewSettings={viewSettings}
310
- uiState={uiState}
311
- uiActions={uiActions}
312
- figmaUrl={figmaUrl}
313
- linkCopied={uiState.linkCopied}
314
- onCopyLink={handleCopyLink}
315
- />
316
-
317
347
  {/* Variant Tabs */}
318
- {activeSegment.segment.variants && activeSegment.segment.variants.length > 0 && (
348
+ {activeFragment.fragment.variants && activeFragment.fragment.variants.length > 0 && (
319
349
  <VariantTabsBar
320
- variants={activeSegment.segment.variants}
350
+ variants={activeFragment.fragment.variants}
321
351
  activeIndex={activeVariantIndex}
322
352
  onSelect={handleSelectVariant}
323
353
  showMatrixView={uiState.showMatrixView}
@@ -338,10 +368,10 @@ export function App({ segments }: AppProps) {
338
368
  >
339
369
  {activeVariant ? (
340
370
  <PreviewArea
341
- componentName={activeSegment.segment.meta.name}
342
- segmentPath={activeSegment.path}
371
+ componentName={activeFragment.fragment.meta.name}
372
+ fragmentPath={activeFragment.path}
343
373
  variant={activeVariant}
344
- variants={activeSegment.segment.variants}
374
+ variants={activeFragment.fragment.variants}
345
375
  zoom={viewSettings.zoom}
346
376
  background={viewSettings.background}
347
377
  viewport={viewSettings.viewport}
@@ -358,10 +388,10 @@ export function App({ segments }: AppProps) {
358
388
  }}
359
389
  onRetry={uiActions.incrementPreviewKey}
360
390
  renderContent={renderVariantWithProps}
361
- previewKey={`${activeSegmentPath}-${activeVariantIndex}-${uiState.previewKey}`}
391
+ previewKey={`${activeFragmentPath}-${activeVariantIndex}-${uiState.previewKey}`}
362
392
  />
363
393
  ) : (
364
- <NoVariantsMessage segment={activeSegment?.segment} />
394
+ <NoVariantsMessage fragment={activeFragment?.fragment} />
365
395
  )}
366
396
  </div>
367
397
  </div>
@@ -369,9 +399,9 @@ export function App({ segments }: AppProps) {
369
399
  {/* Bottom Panel */}
370
400
  {activeVariant && (
371
401
  <BottomPanel
372
- segment={activeSegment.segment}
402
+ fragment={activeFragment.fragment}
373
403
  variant={activeVariant}
374
- segments={segments}
404
+ fragments={fragments}
375
405
  activePanel={uiState.activePanel}
376
406
  onPanelChange={uiActions.setActivePanel}
377
407
  figmaUrl={figmaUrl}
@@ -384,11 +414,11 @@ export function App({ segments }: AppProps) {
384
414
  actionLogs={actionLogs}
385
415
  onClearActionLogs={clearActionLogs}
386
416
  onNavigateToComponent={(name) => {
387
- const target = segments.find(s => s.segment.meta.name === name);
388
- if (target) handleSelectSegment(target.path);
417
+ const target = fragments.find(s => s.fragment.meta.name === name);
418
+ if (target) handleSelectFragment(target.path);
389
419
  }}
390
420
  previewKey={uiState.previewKey}
391
- segmentKey={`${activeSegmentPath}-${activeVariantIndex}`}
421
+ fragmentKey={`${activeFragmentPath}-${activeVariantIndex}`}
392
422
  />
393
423
  )}
394
424
  </div>
@@ -408,7 +438,7 @@ export function App({ segments }: AppProps) {
408
438
 
409
439
  // Top Toolbar Component
410
440
  interface TopToolbarProps {
411
- segment: { path: string; segment: SegmentDefinition };
441
+ fragment: { path: string; fragment: FragmentDefinition };
412
442
  variant: any;
413
443
  viewSettings: ReturnType<typeof useViewSettings>;
414
444
  uiState: ReturnType<typeof useAppState>['state'];
@@ -416,16 +446,82 @@ interface TopToolbarProps {
416
446
  figmaUrl?: string;
417
447
  linkCopied: boolean;
418
448
  onCopyLink: () => void;
449
+ searchQuery: string;
450
+ onSearchChange: (value: string) => void;
451
+ searchInputRef: RefObject<HTMLInputElement>;
452
+ }
453
+
454
+ interface ViewerHeaderProps {
455
+ showHealth: boolean;
456
+ searchQuery: string;
457
+ onSearchChange: (value: string) => void;
458
+ searchInputRef: RefObject<HTMLInputElement>;
459
+ }
460
+
461
+ interface HeaderSearchProps {
462
+ value: string;
463
+ onChange: (value: string) => void;
464
+ inputRef: RefObject<HTMLInputElement>;
419
465
  }
420
466
 
421
- function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaUrl, linkCopied, onCopyLink }: TopToolbarProps) {
467
+ function HeaderSearch({ value, onChange, inputRef }: HeaderSearchProps) {
422
468
  return (
423
- <Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-secondary)', flexShrink: 0 }}>
424
- <Stack direction="row" align="center" gap="sm">
425
- <Text weight="medium" size="sm">{segment.segment.meta.name}</Text>
426
- <Text size="xs" color="tertiary">{segment.segment.meta.category}</Text>
427
- </Stack>
428
- <Stack direction="row" align="center" gap="sm">
469
+ <Header.Search expandable>
470
+ <Input
471
+ ref={inputRef}
472
+ value={value}
473
+ onChange={onChange}
474
+ placeholder="Search components"
475
+ aria-label="Search components"
476
+ size="sm"
477
+ shortcut="⌘K"
478
+ style={{ width: '220px' }}
479
+ />
480
+ </Header.Search>
481
+ );
482
+ }
483
+
484
+ function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef }: ViewerHeaderProps) {
485
+ return (
486
+ <Header aria-label="Fragments viewer header">
487
+ <Header.Trigger />
488
+ <Header.Brand>
489
+ <Stack direction="row" gap="sm" align="center">
490
+ <Text weight="medium" size="sm">{BRAND.name}</Text>
491
+ <Text size="xs" color="tertiary">{showHealth ? 'health dashboard' : 'preview'}</Text>
492
+ </Stack>
493
+ </Header.Brand>
494
+ <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
495
+ <Header.Spacer />
496
+ </Header>
497
+ );
498
+ }
499
+
500
+ function TopToolbar({
501
+ fragment,
502
+ variant,
503
+ viewSettings,
504
+ uiState,
505
+ uiActions,
506
+ figmaUrl,
507
+ linkCopied,
508
+ onCopyLink,
509
+ searchQuery,
510
+ onSearchChange,
511
+ searchInputRef,
512
+ }: TopToolbarProps) {
513
+ return (
514
+ <Header aria-label="Component preview toolbar">
515
+ <Header.Trigger />
516
+ <Header.Brand>
517
+ <Stack direction="row" align="center" gap="sm">
518
+ <Text weight="medium" size="sm">{fragment.fragment.meta.name}</Text>
519
+ <Text size="xs" color="tertiary">{fragment.fragment.meta.category}</Text>
520
+ </Stack>
521
+ </Header.Brand>
522
+ <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
523
+ <Header.Spacer />
524
+ <Header.Actions>
429
525
  <PreviewToolbar
430
526
  zoom={viewSettings.zoom}
431
527
  background={viewSettings.background}
@@ -474,7 +570,7 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
474
570
  const url = new URL(window.location.href);
475
571
  url.hash = '';
476
572
  url.searchParams.set('isolated', 'true');
477
- url.searchParams.set('component', segment.segment.meta.name);
573
+ url.searchParams.set('component', fragment.fragment.meta.name);
478
574
  url.searchParams.set('variant', variant.name);
479
575
  if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
480
576
  if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
@@ -486,7 +582,7 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
486
582
  <ExternalLinkIcon style={{ width: '16px', height: '16px' }} />
487
583
  </Button>
488
584
  </Tooltip>
489
- <ScreenshotButton componentName={segment.segment.meta.name} variantName={variant.name} />
585
+ <ScreenshotButton componentName={fragment.fragment.meta.name} variantName={variant.name} />
490
586
  <Tooltip content="Copy link to share">
491
587
  <Button
492
588
  onClick={onCopyLink}
@@ -499,8 +595,8 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
499
595
  </Tooltip>
500
596
  </>
501
597
  )}
502
- </Stack>
503
- </Stack>
598
+ </Header.Actions>
599
+ </Header>
504
600
  );
505
601
  }
506
602
 
@@ -519,11 +615,13 @@ function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showM
519
615
  return (
520
616
  <Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
521
617
  {!showMatrixView ? (
522
- <VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
618
+ <ScrollArea orientation="horizontal" showFades style={{ flex: 1, minWidth: 0 }}>
619
+ <VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
620
+ </ScrollArea>
523
621
  ) : (
524
622
  <Text size="sm" color="secondary">Showing all {variants.length} variants</Text>
525
623
  )}
526
- <Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px' }}>
624
+ <Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px', flexShrink: 0 }}>
527
625
  {variants.length > 1 && (
528
626
  <Button
529
627
  onClick={onToggleMatrix}
@@ -553,11 +651,11 @@ function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showM
553
651
 
554
652
  // No variants message
555
653
  interface NoVariantsMessageProps {
556
- segment?: SegmentDefinition;
654
+ fragment?: FragmentDefinition;
557
655
  }
558
656
 
559
- function NoVariantsMessage({ segment }: NoVariantsMessageProps) {
560
- const skippedVariants = (segment?._generated as any)?.skippedVariants;
657
+ function NoVariantsMessage({ fragment }: NoVariantsMessageProps) {
658
+ const skippedVariants = (fragment?._generated as any)?.skippedVariants;
561
659
 
562
660
  if (!skippedVariants || skippedVariants.length === 0) {
563
661
  return (
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { memo, useCallback } from 'react';
7
- import type { SegmentDefinition, SegmentVariant } from '../../core/index.js';
7
+ import type { FragmentDefinition, FragmentVariant } from '../../core/index.js';
8
8
  import { Tabs, Badge } from '@fragments/ui';
9
9
  import { ResizablePanel } from './ResizablePanel.js';
10
10
  import { CodePanel } from './CodePanel.js';
@@ -19,9 +19,9 @@ import type { ActionLog } from '../hooks/useActions.js';
19
19
 
20
20
  interface BottomPanelProps {
21
21
  // Component data
22
- segment: SegmentDefinition;
23
- variant: SegmentVariant;
24
- segments: Array<{ path: string; segment: SegmentDefinition }>;
22
+ fragment: FragmentDefinition;
23
+ variant: FragmentVariant;
24
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
25
25
 
26
26
  // Panel state
27
27
  activePanel: ActivePanel;
@@ -45,13 +45,13 @@ interface BottomPanelProps {
45
45
 
46
46
  // Keys
47
47
  previewKey: number;
48
- segmentKey: string;
48
+ fragmentKey: string;
49
49
  }
50
50
 
51
51
  export const BottomPanel = memo(function BottomPanel({
52
- segment,
52
+ fragment,
53
53
  variant,
54
- segments,
54
+ fragments,
55
55
  activePanel,
56
56
  onPanelChange,
57
57
  figmaUrl,
@@ -65,7 +65,7 @@ export const BottomPanel = memo(function BottomPanel({
65
65
  onClearActionLogs,
66
66
  onNavigateToComponent,
67
67
  previewKey,
68
- segmentKey,
68
+ fragmentKey,
69
69
  }: BottomPanelProps) {
70
70
  const handleStylesClick = useCallback(() => {
71
71
  onPanelChange('styles');
@@ -121,8 +121,8 @@ export const BottomPanel = memo(function BottomPanel({
121
121
  <div style={{ padding: '16px' }}>
122
122
  <CodePanel
123
123
  variant={variant}
124
- componentName={segment.meta.name}
125
- propDefs={segment.props}
124
+ componentName={fragment.meta.name}
125
+ propDefs={fragment.props}
126
126
  compact
127
127
  />
128
128
  </div>
@@ -142,17 +142,17 @@ export const BottomPanel = memo(function BottomPanel({
142
142
 
143
143
  {activePanel === 'accessibility' && (
144
144
  <AccessibilityPanel
145
- cacheKey={segmentKey}
145
+ cacheKey={fragmentKey}
146
146
  previewKey={previewKey}
147
147
  autoScan={true}
148
- componentName={segment.meta.name}
148
+ componentName={fragment.meta.name}
149
149
  variantName={variant.name}
150
150
  />
151
151
  )}
152
152
 
153
153
  {activePanel === 'interactions' && (
154
154
  <InteractionsPanel
155
- key={segmentKey}
155
+ key={fragmentKey}
156
156
  variant={variant}
157
157
  previewKey={previewKey}
158
158
  />
@@ -167,16 +167,16 @@ export const BottomPanel = memo(function BottomPanel({
167
167
 
168
168
  {activePanel === 'graph' && (
169
169
  <ComponentGraph
170
- segment={segment}
171
- allSegments={segments}
170
+ fragment={fragment}
171
+ allFragments={fragments}
172
172
  onNavigate={onNavigateToComponent}
173
173
  />
174
174
  )}
175
175
 
176
176
  {activePanel === 'contract' && (
177
177
  <ContractPanel
178
- contract={segment.contract}
179
- componentName={segment.meta.name}
178
+ contract={fragment.contract}
179
+ componentName={fragment.meta.name}
180
180
  />
181
181
  )}
182
182
  </ResizablePanel>
@@ -1,16 +1,16 @@
1
1
  import { useMemo, isValidElement, type ReactNode } from 'react';
2
- import type { SegmentVariant, PropDefinition } from '../../core/index.js';
2
+ import type { FragmentVariant, PropDefinition } from '../../core/index.js';
3
3
  import { CodeBlock } from '@fragments/ui';
4
4
 
5
5
  interface CodePanelProps {
6
- variant: SegmentVariant;
6
+ variant: FragmentVariant;
7
7
  componentName: string;
8
8
  compact?: boolean;
9
9
  propDefs?: Record<string, PropDefinition>;
10
10
  }
11
11
 
12
12
  // Extract props from rendered element by calling render() and introspecting
13
- function extractPropsFromRender(variant: SegmentVariant, componentName: string): Record<string, unknown> | null {
13
+ function extractPropsFromRender(variant: FragmentVariant, componentName: string): Record<string, unknown> | null {
14
14
  try {
15
15
  const rendered = variant.render();
16
16
  if (!isValidElement(rendered)) return null;