@usefragments/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-243QYRUF.js +2145 -0
  3. package/dist/chunk-243QYRUF.js.map +1 -0
  4. package/dist/chunk-3LLRNCPX.js +111 -0
  5. package/dist/chunk-3LLRNCPX.js.map +1 -0
  6. package/dist/chunk-EIYNNS77.js +48 -0
  7. package/dist/chunk-EIYNNS77.js.map +1 -0
  8. package/dist/chunk-JNBFJ34I.js +326 -0
  9. package/dist/chunk-JNBFJ34I.js.map +1 -0
  10. package/dist/chunk-R3X7UBHU.js +228 -0
  11. package/dist/chunk-R3X7UBHU.js.map +1 -0
  12. package/dist/chunk-SH4KPIYH.js +393 -0
  13. package/dist/chunk-SH4KPIYH.js.map +1 -0
  14. package/dist/chunk-V4VQB57N.js +56 -0
  15. package/dist/chunk-V4VQB57N.js.map +1 -0
  16. package/dist/chunk-X34IA4LR.js +319 -0
  17. package/dist/chunk-X34IA4LR.js.map +1 -0
  18. package/dist/chunk-YF65VYRY.js +493 -0
  19. package/dist/chunk-YF65VYRY.js.map +1 -0
  20. package/dist/codes/index.d.ts +40 -0
  21. package/dist/codes/index.js +23 -0
  22. package/dist/codes/index.js.map +1 -0
  23. package/dist/compiled-types/index.d.ts +5 -0
  24. package/dist/compiled-types/index.js +1 -0
  25. package/dist/compiled-types/index.js.map +1 -0
  26. package/dist/generate/index.d.ts +47 -0
  27. package/dist/generate/index.js +11 -0
  28. package/dist/generate/index.js.map +1 -0
  29. package/dist/governance-BOa3KyiJ.d.ts +3279 -0
  30. package/dist/graph/index.d.ts +66 -0
  31. package/dist/graph/index.js +17 -0
  32. package/dist/graph/index.js.map +1 -0
  33. package/dist/index.d.ts +11388 -0
  34. package/dist/index.js +8711 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/package-identity.d.ts +42 -0
  37. package/dist/package-identity.js +23 -0
  38. package/dist/package-identity.js.map +1 -0
  39. package/dist/preview/index.d.ts +484 -0
  40. package/dist/preview/index.js +1129 -0
  41. package/dist/preview/index.js.map +1 -0
  42. package/dist/react-types.d.ts +22 -0
  43. package/dist/react-types.js +1 -0
  44. package/dist/react-types.js.map +1 -0
  45. package/dist/registry.d.ts +2367 -0
  46. package/dist/registry.js +62 -0
  47. package/dist/registry.js.map +1 -0
  48. package/dist/schemas/index.d.ts +2584 -0
  49. package/dist/schemas/index.js +55 -0
  50. package/dist/schemas/index.js.map +1 -0
  51. package/dist/severity.d.ts +18 -0
  52. package/dist/severity.js +27 -0
  53. package/dist/severity.js.map +1 -0
  54. package/dist/test-utils.d.ts +14 -0
  55. package/dist/test-utils.js +63 -0
  56. package/dist/test-utils.js.map +1 -0
  57. package/dist/topology/index.d.ts +48 -0
  58. package/dist/topology/index.js +7 -0
  59. package/dist/topology/index.js.map +1 -0
  60. package/dist/types-xJ2xyp_G.d.ts +119 -0
  61. package/package.json +120 -0
  62. package/src/__tests__/contract-parser.test.ts +614 -0
  63. package/src/__tests__/dtcg-generators.test.ts +439 -0
  64. package/src/__tests__/dtcg-parser.test.ts +884 -0
  65. package/src/__tests__/preview-runtime.test.tsx +111 -0
  66. package/src/__tests__/schema.test.ts +31 -0
  67. package/src/__tests__/severity.test.ts +34 -0
  68. package/src/__tests__/style-comparison.test.ts +662 -0
  69. package/src/__tests__/tokens-compat.test.ts +51 -0
  70. package/src/agent-format.test.ts +105 -0
  71. package/src/agent-format.ts +141 -0
  72. package/src/bundle.ts +149 -0
  73. package/src/codes/__tests__/codes.test.ts +73 -0
  74. package/src/codes/codes.ts +463 -0
  75. package/src/codes/index.ts +7 -0
  76. package/src/codes/source-finding-bridge.ts +113 -0
  77. package/src/compiled-types/index.ts +293 -0
  78. package/src/component-contract.ts +231 -0
  79. package/src/component-discovery.ts +80 -0
  80. package/src/composition.test.ts +262 -0
  81. package/src/composition.ts +318 -0
  82. package/src/config-paths.test.ts +78 -0
  83. package/src/config-paths.ts +47 -0
  84. package/src/config.ts +18 -0
  85. package/src/conform-prove.test.ts +274 -0
  86. package/src/conform-prove.ts +326 -0
  87. package/src/conform.ts +160 -0
  88. package/src/constants.ts +114 -0
  89. package/src/context.ts +2 -0
  90. package/src/contract/fixture.ts +110 -0
  91. package/src/contract/hash.test.ts +86 -0
  92. package/src/contract/hash.ts +195 -0
  93. package/src/contract/index.ts +43 -0
  94. package/src/contract/preimage.test.ts +254 -0
  95. package/src/contract/preimage.ts +365 -0
  96. package/src/contract/stamp.test.ts +100 -0
  97. package/src/contract/stamp.ts +105 -0
  98. package/src/contract-parser.ts +141 -0
  99. package/src/defineFragment.test.ts +257 -0
  100. package/src/defineFragment.ts +293 -0
  101. package/src/dtcg-generators.ts +324 -0
  102. package/src/dtcg-parser.ts +1 -0
  103. package/src/dtcg.ts +237 -0
  104. package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
  105. package/src/facts/builders.ts +835 -0
  106. package/src/facts/compile.ts +422 -0
  107. package/src/facts/fact-index.ts +394 -0
  108. package/src/facts/facts.test.ts +494 -0
  109. package/src/facts/ids.ts +125 -0
  110. package/src/facts/index.ts +119 -0
  111. package/src/facts/types.ts +614 -0
  112. package/src/figma.ts +263 -0
  113. package/src/fragment-types.ts +214 -0
  114. package/src/generate/generate.test.ts +594 -0
  115. package/src/generate/index.ts +438 -0
  116. package/src/governance-integrity.test.ts +186 -0
  117. package/src/governance-integrity.ts +282 -0
  118. package/src/governance.test.ts +300 -0
  119. package/src/governance.ts +605 -0
  120. package/src/governance.type-test.ts +75 -0
  121. package/src/graph/engine.ts +446 -0
  122. package/src/graph/graph.test.ts +746 -0
  123. package/src/graph/index.ts +32 -0
  124. package/src/graph/serialization.ts +55 -0
  125. package/src/graph/types.ts +178 -0
  126. package/src/index.ts +891 -0
  127. package/src/local-canonical.ts +102 -0
  128. package/src/mcp-snapshot.ts +222 -0
  129. package/src/package-identity-match.ts +33 -0
  130. package/src/package-identity.generated.ts +233 -0
  131. package/src/package-identity.test.ts +99 -0
  132. package/src/package-identity.ts +157 -0
  133. package/src/performance-presets.ts +142 -0
  134. package/src/preview/comparison-artifact.test.ts +228 -0
  135. package/src/preview/comparison-artifact.ts +181 -0
  136. package/src/preview/comparison-context.ts +93 -0
  137. package/src/preview/comparison-evidence.ts +112 -0
  138. package/src/preview/comparison-format.ts +21 -0
  139. package/src/preview/comparison-presentation.ts +187 -0
  140. package/src/preview/comparison-references.test.ts +430 -0
  141. package/src/preview/comparison-references.ts +72 -0
  142. package/src/preview/comparison-score.ts +59 -0
  143. package/src/preview/comparison-styles.ts +197 -0
  144. package/src/preview/comparison.test.ts +559 -0
  145. package/src/preview/comparison.ts +488 -0
  146. package/src/preview/index.ts +10 -0
  147. package/src/preview/types.ts +309 -0
  148. package/src/preview/validation.test.ts +290 -0
  149. package/src/preview/validation.ts +356 -0
  150. package/src/preview-runtime.tsx +144 -0
  151. package/src/raw-html-canonical.test.ts +41 -0
  152. package/src/raw-html-canonical.ts +179 -0
  153. package/src/react-types.ts +21 -0
  154. package/src/registry-install-plan.ts +344 -0
  155. package/src/registry.test.ts +245 -0
  156. package/src/registry.ts +369 -0
  157. package/src/rules/a11y-required-accessible-name.ts +93 -0
  158. package/src/rules/color-math.test.ts +72 -0
  159. package/src/rules/color-math.ts +192 -0
  160. package/src/rules/components-forbidden-prop-value.ts +108 -0
  161. package/src/rules/components-prefer-library.ts +943 -0
  162. package/src/rules/components-unknown-prop.ts +94 -0
  163. package/src/rules/composition-pattern-schema.ts +125 -0
  164. package/src/rules/composition-pattern.test.ts +320 -0
  165. package/src/rules/composition-pattern.ts +271 -0
  166. package/src/rules/emit-gate.test.ts +42 -0
  167. package/src/rules/emit-gate.ts +60 -0
  168. package/src/rules/finding.ts +54 -0
  169. package/src/rules/index.ts +235 -0
  170. package/src/rules/jsx-preferred-component.ts +68 -0
  171. package/src/rules/jsx-preferred-import-path.ts +67 -0
  172. package/src/rules/presets.ts +57 -0
  173. package/src/rules/props-invalid-value.ts +115 -0
  174. package/src/rules/rules.test.ts +3636 -0
  175. package/src/rules/spacing-resolution.ts +184 -0
  176. package/src/rules/styles-no-raw-color.ts +329 -0
  177. package/src/rules/styles-no-raw-dimensions.ts +357 -0
  178. package/src/rules/styles-no-raw-spacing.ts +236 -0
  179. package/src/rules/styles-no-raw-typography.ts +237 -0
  180. package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
  181. package/src/rules/tailwind-arbitrary-color.ts +93 -0
  182. package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
  183. package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
  184. package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
  185. package/src/rules/tailwind-forbidden-palette.ts +107 -0
  186. package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
  187. package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
  188. package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
  189. package/src/rules/tailwind-raw-color-via-token.ts +82 -0
  190. package/src/rules/tailwind-rule-utils.ts +106 -0
  191. package/src/rules/tailwind-unknown-class.test.ts +157 -0
  192. package/src/rules/tailwind-unknown-class.ts +121 -0
  193. package/src/rules/taxonomy.test.ts +238 -0
  194. package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
  195. package/src/rules/tiers.ts +111 -0
  196. package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
  197. package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
  198. package/src/rules/tokens-require-dual-fallback.ts +78 -0
  199. package/src/rules/types.ts +27 -0
  200. package/src/rules/utils.test.ts +110 -0
  201. package/src/rules/utils.ts +599 -0
  202. package/src/schema.ts +427 -0
  203. package/src/schemas/index.ts +275 -0
  204. package/src/schemas/schemas.test.ts +124 -0
  205. package/src/severity.ts +54 -0
  206. package/src/storyAdapter.test.ts +571 -0
  207. package/src/storyAdapter.ts +761 -0
  208. package/src/storyFilters.test.ts +350 -0
  209. package/src/storyFilters.ts +253 -0
  210. package/src/storybook-csf.ts +11 -0
  211. package/src/style-comparison.ts +423 -0
  212. package/src/test-utils.ts +68 -0
  213. package/src/token-parser.ts +2 -0
  214. package/src/token-types.ts +287 -0
  215. package/src/tokens/categories.ts +131 -0
  216. package/src/tokens/color.ts +193 -0
  217. package/src/tokens/design-token-parser.test.ts +159 -0
  218. package/src/tokens/design-token-parser.ts +402 -0
  219. package/src/tokens/dtcg.ts +428 -0
  220. package/src/tokens/index.ts +130 -0
  221. package/src/tokens/lookup.ts +19 -0
  222. package/src/tokens/scss.ts +171 -0
  223. package/src/tokens/tailwind-v4.ts +187 -0
  224. package/src/tokens/types.ts +43 -0
  225. package/src/topology/index.ts +9 -0
  226. package/src/topology/resolve-area.test.ts +174 -0
  227. package/src/topology/resolve-area.ts +215 -0
  228. package/src/types.ts +1057 -0
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Canonical Style Comparison Engine
3
+ *
4
+ * Pure comparison logic for design-code drift detection.
5
+ * No React, no filesystem, no browser APIs, no Figma client, no Cloud types.
6
+ *
7
+ * This module is the single source of truth for style comparison.
8
+ * CLI comparison surfaces consume these exports.
9
+ */
10
+
11
+ import type {
12
+ EnhancedStyleDiffItem,
13
+ TokenCategory,
14
+ TokenUsageSummary,
15
+ } from "./token-types.js";
16
+ import { parseColor } from "./tokens/color.js";
17
+ import type { TokenLookup } from "./tokens/lookup.js";
18
+
19
+ export { parseColor } from "./tokens/color.js";
20
+ export type { TokenLookup } from "./tokens/lookup.js";
21
+
22
+ // ─── Canonical Contract Types ────────────────────────────────────────────────
23
+
24
+ /**
25
+ * Source-agnostic normalized token for comparison.
26
+ *
27
+ * This is the canonical shape that all token sources (CSS vars, Tailwind,
28
+ * DTCG, Figma) must produce before entering the comparison engine.
29
+ * A `DesignToken` (CSS-custom-property-specific) can be converted to this
30
+ * by dropping source-specific fields.
31
+ */
32
+ export interface NormalizedToken {
33
+ /** Canonical token name (e.g., "--color-primary", "colors.primary") */
34
+ name: string;
35
+ /** Normalized resolved value suitable for comparison (e.g., "#0051c2", "16px") */
36
+ value: string;
37
+ /** Token category for grouping */
38
+ category: TokenCategory;
39
+ /** Source system identifier */
40
+ source: "css-var" | "tailwind" | "dtcg" | "figma" | (string & {});
41
+ /** Original token name as declared in the source system */
42
+ originalName: string;
43
+ /** Theme or mode (e.g., "default", "dark", "light") */
44
+ theme: string;
45
+ }
46
+
47
+ /**
48
+ * Normalized style map: a record of CSS property names to their string values.
49
+ * Used as the canonical input shape for both design-side and code-side styles.
50
+ */
51
+ export type NormalizedStyleMap = Record<string, string>;
52
+
53
+ /**
54
+ * Options for configuring style comparison behavior.
55
+ */
56
+ export interface StyleComparisonOptions {
57
+ /** Color comparison tolerance in RGB channel delta (default: 5) */
58
+ colorTolerance?: number;
59
+ /** Numeric comparison tolerance in pixels (default: 1) */
60
+ numericTolerance?: number;
61
+ /** Alpha channel comparison tolerance (default: 0.05) */
62
+ alphaTolerance?: number;
63
+ /** CSS properties to compare. If omitted, uses DEFAULT_STYLE_PROPERTIES */
64
+ properties?: string[];
65
+ /** Theme to use for token lookup (default: "default") */
66
+ theme?: string;
67
+ }
68
+
69
+ // ─── Comparison Types ────────────────────────────────────────────────────────
70
+
71
+ /**
72
+ * Style diff result for a single CSS property.
73
+ */
74
+ export interface StyleDiffItem {
75
+ /** CSS property name */
76
+ property: string;
77
+ /** Expected value from design source */
78
+ figma: string;
79
+ /** Actual value from rendered component */
80
+ rendered: string;
81
+ /** Whether values match (within tolerance) */
82
+ match: boolean;
83
+ }
84
+
85
+ /**
86
+ * Result of comparing styles between design and code.
87
+ */
88
+ export interface StyleComparisonResult {
89
+ /** Whether all styles match */
90
+ match: boolean;
91
+ /** Individual property comparisons */
92
+ properties: StyleDiffItem[];
93
+ /** CSS properties from design source */
94
+ figmaStyles: NormalizedStyleMap;
95
+ /** Computed CSS properties from rendered component */
96
+ renderedStyles: NormalizedStyleMap;
97
+ }
98
+
99
+ /**
100
+ * Enhanced style comparison result with token information.
101
+ */
102
+ export interface EnhancedStyleComparisonResult extends StyleComparisonResult {
103
+ /** Individual property comparisons with token info */
104
+ properties: EnhancedStyleDiffItem[];
105
+ /** Token usage summary */
106
+ tokenSummary?: TokenUsageSummary;
107
+ }
108
+
109
+ // ─── Constants ───────────────────────────────────────────────────────────────
110
+
111
+ /** Properties that use color comparison with tolerance */
112
+ const COLOR_PROPERTIES = new Set(["backgroundColor", "borderColor", "color"]);
113
+
114
+ /** Properties that use numeric comparison with tolerance */
115
+ const NUMERIC_PROPERTIES = new Set([
116
+ "borderWidth",
117
+ "borderRadius",
118
+ "fontSize",
119
+ "padding",
120
+ "gap",
121
+ ]);
122
+
123
+ /** Default CSS properties compared by compareStyles() */
124
+ export const DEFAULT_STYLE_PROPERTIES = [
125
+ "backgroundColor",
126
+ "borderColor",
127
+ "borderWidth",
128
+ "borderRadius",
129
+ "fontFamily",
130
+ "fontSize",
131
+ "fontWeight",
132
+ "lineHeight",
133
+ "letterSpacing",
134
+ "textAlign",
135
+ "boxShadow",
136
+ "padding",
137
+ "gap",
138
+ "opacity",
139
+ ] as const;
140
+
141
+ /** Default CSS properties compared by compareStylesWithTokens() (includes "color") */
142
+ export const DEFAULT_ENHANCED_STYLE_PROPERTIES = [
143
+ ...DEFAULT_STYLE_PROPERTIES,
144
+ "color",
145
+ ] as const;
146
+
147
+ // ─── Pure Comparison Functions ───────────────────────────────────────────────
148
+
149
+ /**
150
+ * Normalize a style value for comparison.
151
+ */
152
+ export function normalizeStyleValue(prop: string, value: string): string {
153
+ let normalized = value.trim().replace(/\s+/g, " ");
154
+
155
+ // Normalize "none" shadow to empty
156
+ if (prop === "boxShadow" && normalized === "none") {
157
+ normalized = "";
158
+ }
159
+
160
+ // Normalize rgba(0, 0, 0, 0) to "transparent"
161
+ if (normalized.match(/rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/)) {
162
+ normalized = "transparent";
163
+ }
164
+
165
+ return normalized;
166
+ }
167
+
168
+ /**
169
+ * Compare two color values with tolerance.
170
+ */
171
+ export function compareColors(
172
+ color1: string,
173
+ color2: string,
174
+ tolerance: number,
175
+ alphaTolerance = 0.05
176
+ ): boolean {
177
+ const rgb1 = parseColor(color1);
178
+ const rgb2 = parseColor(color2);
179
+
180
+ if (!rgb1 || !rgb2) {
181
+ return color1 === color2;
182
+ }
183
+
184
+ return (
185
+ Math.abs(rgb1.r - rgb2.r) <= tolerance &&
186
+ Math.abs(rgb1.g - rgb2.g) <= tolerance &&
187
+ Math.abs(rgb1.b - rgb2.b) <= tolerance &&
188
+ Math.abs((rgb1.a ?? 1) - (rgb2.a ?? 1)) <= alphaTolerance
189
+ );
190
+ }
191
+
192
+ /**
193
+ * Compare numeric values (e.g., "10px" vs "11px") with tolerance.
194
+ */
195
+ export function compareNumericValues(
196
+ value1: string,
197
+ value2: string,
198
+ tolerance: number
199
+ ): boolean {
200
+ const num1 = parseFloat(value1);
201
+ const num2 = parseFloat(value2);
202
+
203
+ if (isNaN(num1) || isNaN(num2)) {
204
+ return value1 === value2;
205
+ }
206
+
207
+ return Math.abs(num1 - num2) <= tolerance;
208
+ }
209
+
210
+ /**
211
+ * Compare a single style value with tolerance for color and numeric differences.
212
+ */
213
+ export function compareStyleValue(
214
+ prop: string,
215
+ figma: string,
216
+ rendered: string
217
+ ): boolean {
218
+ const normalizedFigma = normalizeStyleValue(prop, figma);
219
+ const normalizedRendered = normalizeStyleValue(prop, rendered);
220
+
221
+ // Direct match
222
+ if (normalizedFigma === normalizedRendered) {
223
+ return true;
224
+ }
225
+
226
+ // Color comparison with tolerance
227
+ if (COLOR_PROPERTIES.has(prop)) {
228
+ return compareColors(normalizedFigma, normalizedRendered, 5);
229
+ }
230
+
231
+ // Numeric comparison with tolerance (for pixels)
232
+ if (NUMERIC_PROPERTIES.has(prop)) {
233
+ return compareNumericValues(normalizedFigma, normalizedRendered, 1);
234
+ }
235
+
236
+ return false;
237
+ }
238
+
239
+ /**
240
+ * Compare design CSS properties with rendered computed styles.
241
+ */
242
+ export function compareStyles(
243
+ figmaStyles: Record<string, string | undefined>,
244
+ renderedStyles: NormalizedStyleMap
245
+ ): StyleComparisonResult {
246
+ const properties: StyleDiffItem[] = [];
247
+ const cleanFigmaStyles: NormalizedStyleMap = {};
248
+
249
+ const propsToCompare = DEFAULT_STYLE_PROPERTIES;
250
+
251
+ for (const prop of propsToCompare) {
252
+ const figmaValue = figmaStyles[prop];
253
+ const renderedValue = renderedStyles[prop];
254
+
255
+ if (figmaValue !== undefined) {
256
+ cleanFigmaStyles[prop] = figmaValue;
257
+
258
+ const match = compareStyleValue(prop, figmaValue, renderedValue || "");
259
+ properties.push({
260
+ property: prop,
261
+ figma: figmaValue,
262
+ rendered: renderedValue || "(not set)",
263
+ match,
264
+ });
265
+ }
266
+ }
267
+
268
+ const allMatch = properties.every((p) => p.match);
269
+
270
+ return {
271
+ match: allMatch,
272
+ properties,
273
+ figmaStyles: cleanFigmaStyles,
274
+ renderedStyles,
275
+ };
276
+ }
277
+
278
+ // ─── Token-Aware Comparison ──────────────────────────────────────────────────
279
+
280
+ /**
281
+ * Compare styles with token awareness.
282
+ *
283
+ * This enhanced version:
284
+ * 1. Performs normal style comparison
285
+ * 2. Identifies which values match design tokens
286
+ * 3. Flags hardcoded values that should use tokens
287
+ * 4. Generates fix suggestions
288
+ */
289
+ export function compareStylesWithTokens(
290
+ figmaStyles: Record<string, string | undefined>,
291
+ renderedStyles: NormalizedStyleMap,
292
+ tokenLookup?: TokenLookup,
293
+ theme = "default"
294
+ ): EnhancedStyleComparisonResult {
295
+ const properties: EnhancedStyleDiffItem[] = [];
296
+ const cleanFigmaStyles: NormalizedStyleMap = {};
297
+
298
+ const propsToCompare = DEFAULT_ENHANCED_STYLE_PROPERTIES;
299
+
300
+ for (const prop of propsToCompare) {
301
+ const figmaValue = figmaStyles[prop];
302
+ const renderedValue = renderedStyles[prop];
303
+
304
+ if (figmaValue !== undefined) {
305
+ cleanFigmaStyles[prop] = figmaValue;
306
+
307
+ const match = compareStyleValue(prop, figmaValue, renderedValue || "");
308
+
309
+ const item: EnhancedStyleDiffItem = {
310
+ property: prop,
311
+ figma: figmaValue,
312
+ rendered: renderedValue || "(not set)",
313
+ match,
314
+ isHardcoded: false,
315
+ };
316
+
317
+ if (tokenLookup) {
318
+ const figmaTokens = tokenLookup.findByValue(figmaValue, theme);
319
+ const renderedTokens = renderedValue
320
+ ? tokenLookup.findByValue(renderedValue, theme)
321
+ : [];
322
+
323
+ if (figmaTokens.length > 0) {
324
+ item.figmaToken = figmaTokens[0];
325
+ }
326
+
327
+ if (renderedTokens.length > 0) {
328
+ item.renderedToken = renderedTokens[0];
329
+ }
330
+
331
+ // Hardcoded = Figma matches a token, but rendered doesn't use a token
332
+ item.isHardcoded = !!item.figmaToken && !item.renderedToken;
333
+
334
+ if (item.isHardcoded && item.figmaToken) {
335
+ const token = tokenLookup.getToken(item.figmaToken);
336
+ if (token) {
337
+ const cssProperty = toCssProperty(prop);
338
+ item.suggestedFix = {
339
+ tokenName: item.figmaToken,
340
+ tokenValue: token.resolvedValue,
341
+ codeFix: `${cssProperty}: var(${item.figmaToken});`,
342
+ confidence: 0.9,
343
+ reason: `Figma uses token ${item.figmaToken} (${token.resolvedValue}). Replace hardcoded value with token for consistency.`,
344
+ };
345
+ }
346
+ }
347
+ }
348
+
349
+ properties.push(item);
350
+ }
351
+ }
352
+
353
+ const allMatch = properties.every((p) => p.match);
354
+
355
+ let tokenSummary: TokenUsageSummary | undefined;
356
+ if (tokenLookup) {
357
+ tokenSummary = tokenLookup.calculateUsageSummary(
358
+ properties.map((p) => ({
359
+ property: p.property,
360
+ figma: p.figma,
361
+ rendered: p.rendered,
362
+ match: p.match,
363
+ })),
364
+ theme
365
+ );
366
+ }
367
+
368
+ return {
369
+ match: allMatch,
370
+ properties,
371
+ figmaStyles: cleanFigmaStyles,
372
+ renderedStyles,
373
+ tokenSummary,
374
+ };
375
+ }
376
+
377
+ // ─── Formatting Helpers ──────────────────────────────────────────────────────
378
+
379
+ /**
380
+ * Convert camelCase CSS property name to kebab-case.
381
+ */
382
+ function toCssProperty(prop: string): string {
383
+ return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
384
+ }
385
+
386
+ /**
387
+ * Format token usage summary for display.
388
+ */
389
+ export function formatTokenSummary(summary: TokenUsageSummary): string {
390
+ const lines: string[] = [];
391
+
392
+ lines.push(`Token Compliance: ${summary.compliancePercent}%`);
393
+ lines.push(
394
+ `${summary.usingTokens}/${summary.totalProperties} properties using tokens`
395
+ );
396
+
397
+ if (summary.hardcoded > 0) {
398
+ lines.push(`${summary.hardcoded} hardcoded value(s) detected`);
399
+ }
400
+
401
+ if (summary.implicitMatches > 0) {
402
+ lines.push(`${summary.implicitMatches} implicit match(es)`);
403
+ }
404
+
405
+ return lines.join("\n");
406
+ }
407
+
408
+ /**
409
+ * Get status badge for token compliance level.
410
+ */
411
+ export function getComplianceBadge(
412
+ compliancePercent: number
413
+ ): { label: string; color: string } {
414
+ if (compliancePercent >= 100) {
415
+ return { label: "Excellent", color: "green" };
416
+ } else if (compliancePercent >= 80) {
417
+ return { label: "Good", color: "blue" };
418
+ } else if (compliancePercent >= 50) {
419
+ return { label: "Fair", color: "yellow" };
420
+ } else {
421
+ return { label: "Poor", color: "red" };
422
+ }
423
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared factory builders for compiled-fragment tests.
3
+ */
4
+
5
+ import type { CompiledFragment, CompiledBlock } from "./compiled-types/index.js";
6
+
7
+ export function makeCompiledFragment(
8
+ overrides: Partial<CompiledFragment> = {},
9
+ ): CompiledFragment {
10
+ return {
11
+ filePath: "src/components/Button/Button.fragment.tsx",
12
+ meta: {
13
+ name: "Button",
14
+ description: "A clickable button element",
15
+ category: "actions",
16
+ status: "stable",
17
+ tags: ["interactive"],
18
+ ...overrides.meta,
19
+ },
20
+ usage: {
21
+ when: ["Triggering an action", "Submitting a form"],
22
+ whenNot: ["Navigating to another page"],
23
+ ...overrides.usage,
24
+ },
25
+ props: overrides.props ?? {
26
+ variant: {
27
+ type: "enum",
28
+ values: ["primary", "secondary", "ghost"],
29
+ default: "primary",
30
+ description: "Visual style variant",
31
+ },
32
+ size: {
33
+ type: "enum",
34
+ values: ["sm", "md", "lg"],
35
+ default: "md",
36
+ description: "Button size",
37
+ },
38
+ disabled: {
39
+ type: "boolean",
40
+ default: false,
41
+ description: "Whether the button is disabled",
42
+ },
43
+ },
44
+ variants: overrides.variants ?? [
45
+ { name: "Primary", description: "Default primary button", code: "<Button>Click</Button>" },
46
+ { name: "Secondary", description: "Secondary variant", code: '<Button variant="secondary">Click</Button>' },
47
+ ],
48
+ relations: overrides.relations,
49
+ contract: overrides.contract,
50
+ ai: overrides.ai,
51
+ _generated: overrides._generated,
52
+ };
53
+ }
54
+
55
+ export function makeCompiledBlock(
56
+ overrides: Partial<CompiledBlock> = {},
57
+ ): CompiledBlock {
58
+ return {
59
+ filePath: "src/blocks/LoginForm.block.ts",
60
+ name: "Login Form",
61
+ description: "A standard login form with email and password fields",
62
+ category: "authentication",
63
+ components: ["Input", "Button"],
64
+ code: '<form>\n <Input label="Email" />\n <Input label="Password" type="password" />\n <Button>Sign In</Button>\n</form>',
65
+ tags: ["auth", "form"],
66
+ ...overrides,
67
+ };
68
+ }
@@ -0,0 +1,2 @@
1
+ export { parseScssTokens as parseTokenFile } from "./tokens/index.js";
2
+ export type { ParsedToken, TokenParseOutput } from "./tokens/index.js";