@usefragments/core 1.10.2 → 2.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 (130) hide show
  1. package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
  2. package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
  3. package/dist/chunk-7ULGH74M.js +66 -0
  4. package/dist/chunk-7ULGH74M.js.map +1 -0
  5. package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
  6. package/dist/chunk-BMPYIUZE.js.map +1 -0
  7. package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
  8. package/dist/chunk-ML5S6QNU.js.map +1 -0
  9. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  10. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  11. package/dist/chunk-PWIJMOI4.js +202 -0
  12. package/dist/chunk-PWIJMOI4.js.map +1 -0
  13. package/dist/chunk-RYFULE43.js +578 -0
  14. package/dist/chunk-RYFULE43.js.map +1 -0
  15. package/dist/codes/index.d.ts +2 -2
  16. package/dist/codes/index.js +3 -2
  17. package/dist/compiled-types/index.d.ts +327 -2
  18. package/dist/compiled-types/index.js +1 -1
  19. package/dist/generate/index.d.ts +3 -2
  20. package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
  21. package/dist/governance-telemetry.d.ts +6 -0
  22. package/dist/governance-telemetry.js +1 -1
  23. package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
  24. package/dist/index.d.ts +3280 -1305
  25. package/dist/index.js +2414 -350
  26. package/dist/index.js.map +1 -1
  27. package/dist/manifest.d.ts +228 -0
  28. package/dist/manifest.js +24 -0
  29. package/dist/manifest.js.map +1 -0
  30. package/dist/preview/index.js +45 -1
  31. package/dist/preview/index.js.map +1 -1
  32. package/dist/preview-runtime.d.ts +1 -2
  33. package/dist/preview-runtime.js +150 -14
  34. package/dist/preview-runtime.js.map +1 -1
  35. package/dist/react-types.d.ts +1 -2
  36. package/dist/registry.d.ts +1412 -203
  37. package/dist/registry.js +30 -3
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +3 -2
  40. package/dist/storyAdapter.d.ts +1 -2
  41. package/dist/storyAdapter.js +11 -49
  42. package/dist/storyAdapter.js.map +1 -1
  43. package/dist/test-utils.d.ts +3 -2
  44. package/dist/topology/index.d.ts +1 -1
  45. package/dist/topology/index.js +1 -1
  46. package/package.json +8 -2
  47. package/src/__tests__/contract-parser.test.ts +318 -277
  48. package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
  49. package/src/__tests__/preview-runtime.test.tsx +30 -8
  50. package/src/__tests__/schema.test.ts +191 -14
  51. package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
  52. package/src/analysis-plan/coverage.ts +181 -0
  53. package/src/analysis-plan/digest.ts +141 -0
  54. package/src/analysis-plan/index.ts +34 -0
  55. package/src/analysis-plan/types.ts +207 -0
  56. package/src/approved-contract-tokens.test.ts +39 -0
  57. package/src/approved-contract-tokens.ts +18 -0
  58. package/src/codes/__tests__/codes.test.ts +13 -0
  59. package/src/codes/codes.ts +40 -0
  60. package/src/compiled-types/index.ts +640 -39
  61. package/src/compiled-types/parse.test.ts +145 -4
  62. package/src/component-contract.ts +95 -53
  63. package/src/composition.ts +7 -13
  64. package/src/constants.ts +3 -6
  65. package/src/contract/hash.test.ts +20 -0
  66. package/src/contract/hash.ts +66 -9
  67. package/src/contract/index.ts +24 -1
  68. package/src/contract/manifest.test.ts +94 -0
  69. package/src/contract/manifest.ts +68 -0
  70. package/src/contract/preimage.test.ts +219 -1
  71. package/src/contract/preimage.ts +326 -6
  72. package/src/contract/stamp.test.ts +3 -0
  73. package/src/contract/stamp.ts +1 -1
  74. package/src/contract-parser.ts +54 -30
  75. package/src/defineFragment.test.ts +476 -91
  76. package/src/defineFragment.ts +204 -114
  77. package/src/domain-ids.test.ts +35 -0
  78. package/src/domain-ids.ts +61 -0
  79. package/src/evaluation/evaluate.test.ts +522 -0
  80. package/src/evaluation/evaluate.ts +690 -0
  81. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
  82. package/src/evaluation/index.ts +58 -0
  83. package/src/evaluation/receipt.ts +753 -0
  84. package/src/evaluation/types.ts +406 -0
  85. package/src/facts/builders.ts +2 -0
  86. package/src/facts/compile.ts +29 -6
  87. package/src/facts/fact-index.ts +13 -3
  88. package/src/facts/fact-integrity-v1.test.ts +172 -0
  89. package/src/facts/facts.test.ts +15 -0
  90. package/src/facts/ids.ts +46 -3
  91. package/src/facts/index.ts +14 -1
  92. package/src/facts/integrity.ts +134 -0
  93. package/src/facts/types.ts +36 -0
  94. package/src/governance-integrity.test.ts +1 -0
  95. package/src/governance-integrity.ts +5 -3
  96. package/src/governance-telemetry.ts +8 -0
  97. package/src/governance.ts +70 -8
  98. package/src/index.ts +230 -37
  99. package/src/preview/validation.test.ts +62 -0
  100. package/src/preview/validation.ts +48 -2
  101. package/src/preview-runtime.tsx +227 -20
  102. package/src/registry-install-plan.ts +200 -109
  103. package/src/registry-shards.test.ts +263 -0
  104. package/src/registry.ts +237 -0
  105. package/src/repository-binding.test.ts +50 -0
  106. package/src/repository-binding.ts +96 -0
  107. package/src/rules/families.test.ts +36 -0
  108. package/src/rules/finding.ts +7 -2
  109. package/src/rules/index.ts +17 -1
  110. package/src/rules/rule-config.test.ts +66 -0
  111. package/src/rules/rule-config.ts +73 -0
  112. package/src/rules/rules.test.ts +26 -0
  113. package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
  114. package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
  115. package/src/schema.ts +293 -113
  116. package/src/schemas/index.ts +1 -1
  117. package/src/storyAdapter.test.ts +68 -12
  118. package/src/storyAdapter.ts +44 -75
  119. package/src/topology/resolve-area.ts +1 -1
  120. package/src/types.ts +258 -40
  121. package/dist/chunk-3LLRNCPX.js.map +0 -1
  122. package/dist/chunk-RANPUC6C.js +0 -72
  123. package/dist/chunk-RANPUC6C.js.map +0 -1
  124. package/dist/chunk-XN3LSDPY.js.map +0 -1
  125. package/dist/chunk-YF65VYRY.js.map +0 -1
  126. package/src/fragment-types.ts +0 -214
  127. package/src/react-create-element.test.ts +0 -22
  128. package/src/react-create-element.ts +0 -12
  129. package/src/storyFilters.test.ts +0 -350
  130. package/src/storyFilters.ts +0 -253
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { describe, it, expect, beforeEach } from "vitest";
7
- import { createElement, type ComponentType } from "react";
7
+ import { createElement, type ComponentType, type ReactNode } from "react";
8
8
  import {
9
9
  storyModuleToFragment,
10
10
  setPreviewConfig,
@@ -94,8 +94,7 @@ describe("storyModuleToFragment", () => {
94
94
  describe("CSF2 Template.bind() pattern", () => {
95
95
  it("should handle CSF2 story functions", () => {
96
96
  // Simulate Template.bind({}) pattern
97
- const Template: CSF2Story = (args) =>
98
- createElement(MockComponent, args);
97
+ const Template: CSF2Story = (args) => createElement(MockComponent, args);
99
98
 
100
99
  const Primary: CSF2Story = Template.bind({});
101
100
  Primary.args = { label: "Primary Button" };
@@ -120,8 +119,7 @@ describe("storyModuleToFragment", () => {
120
119
  });
121
120
 
122
121
  it("should use storyName when provided on CSF2 story", () => {
123
- const Template: CSF2Story = (args) =>
124
- createElement(MockComponent, args);
122
+ const Template: CSF2Story = (args) => createElement(MockComponent, args);
125
123
 
126
124
  const MyStory: CSF2Story = Template.bind({});
127
125
  MyStory.args = { label: "Test" };
@@ -142,6 +140,22 @@ describe("storyModuleToFragment", () => {
142
140
  });
143
141
 
144
142
  describe("CSF3 object stories", () => {
143
+ it("retains a minimal empty story", () => {
144
+ const storyModule: StoryModule = {
145
+ default: {
146
+ title: "Components/Button",
147
+ component: MockComponent,
148
+ },
149
+ Default: {} as Story,
150
+ };
151
+
152
+ const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
153
+
154
+ expect(fragment!.variants).toHaveLength(1);
155
+ expect(fragment!.variants[0].name).toBe("Default");
156
+ expect(fragment!.variants[0].args).toBeUndefined();
157
+ });
158
+
145
159
  it("should handle CSF3 story objects with args", () => {
146
160
  const storyModule: StoryModule = {
147
161
  default: {
@@ -320,6 +334,40 @@ describe("storyModuleToFragment", () => {
320
334
  });
321
335
 
322
336
  describe("decorators", () => {
337
+ it("preserves Storybook's declared decorator composition order", () => {
338
+ const calls: string[] = [];
339
+ const decorator =
340
+ (name: string) =>
341
+ (Story: () => ReactNode): ReactNode => {
342
+ calls.push(name);
343
+ return Story();
344
+ };
345
+
346
+ setPreviewConfig({ decorators: [decorator("global-first"), decorator("global-last")] });
347
+ const storyModule: StoryModule = {
348
+ default: {
349
+ title: "Components/Button",
350
+ component: MockComponent,
351
+ decorators: [decorator("meta-first"), decorator("meta-last")],
352
+ },
353
+ Default: {
354
+ decorators: [decorator("story-first"), decorator("story-last")],
355
+ } as Story,
356
+ };
357
+
358
+ const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
359
+ fragment!.variants[0].render();
360
+
361
+ expect(calls).toEqual([
362
+ "story-last",
363
+ "story-first",
364
+ "meta-last",
365
+ "meta-first",
366
+ "global-last",
367
+ "global-first",
368
+ ]);
369
+ });
370
+
323
371
  it("should apply story decorators", () => {
324
372
  let decoratorCalled = false;
325
373
 
@@ -455,7 +503,8 @@ describe("storyModuleToFragment", () => {
455
503
  });
456
504
 
457
505
  describe("loaders", () => {
458
- it("should collect loaders from story", () => {
506
+ it("reports Storybook loader presence without executing with a fabricated context", () => {
507
+ let calls = 0;
459
508
  const storyModule: StoryModule = {
460
509
  default: {
461
510
  title: "Components/Button",
@@ -463,17 +512,24 @@ describe("storyModuleToFragment", () => {
463
512
  },
464
513
  WithLoader: {
465
514
  args: { label: "Test" },
466
- loaders: [async () => ({ data: "loaded" })],
515
+ loaders: [
516
+ async () => {
517
+ calls++;
518
+ return { data: "loaded" };
519
+ },
520
+ ],
467
521
  } as Story,
468
522
  };
469
523
 
470
524
  const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
471
525
 
472
- expect(fragment!.variants[0].loaders).toBeDefined();
473
- expect(fragment!.variants[0].loaders).toHaveLength(1);
526
+ expect(fragment!.variants[0].hasUnsupportedStorybookLoaders).toBe(true);
527
+ expect(fragment!.variants[0].loaders).toBeUndefined();
528
+ expect(calls).toBe(0);
474
529
  });
475
530
 
476
- it("should collect loaders from meta and story", () => {
531
+ it("reports preview and meta loaders without replaying them", () => {
532
+ setPreviewConfig({ loaders: [async () => ({ globalData: "global" })] });
477
533
  const storyModule: StoryModule = {
478
534
  default: {
479
535
  title: "Components/Button",
@@ -488,8 +544,8 @@ describe("storyModuleToFragment", () => {
488
544
 
489
545
  const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
490
546
 
491
- // Should have both meta and story loaders
492
- expect(fragment!.variants[0].loaders).toHaveLength(2);
547
+ expect(fragment!.variants[0].hasUnsupportedStorybookLoaders).toBe(true);
548
+ expect(fragment!.variants[0].loaders).toBeUndefined();
493
549
  });
494
550
  });
495
551
 
@@ -8,9 +8,8 @@
8
8
  * Supports Storybook 8.x with both CSF2 (Template.bind) and CSF3 (object stories).
9
9
  */
10
10
 
11
- import type { ComponentType, ReactNode } from "react";
11
+ import { createElement, type ComponentType, type ReactNode } from "react";
12
12
  import { toId, storyNameFromExport, isExportStory } from "./storybook-csf.js";
13
- import { getCreateElement } from "./react-create-element.js";
14
13
  import type {
15
14
  FragmentDefinition,
16
15
  FragmentMeta,
@@ -18,7 +17,6 @@ import type {
18
17
  PropDefinition,
19
18
  FragmentVariant,
20
19
  ControlType,
21
- VariantLoader,
22
20
  PlayFunction,
23
21
  PlayFunctionContext,
24
22
  VariantRenderOptions,
@@ -30,10 +28,7 @@ export { toId, storyNameFromExport, isExportStory };
30
28
  /**
31
29
  * Storybook decorator function signature
32
30
  */
33
- export type Decorator = (
34
- Story: () => ReactNode,
35
- context: StoryContext
36
- ) => ReactNode;
31
+ export type Decorator = (Story: () => ReactNode, context: StoryContext) => ReactNode;
37
32
 
38
33
  /**
39
34
  * Storybook loader function signature
@@ -223,9 +218,7 @@ export function storyModuleToFragment(
223
218
 
224
219
  const fragmentMeta: FragmentMeta = {
225
220
  name: componentName,
226
- description:
227
- meta.parameters?.docs?.description?.component ??
228
- `${componentName} component`,
221
+ description: meta.parameters?.docs?.description?.component ?? `${componentName} component`,
229
222
  category,
230
223
  tags: meta.tags?.filter((t) => t !== "autodocs"),
231
224
  status: "stable",
@@ -360,14 +353,24 @@ function extractControlInfo(argType: StoryArgType): {
360
353
  return {};
361
354
  }
362
355
 
363
- const control = typeof argType.control === "string"
364
- ? { type: argType.control }
365
- : argType.control;
356
+ const control = typeof argType.control === "string" ? { type: argType.control } : argType.control;
366
357
 
367
358
  // Map control type string to ControlType
368
359
  const validControlTypes: ControlType[] = [
369
- "text", "number", "range", "boolean", "select", "multi-select",
370
- "radio", "inline-radio", "check", "inline-check", "object", "file", "color", "date"
360
+ "text",
361
+ "number",
362
+ "range",
363
+ "boolean",
364
+ "select",
365
+ "multi-select",
366
+ "radio",
367
+ "inline-radio",
368
+ "check",
369
+ "inline-check",
370
+ "object",
371
+ "file",
372
+ "color",
373
+ "date",
371
374
  ];
372
375
 
373
376
  const controlType = validControlTypes.includes(control.type as ControlType)
@@ -461,10 +464,11 @@ function inferPropType(argType: StoryArgType): PropDefinition["type"] {
461
464
  * Handles both CSF 3 (objects) and CSF 2 (functions from Template.bind({}))
462
465
  */
463
466
  function isStory(value: unknown): value is Story | CSF2Story {
464
- // CSF 3: Story is an object with args/render/play
465
- if (typeof value === "object" && value !== null) {
466
- const obj = value as Record<string, unknown>;
467
- if ("args" in obj || "render" in obj || "play" in obj) return true;
467
+ // CSF 3 permits a minimal empty object. Storybook's native
468
+ // includeStories/excludeStories decision runs before this check, so do not
469
+ // silently discard `{}` after that filter has accepted its export name.
470
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
471
+ return true;
468
472
  }
469
473
 
470
474
  // CSF 2: Story is a function (from Template.bind({})) with args attached
@@ -544,8 +548,10 @@ function extractVariants(
544
548
  // Get story tags
545
549
  const storyTags = typeof story === "object" ? story.tags : undefined;
546
550
 
547
- // Collect loaders from global, meta, and story (in order)
548
- const loaders = collectLoaders(meta, story);
551
+ // Fragment loaders do not receive Storybook's original context. Preserve
552
+ // presence only; replaying a Storybook loader with a manufactured context
553
+ // is misleading and can change its behaviour.
554
+ const hasUnsupportedStorybookLoaders = hasStorybookLoaders(meta, story);
549
555
 
550
556
  // Compute the merged args for this variant (for code generation)
551
557
  const variantArgs = {
@@ -566,7 +572,7 @@ function extractVariants(
566
572
  ...(wrappedPlay && { play: wrappedPlay }),
567
573
  ...(storyId && { storyId }),
568
574
  ...(storyTags && { tags: storyTags }),
569
- ...(loaders.length > 0 && { loaders }),
575
+ ...(hasUnsupportedStorybookLoaders && { hasUnsupportedStorybookLoaders: true }),
570
576
  ...(hasArgs && { args: variantArgs }),
571
577
  });
572
578
  }
@@ -574,47 +580,13 @@ function extractVariants(
574
580
  return variants;
575
581
  }
576
582
 
577
- /**
578
- * Collect loaders from global, meta, and story levels
579
- * Returns wrapped loader functions that execute with context
580
- */
581
- function collectLoaders(
582
- meta: StoryMeta,
583
- story: Story | CSF2Story
584
- ): VariantLoader[] {
585
- const allLoaders: Loader[] = [
586
- ...(globalPreviewConfig.loaders ?? []),
587
- ...(meta.loaders ?? []),
588
- ...(typeof story === "function" ? story.loaders ?? [] : story.loaders ?? []),
589
- ];
590
-
591
- if (allLoaders.length === 0) {
592
- return [];
593
- }
594
-
595
- // Wrap each loader to execute without requiring context at call time
596
- // The actual context will be built when the loader is executed
597
- return allLoaders.map((loader) => {
598
- return async (): Promise<Record<string, unknown>> => {
599
- // Create a minimal context for loader execution
600
- const minimalContext: StoryContext = {
601
- args: {},
602
- argTypes: {},
603
- globals: {},
604
- parameters: {},
605
- id: "",
606
- kind: meta.title || "Unknown",
607
- name: "",
608
- story: "",
609
- viewMode: "story",
610
- loaded: {},
611
- abortSignal: new AbortController().signal,
612
- componentId: "",
613
- title: meta.title || "Unknown",
614
- };
615
- return loader(minimalContext);
616
- };
617
- });
583
+ /** Report loader presence without fabricating a Storybook context. */
584
+ function hasStorybookLoaders(meta: StoryMeta, story: Story | CSF2Story): boolean {
585
+ return (
586
+ (globalPreviewConfig.loaders?.length ?? 0) > 0 ||
587
+ (meta.loaders?.length ?? 0) > 0 ||
588
+ (story.loaders?.length ?? 0) > 0
589
+ );
618
590
  }
619
591
 
620
592
  /**
@@ -683,7 +655,7 @@ function createRenderFunction(
683
655
  ...globalPreviewConfig.args,
684
656
  ...meta.args,
685
657
  ...(typeof story === "function" ? story.args : story.args),
686
- ...options?.args, // Runtime overrides from viewer props panel
658
+ ...options?.args, // Runtime overrides from viewer props panel
687
659
  };
688
660
 
689
661
  const loadedData = options?.loadedData;
@@ -701,27 +673,24 @@ function createRenderFunction(
701
673
  // CSF 3: Story has custom render function
702
674
  // Support both render(args) and render(args, context) signatures
703
675
  renderFn = () =>
704
- story.render!.length >= 2
705
- ? story.render!(args, context)
706
- : story.render!(args);
676
+ story.render!.length >= 2 ? story.render!(args, context) : story.render!(args);
707
677
  } else if (meta.render) {
708
678
  // CSF 3: Meta has default render function
709
679
  renderFn = () =>
710
- meta.render!.length >= 2
711
- ? meta.render!(args, context)
712
- : meta.render!(args);
680
+ meta.render!.length >= 2 ? meta.render!(args, context) : meta.render!(args);
713
681
  } else {
714
682
  // Default: render component with args
715
- renderFn = () => getCreateElement()(component, args);
683
+ renderFn = () => createElement(component, args);
716
684
  }
717
685
 
718
- // Collect decorators in Storybook order
719
- // story meta global, then reverse to apply innermost first
686
+ // Storybook composes declared decorators in sequence. Keeping that
687
+ // sequence makes the last declaration outermost instead of reversing
688
+ // every global, meta, and story-level decorator list.
720
689
  const allDecorators = [
721
690
  ...(globalPreviewConfig.decorators ?? []),
722
691
  ...(meta.decorators ?? []),
723
- ...(typeof story === "function" ? story.decorators ?? [] : story.decorators ?? []),
724
- ].reverse();
692
+ ...(typeof story === "function" ? (story.decorators ?? []) : (story.decorators ?? [])),
693
+ ];
725
694
 
726
695
  // Apply decorators if any
727
696
  if (allDecorators.length > 0) {
@@ -6,7 +6,7 @@
6
6
  * preview, cross-repo reconciliation) import it; none reimplements glob/area
7
7
  * logic. Pure and browser-safe — no Node, no parser deps.
8
8
  *
9
- * See `apps/cloud/docs/topology/01-architecture.md §1`.
9
+ * See `docs/fragments-v1/ARCHITECTURE.md §1`.
10
10
  */
11
11
 
12
12
  export type AreaCriticality = "low" | "medium" | "high" | "revenue" | "regulated";