@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Style comparison utilities for comparing Figma design properties
3
+ * with rendered component computed styles.
4
+ */
5
+
6
+ /**
7
+ * Style diff result for a single CSS property
8
+ */
9
+ export interface StyleDiffItem {
10
+ /** CSS property name */
11
+ property: string;
12
+ /** Expected value from Figma */
13
+ figma: string;
14
+ /** Actual value from rendered component */
15
+ rendered: string;
16
+ /** Whether values match (within tolerance) */
17
+ match: boolean;
18
+ }
19
+
20
+ /**
21
+ * Result of comparing styles
22
+ */
23
+ export interface StyleComparisonResult {
24
+ /** Whether all styles match */
25
+ match: boolean;
26
+ /** Individual property comparisons */
27
+ properties: StyleDiffItem[];
28
+ /** CSS properties from Figma design */
29
+ figmaStyles: Record<string, string>;
30
+ /** Computed CSS properties from rendered component */
31
+ renderedStyles: Record<string, string>;
32
+ }
33
+
34
+ /**
35
+ * Compare Figma CSS properties with rendered computed styles.
36
+ */
37
+ export function compareStyles(
38
+ figmaStyles: Record<string, string | undefined>,
39
+ renderedStyles: Record<string, string>
40
+ ): StyleComparisonResult {
41
+ const properties: StyleDiffItem[] = [];
42
+ const cleanFigmaStyles: Record<string, string> = {};
43
+
44
+ // Properties to compare
45
+ const propsToCompare = [
46
+ "backgroundColor",
47
+ "borderColor",
48
+ "borderWidth",
49
+ "borderRadius",
50
+ "fontFamily",
51
+ "fontSize",
52
+ "fontWeight",
53
+ "lineHeight",
54
+ "letterSpacing",
55
+ "textAlign",
56
+ "boxShadow",
57
+ "padding",
58
+ "gap",
59
+ "opacity",
60
+ ];
61
+
62
+ for (const prop of propsToCompare) {
63
+ const figmaValue = figmaStyles[prop];
64
+ const renderedValue = renderedStyles[prop];
65
+
66
+ if (figmaValue !== undefined) {
67
+ cleanFigmaStyles[prop] = figmaValue;
68
+
69
+ const match = compareStyleValue(prop, figmaValue, renderedValue || "");
70
+ properties.push({
71
+ property: prop,
72
+ figma: figmaValue,
73
+ rendered: renderedValue || "(not set)",
74
+ match,
75
+ });
76
+ }
77
+ }
78
+
79
+ const allMatch = properties.every((p) => p.match);
80
+
81
+ return {
82
+ match: allMatch,
83
+ properties,
84
+ figmaStyles: cleanFigmaStyles,
85
+ renderedStyles,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Compare a single style value with tolerance for color and numeric differences.
91
+ */
92
+ export function compareStyleValue(
93
+ prop: string,
94
+ figma: string,
95
+ rendered: string
96
+ ): boolean {
97
+ // Normalize values for comparison
98
+ const normalizedFigma = normalizeStyleValue(prop, figma);
99
+ const normalizedRendered = normalizeStyleValue(prop, rendered);
100
+
101
+ // Direct match
102
+ if (normalizedFigma === normalizedRendered) {
103
+ return true;
104
+ }
105
+
106
+ // Color comparison with tolerance
107
+ if (prop === "backgroundColor" || prop === "borderColor") {
108
+ return compareColors(normalizedFigma, normalizedRendered, 5);
109
+ }
110
+
111
+ // Numeric comparison with tolerance (for pixels)
112
+ if (
113
+ ["borderWidth", "borderRadius", "fontSize", "padding", "gap"].includes(prop)
114
+ ) {
115
+ return compareNumericValues(normalizedFigma, normalizedRendered, 1);
116
+ }
117
+
118
+ return false;
119
+ }
120
+
121
+ /**
122
+ * Normalize a style value for comparison.
123
+ */
124
+ export function normalizeStyleValue(prop: string, value: string): string {
125
+ // Remove extra whitespace
126
+ let normalized = value.trim().replace(/\s+/g, " ");
127
+
128
+ // Normalize "none" shadow to empty
129
+ if (prop === "boxShadow" && normalized === "none") {
130
+ normalized = "";
131
+ }
132
+
133
+ // Normalize rgba(0, 0, 0, 0) to "transparent"
134
+ if (normalized.match(/rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/)) {
135
+ normalized = "transparent";
136
+ }
137
+
138
+ return normalized;
139
+ }
140
+
141
+ /**
142
+ * Compare two color values with tolerance.
143
+ */
144
+ export function compareColors(
145
+ color1: string,
146
+ color2: string,
147
+ tolerance: number
148
+ ): boolean {
149
+ const rgb1 = parseColor(color1);
150
+ const rgb2 = parseColor(color2);
151
+
152
+ if (!rgb1 || !rgb2) {
153
+ return color1 === color2;
154
+ }
155
+
156
+ return (
157
+ Math.abs(rgb1.r - rgb2.r) <= tolerance &&
158
+ Math.abs(rgb1.g - rgb2.g) <= tolerance &&
159
+ Math.abs(rgb1.b - rgb2.b) <= tolerance &&
160
+ Math.abs((rgb1.a ?? 1) - (rgb2.a ?? 1)) <= 0.05
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Parse a color string to RGB values.
166
+ */
167
+ export function parseColor(
168
+ color: string
169
+ ): { r: number; g: number; b: number; a?: number } | null {
170
+ // Handle hex colors
171
+ const hexMatch = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
172
+ if (hexMatch) {
173
+ return {
174
+ r: parseInt(hexMatch[1], 16),
175
+ g: parseInt(hexMatch[2], 16),
176
+ b: parseInt(hexMatch[3], 16),
177
+ };
178
+ }
179
+
180
+ // Handle rgb/rgba
181
+ const rgbaMatch = color.match(
182
+ /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/
183
+ );
184
+ if (rgbaMatch) {
185
+ return {
186
+ r: parseInt(rgbaMatch[1], 10),
187
+ g: parseInt(rgbaMatch[2], 10),
188
+ b: parseInt(rgbaMatch[3], 10),
189
+ a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1,
190
+ };
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Compare numeric values (e.g., "10px" vs "11px") with tolerance.
198
+ */
199
+ export function compareNumericValues(
200
+ value1: string,
201
+ value2: string,
202
+ tolerance: number
203
+ ): boolean {
204
+ const num1 = parseFloat(value1);
205
+ const num2 = parseFloat(value2);
206
+
207
+ if (isNaN(num1) || isNaN(num2)) {
208
+ return value1 === value2;
209
+ }
210
+
211
+ return Math.abs(num1 - num2) <= tolerance;
212
+ }
213
+
214
+ // ----- Enhanced Token-Aware Style Comparison -----
215
+
216
+ import type {
217
+ EnhancedStyleDiffItem,
218
+ TokenFix,
219
+ TokenUsageSummary,
220
+ DesignToken,
221
+ } from "../core/index.js";
222
+
223
+ /**
224
+ * Enhanced style diff result with token information
225
+ */
226
+ export interface EnhancedStyleComparisonResult extends StyleComparisonResult {
227
+ /** Individual property comparisons with token info */
228
+ properties: EnhancedStyleDiffItem[];
229
+ /** Token usage summary */
230
+ tokenSummary?: TokenUsageSummary;
231
+ }
232
+
233
+ /**
234
+ * Token registry interface for style comparison
235
+ * (subset of TokenRegistryManager methods needed here)
236
+ */
237
+ export interface TokenLookup {
238
+ findByValue(value: string, theme?: string): string[];
239
+ getToken(name: string): DesignToken | undefined;
240
+ calculateUsageSummary(
241
+ styleDiffs: Array<{
242
+ property: string;
243
+ figma: string;
244
+ rendered: string;
245
+ match: boolean;
246
+ }>,
247
+ theme?: string
248
+ ): TokenUsageSummary;
249
+ }
250
+
251
+ /**
252
+ * Compare styles with token awareness.
253
+ *
254
+ * This enhanced version:
255
+ * 1. Performs normal style comparison
256
+ * 2. Identifies which values match design tokens
257
+ * 3. Flags hardcoded values that should use tokens
258
+ * 4. Generates fix suggestions
259
+ */
260
+ export function compareStylesWithTokens(
261
+ figmaStyles: Record<string, string | undefined>,
262
+ renderedStyles: Record<string, string>,
263
+ tokenLookup?: TokenLookup,
264
+ theme = "default"
265
+ ): EnhancedStyleComparisonResult {
266
+ const properties: EnhancedStyleDiffItem[] = [];
267
+ const cleanFigmaStyles: Record<string, string> = {};
268
+
269
+ // Properties to compare
270
+ const propsToCompare = [
271
+ "backgroundColor",
272
+ "borderColor",
273
+ "borderWidth",
274
+ "borderRadius",
275
+ "fontFamily",
276
+ "fontSize",
277
+ "fontWeight",
278
+ "lineHeight",
279
+ "letterSpacing",
280
+ "textAlign",
281
+ "boxShadow",
282
+ "padding",
283
+ "gap",
284
+ "opacity",
285
+ "color",
286
+ ];
287
+
288
+ for (const prop of propsToCompare) {
289
+ const figmaValue = figmaStyles[prop];
290
+ const renderedValue = renderedStyles[prop];
291
+
292
+ if (figmaValue !== undefined) {
293
+ cleanFigmaStyles[prop] = figmaValue;
294
+
295
+ const match = compareStyleValue(prop, figmaValue, renderedValue || "");
296
+
297
+ // Build enhanced diff item
298
+ const item: EnhancedStyleDiffItem = {
299
+ property: prop,
300
+ figma: figmaValue,
301
+ rendered: renderedValue || "(not set)",
302
+ match,
303
+ isHardcoded: false,
304
+ };
305
+
306
+ // Add token information if registry is available
307
+ if (tokenLookup) {
308
+ const figmaTokens = tokenLookup.findByValue(figmaValue, theme);
309
+ const renderedTokens = renderedValue
310
+ ? tokenLookup.findByValue(renderedValue, theme)
311
+ : [];
312
+
313
+ if (figmaTokens.length > 0) {
314
+ item.figmaToken = figmaTokens[0];
315
+ }
316
+
317
+ if (renderedTokens.length > 0) {
318
+ item.renderedToken = renderedTokens[0];
319
+ }
320
+
321
+ // Determine if this is a hardcoded value
322
+ // Hardcoded = Figma matches a token, but rendered doesn't use a token
323
+ item.isHardcoded = !!item.figmaToken && !item.renderedToken;
324
+
325
+ // Generate fix suggestion if hardcoded
326
+ if (item.isHardcoded && item.figmaToken) {
327
+ const token = tokenLookup.getToken(item.figmaToken);
328
+ if (token) {
329
+ const cssProperty = toCssProperty(prop);
330
+ item.suggestedFix = {
331
+ tokenName: item.figmaToken,
332
+ tokenValue: token.resolvedValue,
333
+ codeFix: `${cssProperty}: var(${item.figmaToken});`,
334
+ confidence: 0.9,
335
+ reason: `Figma uses token ${item.figmaToken} (${token.resolvedValue}). Replace hardcoded value with token for consistency.`,
336
+ };
337
+ }
338
+ }
339
+ }
340
+
341
+ properties.push(item);
342
+ }
343
+ }
344
+
345
+ const allMatch = properties.every((p) => p.match);
346
+
347
+ // Calculate token summary if registry available
348
+ let tokenSummary: TokenUsageSummary | undefined;
349
+ if (tokenLookup) {
350
+ tokenSummary = tokenLookup.calculateUsageSummary(
351
+ properties.map((p) => ({
352
+ property: p.property,
353
+ figma: p.figma,
354
+ rendered: p.rendered,
355
+ match: p.match,
356
+ })),
357
+ theme
358
+ );
359
+ }
360
+
361
+ return {
362
+ match: allMatch,
363
+ properties,
364
+ figmaStyles: cleanFigmaStyles,
365
+ renderedStyles,
366
+ tokenSummary,
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Convert camelCase to kebab-case CSS property
372
+ */
373
+ function toCssProperty(prop: string): string {
374
+ return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
375
+ }
376
+
377
+ /**
378
+ * Format token summary for display
379
+ */
380
+ export function formatTokenSummary(summary: TokenUsageSummary): string {
381
+ const lines: string[] = [];
382
+
383
+ lines.push(`Token Compliance: ${summary.compliancePercent}%`);
384
+ lines.push(
385
+ `${summary.usingTokens}/${summary.totalProperties} properties using tokens`
386
+ );
387
+
388
+ if (summary.hardcoded > 0) {
389
+ lines.push(`${summary.hardcoded} hardcoded value(s) detected`);
390
+ }
391
+
392
+ if (summary.implicitMatches > 0) {
393
+ lines.push(`${summary.implicitMatches} implicit match(es)`);
394
+ }
395
+
396
+ return lines.join("\n");
397
+ }
398
+
399
+ /**
400
+ * Get status badge for token compliance
401
+ */
402
+ export function getComplianceBadge(
403
+ compliancePercent: number
404
+ ): { label: string; color: string } {
405
+ if (compliancePercent >= 100) {
406
+ return { label: "Excellent", color: "green" };
407
+ } else if (compliancePercent >= 80) {
408
+ return { label: "Good", color: "blue" };
409
+ } else if (compliancePercent >= 50) {
410
+ return { label: "Fair", color: "yellow" };
411
+ } else {
412
+ return { label: "Poor", color: "red" };
413
+ }
414
+ }