@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,309 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+
3
+ export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
4
+
5
+ export type Previewability =
6
+ | "ready"
7
+ | "building"
8
+ | "needs-preview-provider"
9
+ | "missing-dependency"
10
+ | "missing-asset"
11
+ | "server-only"
12
+ | "unsupported-framework"
13
+ | "runtime-error"
14
+ | "build-error"
15
+ | "docs-only";
16
+
17
+ export type PreviewDefinitionSource = "typescript" | "fragment" | "manual" | "synthesized";
18
+
19
+ /**
20
+ * Seed provenance of a Specimen state (Slice 4). Distinct from
21
+ * `PreviewDefinitionSource` (how the variant def was authored): this records
22
+ * which evidence stream the state came from, in confidence order — a real
23
+ * Storybook story (`csf`), an observed call-site (`usage`), a fragment variant
24
+ * (`fragment`), or a synthesized enum-gap filler (`synthesized`).
25
+ */
26
+ export type SpecimenStateSource = "csf" | "usage" | "fragment" | "synthesized";
27
+
28
+ export type PreviewComponentTier = "primitive" | "provider-backed" | "app-context" | "server-bound";
29
+
30
+ export interface PreviewArtifactIdentity {
31
+ artifactId: string;
32
+ artifactVersion: "preview-artifact/v1";
33
+ orgId: string;
34
+ projectId: string;
35
+ bindingId: string;
36
+ repoFullName: string;
37
+ commitSha: string;
38
+ sourceRef: string;
39
+ packageName?: string;
40
+ packagePath?: string;
41
+ lockfileHash?: string;
42
+ configHash?: string;
43
+ createdAt: string;
44
+ }
45
+
46
+ export interface PreviewArtifactBuilder {
47
+ name: string;
48
+ version: string;
49
+ configFile?: string;
50
+ typecheck: "typescript" | "none";
51
+ sources: PreviewDefinitionSource[];
52
+ adapters: string[];
53
+ }
54
+
55
+ export interface PreviewArtifactManifest {
56
+ artifactVersion: "preview-artifact/v1";
57
+ identity: PreviewArtifactIdentity;
58
+ builder: PreviewArtifactBuilder;
59
+ runtime: {
60
+ framework: "react";
61
+ reactVersion?: string;
62
+ moduleFormat: "esm";
63
+ entry: string;
64
+ css: string[];
65
+ assetsBaseUrl: string;
66
+ };
67
+ capabilities: PreviewArtifactCapabilities;
68
+ components: Record<string, PreviewComponentDefinition>;
69
+ diagnostics: PreviewDiagnostic[];
70
+ /**
71
+ * The component-contract FCID (`contractHash`) this specimen was derived
72
+ * against (Slice 4). Lets ingest pin each state row to its `contractVersions`
73
+ * row so a served state names the same contract identity CI enforces.
74
+ */
75
+ derivedFromContractHash?: string;
76
+ }
77
+
78
+ export interface PreviewArtifactCapabilities {
79
+ supportsThemes: boolean;
80
+ supportsViewports: boolean;
81
+ supportsActions: boolean;
82
+ supportsSlots: boolean;
83
+ supportsA11yScan: boolean;
84
+ supportsPlayFunctions: boolean;
85
+ supportsSceneNodes: boolean;
86
+ }
87
+
88
+ export interface PreviewDesignReference {
89
+ provider: "figma";
90
+ label?: string;
91
+ nodeUrl?: string;
92
+ fileKey?: string;
93
+ nodeId?: string;
94
+ imageUrl?: string;
95
+ size?: {
96
+ width: number;
97
+ height: number;
98
+ };
99
+ /**
100
+ * Optional comparison evidence produced by a preview capture pipeline.
101
+ *
102
+ * The preview package stores and scores this evidence, but does not own
103
+ * browser rendering, Figma API calls, or pixel-diff execution. Node
104
+ * consumers such as the CLI can populate these fields from Playwright,
105
+ * Figma, and pixelmatch; UI consumers can render the same report shape.
106
+ */
107
+ comparison?: PreviewDesignComparisonReference;
108
+ }
109
+
110
+ export interface PreviewDesignComparisonReference {
111
+ /**
112
+ * Preview state that produced this stored evidence. Consumers can still show
113
+ * the linked design for other states, but should only score stored image and
114
+ * style evidence when the active preview state matches this context.
115
+ */
116
+ state?: PreviewDesignComparisonState;
117
+ previewImageUrl?: string;
118
+ previewSize?: {
119
+ width: number;
120
+ height: number;
121
+ };
122
+ designStyles?: Record<string, string>;
123
+ previewStyles?: Record<string, string>;
124
+ diffImageUrl?: string;
125
+ diffPercentage?: number;
126
+ diffPixelCount?: number;
127
+ totalPixels?: number;
128
+ changedRegions?: Array<{
129
+ x: number;
130
+ y: number;
131
+ width: number;
132
+ height: number;
133
+ }>;
134
+ capturedAt?: string;
135
+ }
136
+
137
+ export interface PreviewDesignComparisonState {
138
+ variantId?: string;
139
+ theme?: string;
140
+ viewport?: PreviewViewport;
141
+ }
142
+
143
+ export interface PreviewComponentDefinition {
144
+ componentKey: string;
145
+ publicRef: string;
146
+ displayName: string;
147
+ sourcePath?: string;
148
+ exportName: string;
149
+ importPath: string;
150
+ tier: PreviewComponentTier;
151
+ previewability: Previewability;
152
+ definitionSource: PreviewDefinitionSource;
153
+ diagnostics: PreviewDiagnostic[];
154
+ props: Record<string, PreviewPropDefinition>;
155
+ variants: PreviewVariantDefinition[];
156
+ defaultVariantId?: string;
157
+ requiredProviders?: PreviewProviderRequirement[];
158
+ canvas?: PreviewCanvasMetadata;
159
+ design?: PreviewDesignReference;
160
+ }
161
+
162
+ export interface PreviewProviderRequirement {
163
+ id: string;
164
+ label: string;
165
+ optional?: boolean;
166
+ }
167
+
168
+ export interface PreviewCanvasMetadata {
169
+ defaultWidth?: number;
170
+ defaultHeight?: number;
171
+ supportsChildren?: boolean;
172
+ }
173
+
174
+ export interface PreviewPropDefinition {
175
+ name: string;
176
+ type: string;
177
+ required: boolean;
178
+ defaultValue?: JsonValue;
179
+ source: PreviewDefinitionSource;
180
+ control:
181
+ | { type: "text" }
182
+ | { type: "number"; min?: number; max?: number; step?: number }
183
+ | { type: "boolean" }
184
+ | { type: "select"; options: Array<string | number | boolean> }
185
+ | { type: "color" }
186
+ | { type: "slot"; presets: PreviewSlotPreset[] }
187
+ | { type: "action" }
188
+ | { type: "unsupported"; reason: string };
189
+ description?: string;
190
+ }
191
+
192
+ export interface PreviewVariantDefinition {
193
+ id: string;
194
+ name: string;
195
+ description?: string;
196
+ source: PreviewDefinitionSource;
197
+ props: Record<string, JsonValue>;
198
+ slots?: Record<string, PreviewSlotValue>;
199
+ tags?: string[];
200
+ /** Seed provenance of this state (Slice 4); pinned alongside the FCID. */
201
+ stateSource?: SpecimenStateSource;
202
+ /** Confidence of the seed, 0..1 (csf high → synthesized low). */
203
+ confidence?: number;
204
+ design?: PreviewDesignReference;
205
+ }
206
+
207
+ export type PreviewSlotValue =
208
+ | { kind: "text"; value: string }
209
+ | { kind: "html"; value: string }
210
+ | { kind: "component"; node: ComponentInstanceNode };
211
+
212
+ export interface PreviewSlotPreset {
213
+ id: string;
214
+ label: string;
215
+ value: PreviewSlotValue;
216
+ }
217
+
218
+ export type ComponentCanvasNode = ComponentInstanceNode | FrameNode | TextNode;
219
+
220
+ export interface ComponentInstanceNode {
221
+ kind: "component-instance";
222
+ nodeId: string;
223
+ componentKey: string;
224
+ variantId?: string;
225
+ props: Record<string, JsonValue>;
226
+ slots: Record<string, ComponentCanvasNode[]>;
227
+ layout: LayoutConstraints;
228
+ styleOverrides?: ComponentStyleOverrides;
229
+ }
230
+
231
+ export interface FrameNode {
232
+ kind: "frame";
233
+ nodeId: string;
234
+ children: string[];
235
+ layout: LayoutConstraints;
236
+ style?: {
237
+ background?: string;
238
+ padding?: number | string;
239
+ gap?: number | string;
240
+ };
241
+ }
242
+
243
+ export interface TextNode {
244
+ kind: "text";
245
+ nodeId: string;
246
+ text: string;
247
+ layout: LayoutConstraints;
248
+ }
249
+
250
+ export interface LayoutConstraints {
251
+ position?: "auto" | "absolute";
252
+ width?: "hug" | "fill" | number;
253
+ height?: "hug" | "fill" | number;
254
+ minWidth?: number;
255
+ minHeight?: number;
256
+ x?: number;
257
+ y?: number;
258
+ }
259
+
260
+ export interface ComponentStyleOverrides {
261
+ className?: string;
262
+ style?: Record<string, JsonValue>;
263
+ }
264
+
265
+ export interface PreviewViewport {
266
+ width: number;
267
+ height: number;
268
+ background?: string;
269
+ }
270
+
271
+ export interface RenderPreviewArgs {
272
+ action: (name: string, payload?: JsonValue[]) => void;
273
+ component: PreviewComponentDefinition;
274
+ componentKey: string;
275
+ manifest: PreviewArtifactManifest;
276
+ props: Record<string, JsonValue>;
277
+ root: HTMLElement;
278
+ slots: Record<string, PreviewSlotValue>;
279
+ theme?: string;
280
+ variantId?: string;
281
+ viewport?: PreviewViewport;
282
+ }
283
+
284
+ export type PreviewCleanup = void | (() => void) | { cleanup: () => void };
285
+
286
+ export type RenderPreview = (args: RenderPreviewArgs) => PreviewCleanup | Promise<PreviewCleanup>;
287
+
288
+ export interface PreviewDiagnostic {
289
+ code:
290
+ | "missing-dependency"
291
+ | "missing-peer-dependency"
292
+ | "unresolved-import"
293
+ | "unsupported-server-component"
294
+ | "node-only-import"
295
+ | "missing-env"
296
+ | "missing-provider"
297
+ | "missing-asset"
298
+ | "css-build-failed"
299
+ | "runtime-error"
300
+ | "render-timeout"
301
+ | "unsupported-prop-control";
302
+ severity: "info" | "warning" | "error";
303
+ message: string;
304
+ componentKey?: string;
305
+ sourcePath?: string;
306
+ importPath?: string;
307
+ suggestedFix?: string;
308
+ docsHref?: string;
309
+ }
@@ -0,0 +1,290 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { definePreviewArtifactManifest, isPreviewArtifactManifest } from "./index";
3
+ import type { PreviewArtifactManifest } from "./index";
4
+
5
+ const manifest: PreviewArtifactManifest = {
6
+ artifactVersion: "preview-artifact/v1",
7
+ identity: {
8
+ artifactId: "example-preview-artifact-v1",
9
+ artifactVersion: "preview-artifact/v1",
10
+ orgId: "org",
11
+ projectId: "project",
12
+ bindingId: "binding",
13
+ repoFullName: "acme/ui",
14
+ commitSha: "abc123",
15
+ sourceRef: "main",
16
+ packageName: "@acme/ui",
17
+ createdAt: "2026-05-20T00:00:00.000Z",
18
+ },
19
+ builder: {
20
+ name: "@fragments-sdk/preview",
21
+ version: "0.1.0",
22
+ typecheck: "typescript",
23
+ sources: ["typescript", "manual"],
24
+ adapters: ["react"],
25
+ },
26
+ runtime: {
27
+ framework: "react",
28
+ reactVersion: "19.x",
29
+ moduleFormat: "esm",
30
+ entry: "index.js",
31
+ css: ["index.css"],
32
+ assetsBaseUrl: "/preview/",
33
+ },
34
+ capabilities: {
35
+ supportsThemes: true,
36
+ supportsViewports: true,
37
+ supportsActions: true,
38
+ supportsSlots: true,
39
+ supportsA11yScan: false,
40
+ supportsPlayFunctions: false,
41
+ supportsSceneNodes: false,
42
+ },
43
+ components: {
44
+ Button: {
45
+ componentKey: "Button",
46
+ publicRef: "Button",
47
+ displayName: "Button",
48
+ sourcePath: "src/button.tsx",
49
+ exportName: "Button",
50
+ importPath: "@acme/ui",
51
+ tier: "primitive",
52
+ previewability: "ready",
53
+ definitionSource: "typescript",
54
+ diagnostics: [],
55
+ props: {},
56
+ variants: [
57
+ {
58
+ id: "default",
59
+ name: "Default",
60
+ source: "manual",
61
+ props: {},
62
+ design: {
63
+ provider: "figma",
64
+ label: "Button / Default",
65
+ nodeUrl: "https://www.figma.com/design/file/System?node-id=1%3A2",
66
+ imageUrl: "/preview/button-default-figma.png",
67
+ size: { width: 120, height: 40 },
68
+ comparison: {
69
+ previewImageUrl: "/preview/button-default-capture.png",
70
+ previewSize: { width: 120, height: 40 },
71
+ designStyles: {
72
+ backgroundColor: "#ffffff",
73
+ borderRadius: "8px",
74
+ },
75
+ previewStyles: {
76
+ backgroundColor: "rgb(255, 255, 255)",
77
+ borderRadius: "8px",
78
+ },
79
+ diffImageUrl: "/preview/button-default-diff.png",
80
+ diffPercentage: 0.2,
81
+ diffPixelCount: 10,
82
+ totalPixels: 4800,
83
+ changedRegions: [{ x: 4, y: 4, width: 12, height: 8 }],
84
+ capturedAt: "2026-05-24T00:00:00.000Z",
85
+ },
86
+ },
87
+ },
88
+ ],
89
+ defaultVariantId: "default",
90
+ },
91
+ },
92
+ diagnostics: [],
93
+ };
94
+
95
+ describe("@usefragments/core/preview artifact validation", () => {
96
+ it("accepts a typed preview artifact manifest", () => {
97
+ expect(isPreviewArtifactManifest(manifest)).toBe(true);
98
+ expect(definePreviewArtifactManifest(manifest)).toBe(manifest);
99
+ });
100
+
101
+ it("rejects manifests without builder metadata", () => {
102
+ const invalid = { ...manifest, builder: undefined };
103
+
104
+ expect(isPreviewArtifactManifest(invalid)).toBe(false);
105
+ expect(() =>
106
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
107
+ ).toThrow(/builder must be an object/);
108
+ });
109
+
110
+ it("rejects Storybook as a Fragments-native source type", () => {
111
+ const invalid = {
112
+ ...manifest,
113
+ builder: {
114
+ ...manifest.builder,
115
+ sources: ["storybook"],
116
+ },
117
+ components: {
118
+ Button: {
119
+ ...manifest.components.Button,
120
+ definitionSource: "storybook",
121
+ },
122
+ },
123
+ };
124
+
125
+ expect(isPreviewArtifactManifest(invalid)).toBe(false);
126
+ expect(() =>
127
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
128
+ ).toThrow(/unsupported source "storybook"/);
129
+ });
130
+
131
+ it("rejects malformed design comparison evidence", () => {
132
+ const invalid = {
133
+ ...manifest,
134
+ components: {
135
+ Button: {
136
+ ...manifest.components.Button,
137
+ design: {
138
+ provider: "figma",
139
+ size: { width: 0, height: 40 },
140
+ comparison: {
141
+ state: {
142
+ variantId: 123,
143
+ theme: false,
144
+ viewport: { width: 120, height: 0 },
145
+ },
146
+ previewImageUrl: 123,
147
+ previewSize: { width: 120, height: -1 },
148
+ designStyles: { backgroundColor: 123 },
149
+ previewStyles: [],
150
+ diffPercentage: 120,
151
+ changedRegions: [{ x: -1, y: 0, width: 12, height: 8 }],
152
+ },
153
+ },
154
+ },
155
+ },
156
+ };
157
+
158
+ expect(isPreviewArtifactManifest(invalid)).toBe(false);
159
+ expect(() =>
160
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
161
+ ).toThrow(/components.Button.design.size.width must be a positive number/);
162
+ expect(() =>
163
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
164
+ ).toThrow(/components.Button.design.comparison.previewImageUrl must be a string/);
165
+ expect(() =>
166
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
167
+ ).toThrow(/components.Button.design.comparison.state.variantId must be a string/);
168
+ expect(() =>
169
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
170
+ ).toThrow(/components.Button.design.comparison.state.theme must be a string/);
171
+ expect(() =>
172
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
173
+ ).toThrow(
174
+ /components.Button.design.comparison.state.viewport.height must be a positive number/
175
+ );
176
+ expect(() =>
177
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
178
+ ).toThrow(/components.Button.design.comparison.designStyles.backgroundColor must be a string/);
179
+ expect(() =>
180
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
181
+ ).toThrow(/components.Button.design.comparison.previewStyles must be an object/);
182
+ expect(() =>
183
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
184
+ ).toThrow(/components.Button.design.comparison.diffPercentage must be between 0 and 100/);
185
+ expect(() =>
186
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
187
+ ).toThrow(
188
+ /components.Button.design.comparison.changedRegions.0.x must be a non-negative number/
189
+ );
190
+ });
191
+
192
+ it("rejects changed regions outside the comparison source size", () => {
193
+ const invalid = {
194
+ ...manifest,
195
+ components: {
196
+ Button: {
197
+ ...manifest.components.Button,
198
+ design: {
199
+ provider: "figma",
200
+ size: { width: 120, height: 40 },
201
+ comparison: {
202
+ previewSize: { width: 100, height: 32 },
203
+ diffPercentage: 0.2,
204
+ changedRegions: [{ x: 90, y: 24, width: 20, height: 12 }],
205
+ },
206
+ },
207
+ },
208
+ },
209
+ };
210
+
211
+ expect(isPreviewArtifactManifest(invalid)).toBe(false);
212
+ expect(() =>
213
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
214
+ ).toThrow(
215
+ /components.Button.design.comparison.changedRegions.0 must fit within comparison source width 100/
216
+ );
217
+ expect(() =>
218
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
219
+ ).toThrow(
220
+ /components.Button.design.comparison.changedRegions.0 must fit within comparison source height 32/
221
+ );
222
+ });
223
+
224
+ it("rejects impossible comparison pixel counts", () => {
225
+ const invalid = {
226
+ ...manifest,
227
+ components: {
228
+ Button: {
229
+ ...manifest.components.Button,
230
+ design: {
231
+ provider: "figma",
232
+ size: { width: 120, height: 40 },
233
+ comparison: {
234
+ diffPercentage: 0.2,
235
+ diffPixelCount: 5000,
236
+ totalPixels: 4800,
237
+ },
238
+ },
239
+ },
240
+ },
241
+ };
242
+
243
+ const fractional = {
244
+ ...invalid,
245
+ components: {
246
+ Button: {
247
+ ...invalid.components.Button,
248
+ design: {
249
+ ...invalid.components.Button.design,
250
+ comparison: {
251
+ diffPercentage: 0.2,
252
+ diffPixelCount: 10.5,
253
+ totalPixels: 4800,
254
+ },
255
+ },
256
+ },
257
+ },
258
+ };
259
+
260
+ const emptyTotal = {
261
+ ...invalid,
262
+ components: {
263
+ Button: {
264
+ ...invalid.components.Button,
265
+ design: {
266
+ ...invalid.components.Button.design,
267
+ comparison: {
268
+ diffPercentage: 0.2,
269
+ diffPixelCount: 0,
270
+ totalPixels: 0,
271
+ },
272
+ },
273
+ },
274
+ },
275
+ };
276
+
277
+ expect(isPreviewArtifactManifest(invalid)).toBe(false);
278
+ expect(() =>
279
+ definePreviewArtifactManifest(invalid as unknown as PreviewArtifactManifest)
280
+ ).toThrow(
281
+ /components.Button.design.comparison.diffPixelCount must be less than or equal to components.Button.design.comparison.totalPixels/
282
+ );
283
+ expect(() =>
284
+ definePreviewArtifactManifest(fractional as unknown as PreviewArtifactManifest)
285
+ ).toThrow(/components.Button.design.comparison.diffPixelCount must be a non-negative integer/);
286
+ expect(() =>
287
+ definePreviewArtifactManifest(emptyTotal as unknown as PreviewArtifactManifest)
288
+ ).toThrow(/components.Button.design.comparison.totalPixels must be a positive integer/);
289
+ });
290
+ });