@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
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { useState, useEffect, useRef, useMemo, useCallback } from "react";
13
13
  import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments/ui';
14
- import type { SegmentDefinition } from "../../core/index.js";
14
+ import type { FragmentDefinition } from "../../core/index.js";
15
15
  import { SearchIcon, ChevronRightIcon } from "./Icons.js";
16
16
 
17
17
  interface CommandPaletteProps {
@@ -19,8 +19,8 @@ interface CommandPaletteProps {
19
19
  isOpen: boolean;
20
20
  /** Callback to close the palette */
21
21
  onClose: () => void;
22
- /** All available segments */
23
- segments: Array<{ path: string; segment: SegmentDefinition }>;
22
+ /** All available fragments */
23
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
24
24
  /** Callback when a component is selected */
25
25
  onSelectComponent: (path: string) => void;
26
26
  /** Callback when a variant is selected */
@@ -40,7 +40,7 @@ interface SearchResult {
40
40
  export function CommandPalette({
41
41
  isOpen,
42
42
  onClose,
43
- segments,
43
+ fragments,
44
44
  onSelectComponent,
45
45
  onSelectVariant,
46
46
  }: CommandPaletteProps) {
@@ -53,9 +53,9 @@ export function CommandPalette({
53
53
  const results = useMemo(() => {
54
54
  const allResults: SearchResult[] = [];
55
55
 
56
- for (const { path, segment } of segments) {
57
- const componentName = segment.meta.name;
58
- const category = segment.meta.category;
56
+ for (const { path, fragment } of fragments) {
57
+ const componentName = fragment.meta.name;
58
+ const category = fragment.meta.category;
59
59
 
60
60
  // Add component result
61
61
  allResults.push({
@@ -67,9 +67,9 @@ export function CommandPalette({
67
67
  });
68
68
 
69
69
  // Add variant results
70
- if (segment.variants) {
71
- for (let i = 0; i < segment.variants.length; i++) {
72
- const variant = segment.variants[i];
70
+ if (fragment.variants) {
71
+ for (let i = 0; i < fragment.variants.length; i++) {
72
+ const variant = fragment.variants[i];
73
73
  allResults.push({
74
74
  type: "variant",
75
75
  path,
@@ -106,7 +106,7 @@ export function CommandPalette({
106
106
  .slice(0, 20);
107
107
 
108
108
  return scored;
109
- }, [segments, query]);
109
+ }, [fragments, query]);
110
110
 
111
111
  // Reset selection when results change
112
112
  useEffect(() => {
@@ -9,16 +9,16 @@
9
9
  */
10
10
 
11
11
  import { useMemo, useState } from "react";
12
- import type { SegmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
12
+ import type { FragmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
13
13
  import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
14
14
  import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
15
15
  import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments/ui";
16
16
 
17
17
  interface ComponentGraphProps {
18
- /** Current segment definition */
19
- segment: SegmentDefinition | null;
20
- /** All available segments for navigation */
21
- allSegments: Array<{ path: string; segment: SegmentDefinition }>;
18
+ /** Current fragment definition */
19
+ fragment: FragmentDefinition | null;
20
+ /** All available fragments for navigation */
21
+ allFragments: Array<{ path: string; fragment: FragmentDefinition }>;
22
22
  /** Callback when a component is clicked */
23
23
  onNavigate?: (componentName: string) => void;
24
24
  }
@@ -68,24 +68,24 @@ const RELATIONSHIP_CONFIG: Record<RelationshipType, { label: string; color: stri
68
68
  },
69
69
  };
70
70
 
71
- export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGraphProps) {
71
+ export function ComponentGraph({ fragment, allFragments, onNavigate }: ComponentGraphProps) {
72
72
  const [showAutoDetected, setShowAutoDetected] = useState(true);
73
73
 
74
74
  // Auto-detect relationships
75
75
  const detectedRelationships = useMemo(() => {
76
- if (!segment) return [];
76
+ if (!fragment) return [];
77
77
  try {
78
- return detectAllRelationships(segment, allSegments);
78
+ return detectAllRelationships(fragment, allFragments);
79
79
  } catch {
80
80
  return [];
81
81
  }
82
- }, [segment, allSegments]);
82
+ }, [fragment, allFragments]);
83
83
 
84
84
  // Merge manual and detected relationships
85
85
  const allRelationships = useMemo(() => {
86
- if (!segment) return [];
87
- return mergeRelationships(segment.relations, showAutoDetected ? detectedRelationships : []);
88
- }, [segment, detectedRelationships, showAutoDetected]);
86
+ if (!fragment) return [];
87
+ return mergeRelationships(fragment.relations, showAutoDetected ? detectedRelationships : []);
88
+ }, [fragment, detectedRelationships, showAutoDetected]);
89
89
 
90
90
  // Group relations by type
91
91
  const groupedRelations = useMemo(() => {
@@ -110,22 +110,22 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
110
110
 
111
111
  // Find reverse relations (components that reference this one)
112
112
  const reverseRelations = useMemo(() => {
113
- if (!segment) return [];
113
+ if (!fragment) return [];
114
114
 
115
- const componentName = segment.meta.name;
116
- const reverse: Array<{ segment: SegmentDefinition; relation: ComponentRelation }> = [];
115
+ const componentName = fragment.meta.name;
116
+ const reverse: Array<{ fragment: FragmentDefinition; relation: ComponentRelation }> = [];
117
117
 
118
- for (const { segment: otherSegment } of allSegments) {
119
- if (otherSegment.meta.name === componentName) continue;
120
- if (!otherSegment.relations) continue;
118
+ for (const { fragment: otherFragment } of allFragments) {
119
+ if (otherFragment.meta.name === componentName) continue;
120
+ if (!otherFragment.relations) continue;
121
121
 
122
- for (const relation of otherSegment.relations) {
122
+ for (const relation of otherFragment.relations) {
123
123
  if (relation.component === componentName) {
124
124
  reverse.push({
125
- segment: otherSegment,
125
+ fragment: otherFragment,
126
126
  relation: {
127
127
  ...relation,
128
- component: otherSegment.meta.name,
128
+ component: otherFragment.meta.name,
129
129
  // Flip the relationship direction for display
130
130
  relationship: flipRelationship(relation.relationship),
131
131
  },
@@ -135,11 +135,11 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
135
135
  }
136
136
 
137
137
  return reverse;
138
- }, [segment, allSegments]);
138
+ }, [fragment, allFragments]);
139
139
 
140
- // Check if a component exists in our segments
140
+ // Check if a component exists in our fragments
141
141
  const componentExists = (name: string) => {
142
- return allSegments.some(s => s.segment.meta.name === name);
142
+ return allFragments.some(s => s.fragment.meta.name === name);
143
143
  };
144
144
 
145
145
  const handleNavigate = (componentName: string) => {
@@ -148,8 +148,8 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
148
148
  }
149
149
  };
150
150
 
151
- // No segment selected
152
- if (!segment) {
151
+ // No fragment selected
152
+ if (!fragment) {
153
153
  return (
154
154
  <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
155
155
  <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
@@ -184,7 +184,7 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
184
184
  </EmptyState.Icon>
185
185
  <EmptyState.Title>No relationships defined</EmptyState.Title>
186
186
  <EmptyState.Description>
187
- This component doesn't have any defined relationships. Add a <code style={{ padding: '2px 4px', background: 'var(--bg-secondary)', borderRadius: '4px', fontSize: '12px' }}>relations</code> array to your segment definition to visualize component connections.
187
+ This component doesn't have any defined relationships. Add a <code style={{ padding: '2px 4px', background: 'var(--bg-secondary)', borderRadius: '4px', fontSize: '12px' }}>relations</code> array to your fragment definition to visualize component connections.
188
188
  </EmptyState.Description>
189
189
  <div style={{ marginTop: '16px', width: '100%' }}>
190
190
  <CodeBlock language="typescript">{`relations: [
@@ -220,7 +220,7 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
220
220
  )}
221
221
  </Stack>
222
222
  <Text size="xs" color="secondary" style={{ marginTop: '4px' }}>
223
- Relationships for <Text as="span" weight="medium" size="xs">{segment.meta.name}</Text>
223
+ Relationships for <Text as="span" weight="medium" size="xs">{fragment.meta.name}</Text>
224
224
  {showAutoDetected && detectedCount > 0 && (
225
225
  <span style={{ marginLeft: '4px' }}>
226
226
  ({detectedCount} from variants)
@@ -1,9 +1,9 @@
1
- import type { SegmentMeta } from '../../core/index.js';
1
+ import type { FragmentMeta } from '../../core/index.js';
2
2
  import { Badge, Alert, Text, Stack, Separator } from '@fragments/ui';
3
3
  import { WarningIcon, BeakerIcon } from './Icons.js';
4
4
 
5
5
  interface ComponentHeaderProps {
6
- meta: SegmentMeta;
6
+ meta: FragmentMeta;
7
7
  }
8
8
 
9
9
  // Map status to Badge variant
@@ -1,14 +1,14 @@
1
1
  /**
2
- * ContractPanel component - displays SegmentContract metadata for AI agents.
2
+ * ContractPanel component - displays FragmentContract metadata for AI agents.
3
3
  * Shows propsSummary, scenarioTags, bans, and a11yRules.
4
4
  */
5
5
 
6
6
  import { memo } from 'react';
7
- import type { SegmentContract } from '../../core/index.js';
7
+ import type { FragmentContract } from '../../core/index.js';
8
8
  import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments/ui';
9
9
 
10
10
  interface ContractPanelProps {
11
- contract?: SegmentContract;
11
+ contract?: FragmentContract;
12
12
  componentName: string;
13
13
  }
14
14
 
@@ -37,7 +37,7 @@ export const ContractPanel = memo(function ContractPanel({
37
37
  </EmptyState.Icon>
38
38
  <EmptyState.Title>No contract metadata</EmptyState.Title>
39
39
  <EmptyState.Description>
40
- Add a <code style={{ background: 'var(--bg-hover)', padding: '2px 4px', borderRadius: '4px' }}>contract</code> field to the segment definition to enable AI agent features.
40
+ Add a <code style={{ background: 'var(--bg-hover)', padding: '2px 4px', borderRadius: '4px' }}>contract</code> field to the fragment definition to enable AI agent features.
41
41
  </EmptyState.Description>
42
42
  <EmptyState.Actions>
43
43
  <CodeBlock
@@ -186,7 +186,7 @@ function Section({ title, subtitle, children }: SectionProps) {
186
186
  }
187
187
 
188
188
  // Check if contract is empty or has no meaningful content
189
- function isContractEmpty(contract: SegmentContract): boolean {
189
+ function isContractEmpty(contract: FragmentContract): boolean {
190
190
  return (
191
191
  (!contract.propsSummary || contract.propsSummary.length === 0) &&
192
192
  (!contract.scenarioTags || contract.scenarioTags.length === 0) &&
@@ -196,7 +196,7 @@ function isContractEmpty(contract: SegmentContract): boolean {
196
196
  }
197
197
 
198
198
  // Check if contract has some but not all fields
199
- function isPartialContract(contract: SegmentContract): boolean {
199
+ function isPartialContract(contract: FragmentContract): boolean {
200
200
  const fieldCount = [
201
201
  contract.propsSummary?.length ?? 0,
202
202
  contract.scenarioTags?.length ?? 0,
@@ -40,7 +40,7 @@ function parseFigmaUrl(figmaUrl: string): ParsedFigmaUrl | null {
40
40
  * Build a Figma embed URL.
41
41
  */
42
42
  function buildEmbedUrl(fileKey: string, nodeId?: string): string {
43
- let embedUrl = `https://embed.figma.com/design/${fileKey}?embed-host=segments`;
43
+ let embedUrl = `https://embed.figma.com/design/${fileKey}?embed-host=fragments`;
44
44
 
45
45
  if (nodeId) {
46
46
  const embedNodeId = nodeId.replace(/:/g, "-");
@@ -205,25 +205,25 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
205
205
  }
206
206
 
207
207
  /**
208
- * Hook to collect all Figma URLs from a segment's variants.
208
+ * Hook to collect all Figma URLs from a fragment's variants.
209
209
  * This enables the FigmaEmbed to preload all variant iframes.
210
210
  */
211
211
  export function useAllFigmaUrls(
212
- segment: { meta: { figma?: string }; variants?: Array<{ figma?: string }> } | undefined
212
+ fragment: { meta: { figma?: string }; variants?: Array<{ figma?: string }> } | undefined
213
213
  ): string[] {
214
214
  return useMemo(() => {
215
- if (!segment) return [];
215
+ if (!fragment) return [];
216
216
 
217
217
  const urls: string[] = [];
218
218
 
219
219
  // Add meta-level Figma URL
220
- if (segment.meta.figma) {
221
- urls.push(segment.meta.figma);
220
+ if (fragment.meta.figma) {
221
+ urls.push(fragment.meta.figma);
222
222
  }
223
223
 
224
224
  // Add variant-level Figma URLs
225
- if (segment.variants) {
226
- for (const variant of segment.variants) {
225
+ if (fragment.variants) {
226
+ for (const variant of fragment.variants) {
227
227
  if (variant.figma) {
228
228
  urls.push(variant.figma);
229
229
  }
@@ -232,5 +232,5 @@ export function useAllFigmaUrls(
232
232
 
233
233
  // Deduplicate
234
234
  return [...new Set(urls)];
235
- }, [segment]);
235
+ }, [fragment]);
236
236
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { useMemo, useState, useCallback, useEffect } from 'react';
8
- import type { SegmentDefinition } from '../../core/index.js';
8
+ import type { FragmentDefinition } from '../../core/index.js';
9
9
  import type { ImpactValue } from 'axe-core';
10
10
  import { Badge, Progress, Stack, Text, Card, EmptyState, Table } from '@fragments/ui';
11
11
  import {
@@ -17,7 +17,7 @@ import {
17
17
  import type { A11ySummary, CachedA11yResult } from '../types/a11y.js';
18
18
 
19
19
  interface HealthDashboardProps {
20
- segments: Array<{ path: string; segment: SegmentDefinition }>;
20
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
21
21
  onNavigate?: (componentName: string) => void;
22
22
  }
23
23
 
@@ -50,9 +50,9 @@ interface ComponentRow {
50
50
  }
51
51
 
52
52
  function calculateCoverage(
53
- segments: Array<{ path: string; segment: SegmentDefinition }>
53
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>
54
54
  ): { metrics: CoverageMetric[]; components: ComponentRow[]; categoryCount: number } {
55
- const total = segments.length;
55
+ const total = fragments.length;
56
56
 
57
57
  if (total === 0) {
58
58
  return { metrics: [], components: [], categoryCount: 0 };
@@ -66,32 +66,32 @@ function calculateCoverage(
66
66
  let withUsage = 0;
67
67
  let figmaLinked = 0;
68
68
 
69
- for (const { segment } of segments) {
70
- const cat = segment.meta.category || 'uncategorized';
69
+ for (const { fragment } of fragments) {
70
+ const cat = fragment.meta.category || 'uncategorized';
71
71
  categories.add(cat);
72
72
 
73
- if (segment.meta.description && segment.meta.description.trim().length > 10) {
73
+ if (fragment.meta.description && fragment.meta.description.trim().length > 10) {
74
74
  documented++;
75
75
  }
76
76
 
77
- const variantCount = segment.variants?.length || 0;
77
+ const variantCount = fragment.variants?.length || 0;
78
78
  if (variantCount > 0) {
79
79
  withVariants++;
80
80
  }
81
81
 
82
- if (segment.usage && (segment.usage.when.length > 0 || segment.usage.whenNot.length > 0)) {
82
+ if (fragment.usage && (fragment.usage.when.length > 0 || fragment.usage.whenNot.length > 0)) {
83
83
  withUsage++;
84
84
  }
85
85
 
86
- if (segment.meta.figma || segment.variants?.some((v) => v.figma)) {
86
+ if (fragment.meta.figma || fragment.variants?.some((v) => v.figma)) {
87
87
  figmaLinked++;
88
88
  }
89
89
 
90
90
  components.push({
91
- name: segment.meta.name,
91
+ name: fragment.meta.name,
92
92
  category: cat,
93
93
  variantCount,
94
- status: segment.meta.status || 'stable',
94
+ status: fragment.meta.status || 'stable',
95
95
  });
96
96
  }
97
97
 
@@ -120,10 +120,10 @@ function impactToBadgeVariant(impact: ImpactValue | undefined): 'error' | 'warni
120
120
  }
121
121
  }
122
122
 
123
- export function HealthDashboard({ segments, onNavigate }: HealthDashboardProps) {
123
+ export function HealthDashboard({ fragments, onNavigate }: HealthDashboardProps) {
124
124
  const { metrics, components, categoryCount } = useMemo(
125
- () => calculateCoverage(segments),
126
- [segments]
125
+ () => calculateCoverage(fragments),
126
+ [fragments]
127
127
  );
128
128
 
129
129
  const [componentA11y, setComponentA11y] = useState<Record<string, ComponentA11yResult>>({});
@@ -212,7 +212,7 @@ export function HealthDashboard({ segments, onNavigate }: HealthDashboardProps)
212
212
  };
213
213
  }, [componentA11y, a11ySummary]);
214
214
 
215
- if (segments.length === 0) {
215
+ if (fragments.length === 0) {
216
216
  return (
217
217
  <EmptyState>
218
218
  <EmptyState.Description>No components loaded</EmptyState.Description>
@@ -234,7 +234,7 @@ export function HealthDashboard({ segments, onNavigate }: HealthDashboardProps)
234
234
  <div>
235
235
  <Text as="h1" size="lg" weight="semibold">Fragments</Text>
236
236
  <Text size="sm" color="tertiary" style={{ marginTop: '2px' }}>
237
- {segments.length} component{segments.length !== 1 ? 's' : ''} · {categoryCount} categor{categoryCount !== 1 ? 'ies' : 'y'}
237
+ {fragments.length} component{fragments.length !== 1 ? 's' : ''} · {categoryCount} categor{categoryCount !== 1 ? 'ies' : 'y'}
238
238
  </Text>
239
239
  </div>
240
240
 
@@ -199,6 +199,49 @@ export function ViewportIcon({ className, style }: IconProps) {
199
199
  );
200
200
  }
201
201
 
202
+ export function ResponsiveIcon({ className, style }: IconProps) {
203
+ return (
204
+ <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.7}>
205
+ <path strokeLinecap="round" strokeLinejoin="round" d="M7 12h10m0 0l-2.5-2.5M17 12l-2.5 2.5M7 12l2.5-2.5M7 12l2.5 2.5" />
206
+ </svg>
207
+ );
208
+ }
209
+
210
+ export function DesktopIcon({ className, style }: IconProps) {
211
+ return (
212
+ <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
213
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 5.25A2.25 2.25 0 016 3h12a2.25 2.25 0 012.25 2.25v8.25A2.25 2.25 0 0118 15.75H6a2.25 2.25 0 01-2.25-2.25V5.25zM9.75 20.25h4.5M12 15.75v4.5" />
214
+ </svg>
215
+ );
216
+ }
217
+
218
+ export function TabletIcon({ className, style }: IconProps) {
219
+ return (
220
+ <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
221
+ <rect x="6.75" y="3" width="10.5" height="18" rx="2.25" />
222
+ <circle cx="12" cy="17.25" r="0.85" fill="currentColor" stroke="none" />
223
+ </svg>
224
+ );
225
+ }
226
+
227
+ export function MobileIcon({ className, style }: IconProps) {
228
+ return (
229
+ <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
230
+ <rect x="8.25" y="2.25" width="7.5" height="19.5" rx="2.25" />
231
+ <circle cx="12" cy="18.75" r="0.8" fill="currentColor" stroke="none" />
232
+ </svg>
233
+ );
234
+ }
235
+
236
+ export function SettingsIcon({ className, style }: IconProps) {
237
+ return (
238
+ <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
239
+ <path strokeLinecap="round" strokeLinejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.094c.55 0 1.02.398 1.11.94l.18 1.09c.06.36.29.668.62.84l.94.5c.318.168.7.18 1.028.03l.998-.455c.5-.23 1.09-.048 1.38.42l.546.882c.29.468.19 1.074-.24 1.424l-.858.698a1.26 1.26 0 00-.456.985v1.046c0 .38.17.738.456.984l.858.699c.43.35.53.956.24 1.424l-.545.882a1.1 1.1 0 01-1.381.42l-.998-.455a1.26 1.26 0 00-1.028.03l-.94.5a1.26 1.26 0 00-.62.84l-.18 1.09a1.125 1.125 0 01-1.11.94h-1.094a1.124 1.124 0 01-1.11-.94l-.18-1.09a1.26 1.26 0 00-.62-.84l-.94-.5a1.26 1.26 0 00-1.028-.03l-.998.455a1.1 1.1 0 01-1.381-.42l-.545-.882a1.125 1.125 0 01.24-1.424l.858-.699a1.26 1.26 0 00.456-.984V9.878c0-.38-.17-.738-.456-.985l-.858-.698a1.125 1.125 0 01-.24-1.424l.545-.882c.29-.468.88-.65 1.381-.42l.998.455c.33.15.71.138 1.028-.03l.94-.5c.33-.172.56-.48.62-.84l.18-1.09z" />
240
+ <circle cx="12" cy="12" r="3" />
241
+ </svg>
242
+ );
243
+ }
244
+
202
245
  // Misc
203
246
  export function ControlsIcon({ className, style }: IconProps) {
204
247
  return (
@@ -235,7 +278,16 @@ export function HeartPulseIcon({ className, style }: IconProps) {
235
278
 
236
279
  export function DashboardIcon({ className, style }: IconProps) {
237
280
  return (
238
- <svg className={className} style={style} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
281
+ <svg
282
+ className={className}
283
+ style={style}
284
+ width="20"
285
+ height="20"
286
+ fill="none"
287
+ viewBox="0 0 24 24"
288
+ stroke="currentColor"
289
+ strokeWidth={1.5}
290
+ >
239
291
  <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
240
292
  </svg>
241
293
  );
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { useState, useCallback, useRef, useEffect } from "react";
13
13
  import { Button, Stack, Text, Badge, EmptyState, CodeBlock, Alert } from "@fragments/ui";
14
- import type { PlayFunction, PlayFunctionContext, SegmentVariant } from "../../core/index.js";
14
+ import type { PlayFunction, PlayFunctionContext, FragmentVariant } from "../../core/index.js";
15
15
  import {
16
16
  PlayIcon,
17
17
  CheckIcon,
@@ -54,7 +54,7 @@ interface DebugState {
54
54
 
55
55
  interface InteractionsPanelProps {
56
56
  /** The current variant being displayed */
57
- variant: SegmentVariant | null;
57
+ variant: FragmentVariant | null;
58
58
  /** Selector for the preview container element */
59
59
  previewSelector?: string;
60
60
  /** Key that changes when the preview updates */
@@ -15,8 +15,8 @@ import { usePreviewBridge, type ParentMessage } from '../hooks/usePreviewBridge.
15
15
  const MAX_RETRIES = 3;
16
16
 
17
17
  export interface IsolatedPreviewFrameProps {
18
- /** Segment path (file path) to render */
19
- segmentPath: string;
18
+ /** Fragment path (file path) to render */
19
+ fragmentPath: string;
20
20
  /** Variant name to render */
21
21
  variantName: string;
22
22
  /** Props to pass to the variant render function */
@@ -50,7 +50,7 @@ export interface IsolatedPreviewFrameProps {
50
50
  * for complete CSS isolation from the viewer shell.
51
51
  */
52
52
  export const IsolatedPreviewFrame = memo(function IsolatedPreviewFrame({
53
- segmentPath,
53
+ fragmentPath,
54
54
  variantName,
55
55
  props,
56
56
  theme,
@@ -109,12 +109,12 @@ export const IsolatedPreviewFrame = memo(function IsolatedPreviewFrame({
109
109
  if (!isReady) return;
110
110
 
111
111
  // Create a render key to detect changes
112
- const renderKey = `${segmentPath}:${variantName}:${JSON.stringify(props)}:${previewKey || ''}`;
112
+ const renderKey = `${fragmentPath}:${variantName}:${JSON.stringify(props)}:${previewKey || ''}`;
113
113
  if (renderKey === lastRenderRef.current) return;
114
114
  lastRenderRef.current = renderKey;
115
115
 
116
- render(segmentPath, variantName, props);
117
- }, [isReady, segmentPath, variantName, props, previewKey, render]);
116
+ render(fragmentPath, variantName, props);
117
+ }, [isReady, fragmentPath, variantName, props, previewKey, render]);
118
118
 
119
119
  // Sync theme when it changes
120
120
  useEffect(() => {
@@ -1,10 +1,10 @@
1
1
  import { useMemo, useEffect, useState } from "react";
2
- import type { SegmentDefinition } from "../../core/index.js";
2
+ import type { FragmentDefinition } from "../../core/index.js";
3
3
  import { VariantRenderer } from "./VariantRenderer.js";
4
4
  import { getBackgroundStyle, type BackgroundOption, type ZoomLevel } from "./PreviewToolbar.js";
5
5
 
6
6
  interface IsolatedRenderProps {
7
- segments: Array<{ path: string; segment: SegmentDefinition }>;
7
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
8
8
  }
9
9
 
10
10
  /**
@@ -12,7 +12,7 @@ interface IsolatedRenderProps {
12
12
  * Renders a single variant with minimal UI for visual testing.
13
13
  * URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&bg=white&theme=light
14
14
  */
15
- export function IsolatedRender({ segments }: IsolatedRenderProps) {
15
+ export function IsolatedRender({ fragments }: IsolatedRenderProps) {
16
16
  const [ready, setReady] = useState(false);
17
17
 
18
18
  // Parse query parameters
@@ -31,21 +31,21 @@ export function IsolatedRender({ segments }: IsolatedRenderProps) {
31
31
  };
32
32
  }, []);
33
33
 
34
- // Find the matching segment and variant
34
+ // Find the matching fragment and variant
35
35
  const match = useMemo(() => {
36
36
  if (!params.component || !params.variant) {
37
37
  return null;
38
38
  }
39
39
 
40
- const segment = segments.find(
41
- (s) => s.segment.meta.name === params.component
40
+ const fragment = fragments.find(
41
+ (s) => s.fragment.meta.name === params.component
42
42
  );
43
43
 
44
- if (!segment) {
44
+ if (!fragment) {
45
45
  return null;
46
46
  }
47
47
 
48
- const variant = segment.segment.variants.find(
48
+ const variant = fragment.fragment.variants.find(
49
49
  (v) => v.name === params.variant
50
50
  );
51
51
 
@@ -53,8 +53,8 @@ export function IsolatedRender({ segments }: IsolatedRenderProps) {
53
53
  return null;
54
54
  }
55
55
 
56
- return { segment: segment.segment, variant };
57
- }, [segments, params]);
56
+ return { fragment: fragment.fragment, variant };
57
+ }, [fragments, params]);
58
58
 
59
59
  // Apply theme
60
60
  useEffect(() => {
@@ -3,13 +3,17 @@ import { AppShell } from '@fragments/ui';
3
3
 
4
4
  interface LayoutProps {
5
5
  leftSidebar: ReactNode;
6
+ header: ReactNode;
6
7
  children: ReactNode;
7
8
  }
8
9
 
9
- export function Layout({ leftSidebar, children }: LayoutProps) {
10
+ export function Layout({ leftSidebar, header, children }: LayoutProps) {
10
11
  return (
11
- <AppShell layout="sidebar-inset">
12
- <AppShell.Sidebar width="256px">
12
+ <AppShell layout="inset">
13
+ <AppShell.Header>
14
+ {header}
15
+ </AppShell.Header>
16
+ <AppShell.Sidebar width="256px" collapsible="icon">
13
17
  {leftSidebar}
14
18
  </AppShell.Sidebar>
15
19
  <AppShell.Main padding="none">