@pixel-point/toolcraft 0.0.16 → 0.0.17

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 (170) hide show
  1. package/package.json +1 -1
  2. package/src/generate-performance-iteration-execution.test.mjs +2 -2
  3. package/src/generate-test-demand-only-lifecycle.mjs +14 -3
  4. package/src/generate-test-dependency-sandbox.mjs +42 -5
  5. package/src/generate-test-doc-assertions.mjs +68 -18
  6. package/src/generate-test-doc-assertions.test.mjs +22 -0
  7. package/src/generate-test-file-assertions.mjs +43 -76
  8. package/src/generate-test-output-assertions.mjs +95 -0
  9. package/src/generate-test-package-assertions.mjs +6 -0
  10. package/src/generate-test-performance-boundary-assertions.mjs +45 -0
  11. package/src/generate-test-product-fixture.mjs +1 -0
  12. package/src/generate-test-product-fixture.test.mjs +18 -0
  13. package/src/generate-test-product-iteration.mjs +1 -0
  14. package/src/generate-test-product-iteration.test.mjs +1 -0
  15. package/src/generate-test-standalone-delivery-stages.mjs +14 -6
  16. package/src/generate.test.mjs +14 -0
  17. package/templates/runtime/contracts/component-contracts.choices.test.ts +12 -9
  18. package/templates/runtime/contracts/component-contracts.choices.ts +10 -9
  19. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +17 -2
  20. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +1 -1
  21. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
  22. package/templates/runtime/contracts/component-contracts.runtime.ts +3 -2
  23. package/templates/runtime/contracts/decision-contracts.test.ts +25 -4
  24. package/templates/runtime/contracts/decision-contracts.ts +3 -3
  25. package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
  26. package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
  27. package/templates/runtime/export/artifact-download.test.ts +71 -0
  28. package/templates/runtime/export/artifact-download.ts +80 -0
  29. package/templates/runtime/export/artifact-export-request.ts +23 -0
  30. package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
  31. package/templates/runtime/export/artifact-export-settings.ts +96 -0
  32. package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
  33. package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
  34. package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
  35. package/templates/runtime/export/artifact-frame-state.ts +44 -0
  36. package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
  37. package/templates/runtime/export/artifact-scene-frame.ts +118 -0
  38. package/templates/runtime/export/export-background.ts +65 -0
  39. package/templates/runtime/export/export-error.ts +43 -0
  40. package/templates/runtime/export/export.test.ts +6 -186
  41. package/templates/runtime/export/image-artifact-export.test.ts +111 -0
  42. package/templates/runtime/export/image-artifact-export.ts +145 -0
  43. package/templates/runtime/export/index.ts +14 -1
  44. package/templates/runtime/export/product-export-renderer.test.ts +77 -0
  45. package/templates/runtime/export/product-export-renderer.ts +63 -0
  46. package/templates/runtime/export/video-artifact-export.test.ts +166 -0
  47. package/templates/runtime/export/video-artifact-export.ts +164 -0
  48. package/templates/runtime/export/video-encoding-backend.ts +124 -0
  49. package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
  50. package/templates/runtime/export/video-encoding-policy.ts +86 -0
  51. package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
  52. package/templates/runtime/export/video-frame-schedule.ts +42 -0
  53. package/templates/runtime/index.ts +1 -0
  54. package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
  55. package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
  56. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +4 -303
  57. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +12 -27
  58. package/templates/runtime/react/app-shell/toolcraft-app.tsx +22 -4
  59. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
  60. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
  61. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
  62. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
  63. package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
  64. package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
  65. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
  66. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
  67. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
  68. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +17 -83
  69. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +42 -84
  70. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +62 -0
  71. package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
  72. package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
  73. package/templates/runtime/scene/scene-bounds.ts +1 -2
  74. package/templates/runtime/schema/canvas-render-scale.ts +62 -0
  75. package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
  76. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +3 -3
  77. package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
  78. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
  79. package/templates/runtime/schema/define-toolcraft.ts +1 -1
  80. package/templates/runtime/schema/schema-resolvers.ts +0 -56
  81. package/templates/runtime/schema/types.ts +4 -4
  82. package/templates/runtime/state/canvas-background-state.ts +6 -0
  83. package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
  84. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
  85. package/templates/starter/AGENTS.md +8 -7
  86. package/templates/starter/NOTICE.md +6 -0
  87. package/templates/starter/docs/toolcraft/README.md +2 -1
  88. package/templates/starter/docs/toolcraft/acceptance-testing.md +8 -6
  89. package/templates/starter/docs/toolcraft/agent-worklog.md +3 -3
  90. package/templates/starter/docs/toolcraft/assembly-workflow.md +4 -3
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -2
  92. package/templates/starter/docs/toolcraft/core/performance.md +14 -3
  93. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +3 -1
  94. package/templates/starter/docs/toolcraft/core/setup-export.md +16 -12
  95. package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
  96. package/templates/starter/docs/toolcraft/performance.md +20 -6
  97. package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
  98. package/templates/starter/docs/toolcraft/schema-reference.md +2 -6
  99. package/templates/starter/docs/toolcraft/workflow.md +7 -2
  100. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
  101. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +43 -1
  102. package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
  103. package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
  104. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
  105. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
  106. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +4 -1
  107. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +0 -1
  108. package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
  109. package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
  110. package/templates/starter/e2e/app-performance.spec.ts +16 -9
  111. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +9 -330
  112. package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
  113. package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
  114. package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
  115. package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
  116. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +4 -1
  117. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +43 -9
  118. package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
  119. package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
  120. package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
  121. package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
  122. package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
  123. package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
  124. package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
  125. package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
  126. package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
  127. package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
  128. package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
  129. package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
  130. package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
  131. package/templates/starter/e2e/performance-path-helpers.ts +210 -91
  132. package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
  133. package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
  134. package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
  135. package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
  136. package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
  137. package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
  138. package/templates/starter/gitignore +1 -0
  139. package/templates/starter/package.json +1 -0
  140. package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
  141. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
  142. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
  143. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
  144. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +24 -2
  145. package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
  146. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +46 -0
  147. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
  148. package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
  149. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +168 -0
  150. package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
  151. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
  152. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +155 -206
  153. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
  154. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
  155. package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
  156. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
  157. package/templates/starter/src/app/acceptance/output-background-rules.ts +1 -1
  158. package/templates/starter/src/app/acceptance/output-export.ts +13 -2
  159. package/templates/starter/src/app/acceptance/types.ts +7 -0
  160. package/templates/starter/src/app/acceptance/validate-coverage.ts +2 -1
  161. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
  162. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
  163. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +42 -1
  164. package/templates/starter/src/app/starter-acceptance.ts +2 -0
  165. package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
  166. package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
  167. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
  168. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -0
  169. package/templates/starter/src/routes/index.tsx +1 -0
  170. package/templates/runtime/export/export.ts +0 -166
@@ -6,34 +6,35 @@ import test from "node:test";
6
6
  import { createServer } from "vite";
7
7
 
8
8
  const starterRoot = path.resolve(import.meta.dirname, "..");
9
- const generatedRuntimePath = path.join(
9
+ const generatedProofControllerPath = path.join(
10
10
  starterRoot,
11
- "src/toolcraft/runtime/react/app-shell/toolcraft-source-asset-context.tsx",
11
+ "src/toolcraft/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts",
12
12
  );
13
- const monorepoRuntimePath = path.resolve(
13
+ const monorepoProofControllerPath = path.resolve(
14
14
  starterRoot,
15
- "../packages/toolcraft-runtime/src/react/app-shell/toolcraft-source-asset-context.tsx",
15
+ "../packages/toolcraft-runtime/src/react/app-shell/toolcraft-unavailable-resource-proof.ts",
16
16
  );
17
+ let transformedProofControllerSequence = 0;
17
18
 
18
- async function resolveRuntimeSource() {
19
+ async function resolveProofControllerSource() {
19
20
  try {
20
21
  return {
21
- filePath: generatedRuntimePath,
22
+ filePath: generatedProofControllerPath,
22
23
  requestPath:
23
- "/src/toolcraft/runtime/react/app-shell/toolcraft-source-asset-context.tsx",
24
+ "/src/toolcraft/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts",
24
25
  root: starterRoot,
25
- source: await fs.readFile(generatedRuntimePath, "utf8"),
26
+ source: await fs.readFile(generatedProofControllerPath, "utf8"),
26
27
  };
27
28
  } catch (error) {
28
29
  if (error?.code !== "ENOENT") throw error;
29
30
  }
30
31
 
31
32
  return {
32
- filePath: monorepoRuntimePath,
33
+ filePath: monorepoProofControllerPath,
33
34
  requestPath:
34
- "/packages/toolcraft-runtime/src/react/app-shell/toolcraft-source-asset-context.tsx",
35
+ "/packages/toolcraft-runtime/src/react/app-shell/toolcraft-unavailable-resource-proof.ts",
35
36
  root: path.resolve(starterRoot, ".."),
36
- source: await fs.readFile(monorepoRuntimePath, "utf8"),
37
+ source: await fs.readFile(monorepoProofControllerPath, "utf8"),
37
38
  };
38
39
  }
39
40
 
@@ -46,7 +47,7 @@ function readInjectedViteEnv(code, mode) {
46
47
  return JSON.parse(match[1]);
47
48
  }
48
49
 
49
- async function transformRuntime({ mode, requestPath, root }) {
50
+ async function transformProofController({ mode, requestPath, root }) {
50
51
  const server = await createServer({
51
52
  configFile: false,
52
53
  envDir: false,
@@ -65,67 +66,135 @@ async function transformRuntime({ mode, requestPath, root }) {
65
66
  }
66
67
  }
67
68
 
69
+ async function loadTransformedProofController({
70
+ fixtureFlag,
71
+ mode,
72
+ requestPath,
73
+ root,
74
+ }) {
75
+ const previousFixtureValue =
76
+ process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
77
+ try {
78
+ if (fixtureFlag === undefined) {
79
+ delete process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
80
+ } else {
81
+ process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES = fixtureFlag;
82
+ }
83
+ const code = await transformProofController({ mode, requestPath, root });
84
+ const moduleUrl =
85
+ `data:text/javascript;base64,${Buffer.from(code).toString("base64")}` +
86
+ `#toolcraft-proof-${++transformedProofControllerSequence}`;
87
+ const transformedModule = await import(moduleUrl);
88
+ assert.equal(
89
+ typeof transformedModule.decorateToolcraftUnavailableResourceProofCoordinator,
90
+ "function",
91
+ );
92
+ return {
93
+ code,
94
+ decorateCoordinator:
95
+ transformedModule.decorateToolcraftUnavailableResourceProofCoordinator,
96
+ };
97
+ } finally {
98
+ if (previousFixtureValue === undefined) {
99
+ delete process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
100
+ } else {
101
+ process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES = previousFixtureValue;
102
+ }
103
+ }
104
+ }
105
+
106
+ async function observeProofControllerDecoration(decorateCoordinator) {
107
+ const addedEvents = [];
108
+ const removedEvents = [];
109
+ const previousDocument = globalThis.document;
110
+ globalThis.document = {
111
+ addEventListener(eventName) {
112
+ addedEvents.push(eventName);
113
+ },
114
+ removeEventListener(eventName) {
115
+ removedEvents.push(eventName);
116
+ },
117
+ };
118
+ const originalDispose = async () => {};
119
+ const originalResolveResource = async () => new Uint8Array([1]);
120
+ const coordinator = {
121
+ dispose: originalDispose,
122
+ resolveResource: originalResolveResource,
123
+ };
124
+ try {
125
+ const returned = decorateCoordinator(coordinator, {});
126
+ const decorationApplied =
127
+ coordinator.dispose !== originalDispose &&
128
+ coordinator.resolveResource !== originalResolveResource;
129
+ await coordinator.dispose();
130
+ return {
131
+ addedEvents,
132
+ decorationApplied,
133
+ removedEvents,
134
+ returnedOriginal: returned === coordinator,
135
+ };
136
+ } finally {
137
+ if (previousDocument === undefined) delete globalThis.document;
138
+ else globalThis.document = previousDocument;
139
+ }
140
+ }
141
+
68
142
  test("unavailable-resource proof activation uses Vite-static env access and stays production-disabled", async () => {
69
- const runtime = await resolveRuntimeSource();
143
+ const proofController = await resolveProofControllerSource();
70
144
  assert.match(
71
- runtime.source,
145
+ proofController.source,
72
146
  /import\.meta\.env\?\.MODE/u,
73
- `${runtime.filePath} must read MODE through direct, Node-safe Vite-static access.`,
147
+ `${proofController.filePath} must read MODE through direct, Node-safe Vite-static access.`,
74
148
  );
75
149
  assert.match(
76
- runtime.source,
150
+ proofController.source,
77
151
  /import\.meta\.env\?\.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES/u,
78
- `${runtime.filePath} must read the protected fixture flag through direct, Node-safe Vite-static access.`,
152
+ `${proofController.filePath} must read the protected fixture flag through direct, Node-safe Vite-static access.`,
79
153
  );
80
154
  assert.doesNotMatch(
81
- runtime.source,
155
+ proofController.source,
82
156
  /\bconst\s+\w+\s*=\s*import\.meta\s*(?:;|\n)/u,
83
- `${runtime.filePath} must not alias import.meta before reading env.`,
157
+ `${proofController.filePath} must not alias import.meta before reading env.`,
84
158
  );
85
159
 
86
- const previousFixtureValue =
87
- process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
88
- try {
89
- process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES =
90
- "unavailable-resource";
91
- const testEnv = readInjectedViteEnv(
92
- await transformRuntime({ ...runtime, mode: "test" }),
93
- "test",
160
+ for (const scenario of [
161
+ { enabled: false, fixtureFlag: undefined, mode: "production" },
162
+ { enabled: false, fixtureFlag: undefined, mode: "development" },
163
+ { enabled: true, fixtureFlag: undefined, mode: "test" },
164
+ {
165
+ enabled: true,
166
+ fixtureFlag: "unavailable-resource",
167
+ mode: "production",
168
+ },
169
+ ]) {
170
+ const transformed = await loadTransformedProofController({
171
+ ...proofController,
172
+ fixtureFlag: scenario.fixtureFlag,
173
+ mode: scenario.mode,
174
+ });
175
+ const injectedEnv = readInjectedViteEnv(
176
+ transformed.code,
177
+ scenario.mode,
94
178
  );
95
- assert.equal(testEnv.MODE, "test");
179
+ assert.equal(injectedEnv.MODE, scenario.mode);
96
180
  assert.equal(
97
- testEnv.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES,
98
- "unavailable-resource",
181
+ injectedEnv.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES,
182
+ scenario.fixtureFlag,
99
183
  );
100
- assert.equal(
101
- testEnv.MODE === "test" ||
102
- testEnv.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES ===
103
- "unavailable-resource",
104
- true,
184
+ assert.deepEqual(
185
+ await observeProofControllerDecoration(
186
+ transformed.decorateCoordinator,
187
+ ),
188
+ {
189
+ addedEvents: scenario.enabled
190
+ ? ["toolcraft.browser-proof.unavailable-resource-request"]
191
+ : [],
192
+ decorationApplied: scenario.enabled,
193
+ removedEvents: scenario.enabled
194
+ ? ["toolcraft.browser-proof.unavailable-resource-request"]
195
+ : [],
196
+ returnedOriginal: true,
197
+ },
105
198
  );
106
-
107
- delete process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
108
- const productionEnv = readInjectedViteEnv(
109
- await transformRuntime({ ...runtime, mode: "production" }),
110
- "production",
111
- );
112
- assert.equal(productionEnv.MODE, "production");
113
- assert.equal(
114
- productionEnv.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES,
115
- undefined,
116
- );
117
- assert.equal(
118
- productionEnv.MODE === "test" ||
119
- productionEnv.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES ===
120
- "unavailable-resource",
121
- false,
122
- );
123
- } finally {
124
- if (previousFixtureValue === undefined) {
125
- delete process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES;
126
- } else {
127
- process.env.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES =
128
- previousFixtureValue;
129
- }
130
199
  }
131
200
  });
@@ -0,0 +1,113 @@
1
+ import type {
2
+ ResolvedToolcraftAppSchema,
3
+ ToolcraftActionSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+
6
+ import { getControlActions } from "./actions";
7
+ import type {
8
+ ToolcraftComponentAcceptance,
9
+ ToolcraftExportArtifactCoverage,
10
+ } from "./types";
11
+
12
+ const requiredCoverageByRole = {
13
+ "export-image": "all-required-image-export-behavior",
14
+ "export-video": "all-required-video-export-behavior",
15
+ } as const satisfies Record<
16
+ "export-image" | "export-video",
17
+ ToolcraftExportArtifactCoverage
18
+ >;
19
+
20
+ function getCoverageValues(
21
+ entry: ToolcraftComponentAcceptance,
22
+ ): readonly ToolcraftExportArtifactCoverage[] {
23
+ if (!entry.exportArtifactCoverage) return [];
24
+ return typeof entry.exportArtifactCoverage === "string"
25
+ ? [entry.exportArtifactCoverage]
26
+ : entry.exportArtifactCoverage;
27
+ }
28
+
29
+ function isMediaExportAction(
30
+ action: ToolcraftActionSchema | string,
31
+ ): action is ToolcraftActionSchema & {
32
+ role: "export-image" | "export-video";
33
+ } {
34
+ return (
35
+ typeof action !== "string" &&
36
+ (action.role === "export-image" || action.role === "export-video")
37
+ );
38
+ }
39
+
40
+ function collectMediaExportActions(schema: ResolvedToolcraftAppSchema) {
41
+ return (schema.panels.controls?.sections ?? []).flatMap((section) =>
42
+ Object.values(section.controls).flatMap((control) =>
43
+ control.type !== "panelActions" || !control.target
44
+ ? []
45
+ : getControlActions(control)
46
+ .filter(isMediaExportAction)
47
+ .map((action) => ({
48
+ requiredCoverage: requiredCoverageByRole[action.role],
49
+ role: action.role,
50
+ target: control.target,
51
+ value: action.value,
52
+ })),
53
+ ),
54
+ );
55
+ }
56
+
57
+ export function getToolcraftExportArtifactCoverageErrors({
58
+ acceptance,
59
+ schema,
60
+ }: Readonly<{
61
+ acceptance: readonly ToolcraftComponentAcceptance[];
62
+ schema: ResolvedToolcraftAppSchema;
63
+ }>): string[] {
64
+ const errors: string[] = [];
65
+ const exportActions = collectMediaExportActions(schema);
66
+
67
+ for (const entry of acceptance) {
68
+ const coverage = getCoverageValues(entry);
69
+ if (coverage.length === 0) continue;
70
+ if (entry.evidence !== "exported-bytes") {
71
+ errors.push(
72
+ `Acceptance "${entry.id}" declares exportArtifactCoverage but evidence is not "exported-bytes".`,
73
+ );
74
+ }
75
+ if (!entry.automated || !entry.browser) {
76
+ errors.push(
77
+ `Acceptance "${entry.id}" exportArtifactCoverage requires automated and browser proof.`,
78
+ );
79
+ }
80
+ for (const value of coverage) {
81
+ const hasMatchingAction = exportActions.some(
82
+ (action) =>
83
+ action.target === entry.target &&
84
+ action.requiredCoverage === value &&
85
+ (entry.actionCoverage ?? []).includes(action.value),
86
+ );
87
+ if (!hasMatchingAction) {
88
+ errors.push(
89
+ `Acceptance "${entry.id}" declares ${value} without a matching typed export action in target/actionCoverage.`,
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ for (const action of exportActions) {
96
+ const hasCoverage = acceptance.some(
97
+ (entry) =>
98
+ entry.target === action.target &&
99
+ entry.evidence === "exported-bytes" &&
100
+ entry.automated &&
101
+ entry.browser &&
102
+ (entry.actionCoverage ?? []).includes(action.value) &&
103
+ getCoverageValues(entry).includes(action.requiredCoverage),
104
+ );
105
+ if (!hasCoverage) {
106
+ errors.push(
107
+ `Typed ${action.role} action "${action.value}" on "${action.target}" requires ${action.requiredCoverage} acceptance coverage.`,
108
+ );
109
+ }
110
+ }
111
+
112
+ return errors;
113
+ }
@@ -103,7 +103,7 @@ export function getToolcraftOutputBackgroundErrors({
103
103
 
104
104
  if (!schemaHasOutputBackgroundToggleControl(controls)) {
105
105
  errors.push(
106
- 'Product apps with Export PNG must expose export.includeBackground in runtime Setup as a Switch labeled "Background". PNG export must pass that runtime value to createToolcraftPngExportCanvas includeBackground; live preview must use shouldIncludeToolcraftPreviewBackground(state); video export keeps the background.',
106
+ 'Product apps with Export PNG must expose export.includeBackground in runtime Setup as a Switch labeled "Background". Runtime artifact export owns PNG alpha and preserves the background for JPEG and video; bounded live preview must use shouldIncludeToolcraftPreviewBackground(state).',
107
107
  );
108
108
  }
109
109
 
@@ -11,7 +11,11 @@ import {
11
11
  import { getToolcraftOutputExportLayoutErrors } from "./output-export-layout-rules";
12
12
  import { buildToolcraftOutputExportFacts } from "./output-export-model";
13
13
  import { getToolcraftImageExportErrors } from "./output-image-export-rules";
14
- import type { ToolcraftVisibleControl } from "./types";
14
+ import { getToolcraftExportArtifactCoverageErrors } from "./export-artifact-coverage";
15
+ import type {
16
+ ToolcraftComponentAcceptance,
17
+ ToolcraftVisibleControl,
18
+ } from "./types";
15
19
 
16
20
  export {
17
21
  isOutputBackgroundToggleControl,
@@ -20,16 +24,22 @@ export {
20
24
  };
21
25
 
22
26
  export function getToolcraftOutputExportErrors({
27
+ acceptance,
23
28
  controls,
24
29
  schema,
25
30
  hasVideoExportAction = schemaHasVideoExportPanelAction(schema),
26
31
  }: {
32
+ acceptance: readonly ToolcraftComponentAcceptance[];
27
33
  controls: readonly ToolcraftVisibleControl[];
28
34
  hasVideoExportAction?: boolean;
29
35
  schema: ResolvedToolcraftAppSchema;
30
36
  }): string[] {
37
+ const exportArtifactCoverageErrors = getToolcraftExportArtifactCoverageErrors({
38
+ acceptance,
39
+ schema,
40
+ });
31
41
  if (!schemaHasPngExportPanelAction(schema)) {
32
- return [];
42
+ return exportArtifactCoverageErrors;
33
43
  }
34
44
 
35
45
  const facts = buildToolcraftOutputExportFacts({
@@ -41,5 +51,6 @@ export function getToolcraftOutputExportErrors({
41
51
  ...getToolcraftOutputExportLayoutErrors(facts),
42
52
  ...getToolcraftOutputBackgroundErrors({ controls, facts }),
43
53
  ...getToolcraftImageExportErrors(facts),
54
+ ...exportArtifactCoverageErrors,
44
55
  ];
45
56
  }
@@ -58,6 +58,10 @@ export type ToolcraftAcceptanceEvidence =
58
58
  | "timeline-output"
59
59
  | "viewport-side-effect";
60
60
 
61
+ export type ToolcraftExportArtifactCoverage =
62
+ | "all-required-image-export-behavior"
63
+ | "all-required-video-export-behavior";
64
+
61
65
  export type ToolcraftReferenceCoverage =
62
66
  | "canvas-sizing"
63
67
  | "control-mapping"
@@ -290,6 +294,9 @@ export type ToolcraftComponentAcceptance = {
290
294
  browserTestName: string;
291
295
  componentType: string;
292
296
  evidence: ToolcraftAcceptanceEvidence;
297
+ exportArtifactCoverage?:
298
+ | ToolcraftExportArtifactCoverage
299
+ | readonly ToolcraftExportArtifactCoverage[];
293
300
  expectedObservable: string;
294
301
  fixture: string;
295
302
  id: string;
@@ -160,8 +160,9 @@ const toolcraftAcceptanceValidators: readonly ToolcraftAcceptanceValidator[] = [
160
160
  {
161
161
  path: "schema.export",
162
162
  ruleId: "output-export-required",
163
- validate: ({ controls, hasVideoExportAction, schema }) =>
163
+ validate: ({ acceptance, controls, hasVideoExportAction, schema }) =>
164
164
  getToolcraftOutputExportErrors({
165
+ acceptance,
165
166
  controls,
166
167
  hasVideoExportAction,
167
168
  schema,
@@ -0,0 +1,131 @@
1
+ import { defineToolcraft } from "@repo/toolcraft-runtime";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { getToolcraftExportArtifactCoverageErrors } from "./acceptance/export-artifact-coverage";
5
+ import type { ToolcraftComponentAcceptance } from "./acceptance/types";
6
+
7
+ function createSchema(roles: readonly ("export-image" | "export-video")[]) {
8
+ return defineToolcraft({
9
+ canvas: { enabled: true },
10
+ panels: {
11
+ controls: {
12
+ sections: [
13
+ {
14
+ controls: {
15
+ output: {
16
+ actions: roles.map((role) => ({
17
+ label: role === "export-image" ? "Export Image" : "Export Video",
18
+ role,
19
+ value: role === "export-image" ? "export.image" : "export.video",
20
+ })),
21
+ target: "actions.output",
22
+ type: "panelActions",
23
+ },
24
+ },
25
+ },
26
+ ],
27
+ title: "Controls",
28
+ },
29
+ },
30
+ });
31
+ }
32
+
33
+ function createAcceptance(
34
+ overrides: Partial<ToolcraftComponentAcceptance> = {},
35
+ ): ToolcraftComponentAcceptance {
36
+ return {
37
+ actionCoverage: ["export.image"],
38
+ automated: true,
39
+ automatedTestName: "exports image",
40
+ browser: true,
41
+ browserTestName: "browser: exports image",
42
+ componentType: "panelActions",
43
+ evidence: "exported-bytes",
44
+ expectedObservable: "Downloads decoded image output.",
45
+ fixture: "image export fixture",
46
+ id: "actions.output",
47
+ kind: "control",
48
+ target: "actions.output",
49
+ userAction: "Click Export Image.",
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ describe("Toolcraft export artifact acceptance", () => {
55
+ it("requires exact image coverage for an image action", () => {
56
+ const schema = createSchema(["export-image"]);
57
+
58
+ expect(
59
+ getToolcraftExportArtifactCoverageErrors({
60
+ acceptance: [createAcceptance()],
61
+ schema,
62
+ }),
63
+ ).toContainEqual(
64
+ expect.stringContaining("all-required-image-export-behavior"),
65
+ );
66
+ expect(
67
+ getToolcraftExportArtifactCoverageErrors({
68
+ acceptance: [
69
+ createAcceptance({
70
+ exportArtifactCoverage: "all-required-image-export-behavior",
71
+ }),
72
+ ],
73
+ schema,
74
+ }),
75
+ ).toEqual([]);
76
+ });
77
+
78
+ it("does not allow image coverage to satisfy video", () => {
79
+ const errors = getToolcraftExportArtifactCoverageErrors({
80
+ acceptance: [
81
+ createAcceptance({
82
+ actionCoverage: ["export.video"],
83
+ exportArtifactCoverage: "all-required-image-export-behavior",
84
+ }),
85
+ ],
86
+ schema: createSchema(["export-video"]),
87
+ });
88
+
89
+ expect(errors).toEqual([
90
+ expect.stringContaining("without a matching typed export action"),
91
+ expect.stringContaining("all-required-video-export-behavior"),
92
+ ]);
93
+ });
94
+
95
+ it("requires exported bytes plus automated browser proof", () => {
96
+ const errors = getToolcraftExportArtifactCoverageErrors({
97
+ acceptance: [
98
+ createAcceptance({
99
+ automated: false,
100
+ browser: false,
101
+ evidence: "command-side-effect",
102
+ exportArtifactCoverage: "all-required-image-export-behavior",
103
+ }),
104
+ ],
105
+ schema: createSchema(["export-image"]),
106
+ });
107
+
108
+ expect(errors).toEqual([
109
+ expect.stringContaining('evidence is not "exported-bytes"'),
110
+ expect.stringContaining("requires automated and browser proof"),
111
+ expect.stringContaining("all-required-image-export-behavior"),
112
+ ]);
113
+ });
114
+
115
+ it("allows one row to cover both typed actions explicitly", () => {
116
+ expect(
117
+ getToolcraftExportArtifactCoverageErrors({
118
+ acceptance: [
119
+ createAcceptance({
120
+ actionCoverage: ["export.image", "export.video"],
121
+ exportArtifactCoverage: [
122
+ "all-required-image-export-behavior",
123
+ "all-required-video-export-behavior",
124
+ ],
125
+ }),
126
+ ],
127
+ schema: createSchema(["export-image", "export-video"]),
128
+ }),
129
+ ).toEqual([]);
130
+ });
131
+ });
@@ -4,7 +4,6 @@ import type { ToolcraftControlSchema } from "@repo/toolcraft-runtime";
4
4
  import type { ToolcraftComponentAcceptance } from "./acceptance/types";
5
5
  import {
6
6
  defineContractSchemaFixture,
7
- contractSchemaFixture,
8
7
  validateContractAcceptance,
9
8
  } from "./starter-acceptance.contract-fixtures";
10
9
 
@@ -63,9 +62,11 @@ function createKeyframesSchema(
63
62
  title = "Style",
64
63
  ) {
65
64
  return defineContractSchemaFixture({
66
- ...contractSchemaFixture,
65
+ canvas: {
66
+ enabled: true,
67
+ upload: true,
68
+ },
67
69
  panels: {
68
- ...contractSchemaFixture.panels,
69
70
  controls: {
70
71
  sections: [
71
72
  {
@@ -75,7 +76,16 @@ function createKeyframesSchema(
75
76
  ],
76
77
  title: "Controls",
77
78
  },
78
- timeline: { defaultDurationSeconds: 8, enabled: true, mode: "keyframes" as const },
79
+ timeline: {
80
+ defaultDurationSeconds: 8,
81
+ enabled: true,
82
+ mode: "keyframes",
83
+ },
84
+ },
85
+ toolbar: {
86
+ history: true,
87
+ radar: true,
88
+ zoom: true,
79
89
  },
80
90
  });
81
91
  }
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import { schemaHasVideoExportPanelAction } from "./acceptance/output-export";
3
+ import {
4
+ getToolcraftOutputExportErrors,
5
+ schemaHasVideoExportPanelAction,
6
+ } from "./acceptance/output-export";
4
7
  import { defineContractSchemaFixture, validateContractAcceptance } from "./starter-acceptance.contract-fixtures";
5
8
 
6
9
  describe("Toolcraft output export synthetic rules", () => {
@@ -20,6 +23,44 @@ describe("Toolcraft output export synthetic rules", () => {
20
23
  expect(schemaHasVideoExportPanelAction(playbackSchema)).toBe(false);
21
24
  });
22
25
 
26
+ it("does not let a video-only schema bypass artifact coverage", () => {
27
+ const videoOnlySchema = defineContractSchemaFixture({
28
+ canvas: { enabled: true },
29
+ panels: {
30
+ controls: {
31
+ sections: [
32
+ {
33
+ controls: {
34
+ outputActions: {
35
+ actions: [
36
+ {
37
+ label: "Export Video",
38
+ role: "export-video",
39
+ value: "export.video",
40
+ },
41
+ ],
42
+ target: "actions.output",
43
+ type: "panelActions",
44
+ },
45
+ },
46
+ },
47
+ ],
48
+ title: "Controls",
49
+ },
50
+ },
51
+ });
52
+
53
+ expect(
54
+ getToolcraftOutputExportErrors({
55
+ acceptance: [],
56
+ controls: [],
57
+ schema: videoOnlySchema,
58
+ }),
59
+ ).toEqual([
60
+ expect.stringContaining("all-required-video-export-behavior"),
61
+ ]);
62
+ });
63
+
23
64
  it("rejects reset actions in sticky footer panelActions", () => {
24
65
  const schemaWithFooterReset = defineContractSchemaFixture({
25
66
  canvas: {
@@ -23,6 +23,7 @@ import type {
23
23
  ToolcraftControlPartCoverage,
24
24
  ToolcraftControlSectionInventoryEntry,
25
25
  ToolcraftCustomControlCoverage,
26
+ ToolcraftExportArtifactCoverage,
26
27
  ToolcraftLayerCoverage,
27
28
  ToolcraftInteractionCapability,
28
29
  ToolcraftInteractionEvidenceSource,
@@ -96,6 +97,7 @@ export type {
96
97
  ToolcraftControlPartCoverage,
97
98
  ToolcraftControlSectionInventoryEntry,
98
99
  ToolcraftCustomControlCoverage,
100
+ ToolcraftExportArtifactCoverage,
99
101
  ToolcraftLayerCoverage,
100
102
  ToolcraftInteractionCapability,
101
103
  ToolcraftInteractionEvidenceSource,