@pixel-point/toolcraft 0.0.17 → 0.0.18

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/generate-test-doc-assertions.mjs +58 -68
  3. package/src/generate-test-doc-assertions.test.mjs +43 -0
  4. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  5. package/src/generate-test-file-assertions.mjs +26 -75
  6. package/src/generate-test-product-iteration.test.mjs +17 -0
  7. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  8. package/src/generate-test-standalone-assertions.mjs +10 -0
  9. package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
  10. package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
  11. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  12. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  13. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  14. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  15. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
  16. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  17. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  18. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
  19. package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
  20. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  21. package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
  22. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  23. package/templates/runtime/export/export-background.ts +0 -19
  24. package/templates/runtime/export/export.test.ts +45 -42
  25. package/templates/runtime/export/index.ts +44 -16
  26. package/templates/runtime/index.ts +2 -0
  27. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  28. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
  29. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
  30. package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
  31. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  32. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  33. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  34. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  35. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  36. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  37. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  38. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  39. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  40. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
  41. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  42. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  43. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
  44. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
  45. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  46. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  47. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  48. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  49. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  50. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  51. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  52. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  53. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  54. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  55. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  56. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  57. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  58. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  59. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  60. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  61. package/templates/runtime/react/index.ts +4 -0
  62. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  63. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  64. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  65. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  66. package/templates/runtime/scene/scene-bounds.ts +31 -17
  67. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  68. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  69. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  70. package/templates/runtime/schema/control-applicability.ts +433 -0
  71. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  72. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  73. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  74. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  75. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  76. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  77. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  78. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  79. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  80. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  81. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  82. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  83. package/templates/runtime/schema/types.ts +68 -13
  84. package/templates/starter/AGENTS.md +12 -10
  85. package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
  86. package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
  87. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  88. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  89. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  90. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
  92. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
  93. package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
  94. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  95. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  96. package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
  97. package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
  98. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  99. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
  100. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  101. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
  102. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  103. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  104. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  105. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  106. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  107. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  108. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  109. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  110. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
  111. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  112. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  113. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  114. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  115. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  116. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  117. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  118. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  119. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
  120. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  121. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  122. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  123. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  124. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  125. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  126. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  127. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  128. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  129. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  130. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  131. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  132. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  133. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  134. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  135. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  136. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  137. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  138. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  139. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  140. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  141. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  142. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  143. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  144. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  145. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  146. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  147. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  148. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  149. package/templates/starter/src/app/acceptance/output-export.ts +22 -10
  150. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  151. package/templates/starter/src/app/acceptance/types.ts +23 -8
  152. package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
  153. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  154. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  155. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  156. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  157. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  158. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  159. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  160. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  161. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  162. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  163. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  164. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  165. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  166. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  167. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  168. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  169. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  170. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  171. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  172. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  173. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  174. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  175. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  176. package/templates/starter/src/app/starter-acceptance.ts +11 -2
  177. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  178. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  179. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
  180. package/templates/starter/src/routes/index.tsx +1 -0
  181. package/templates/ui/components/composites/tabs.tsx +2 -2
  182. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  183. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  184. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  185. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  186. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  187. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  188. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  189. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  190. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  191. package/templates/ui/components/controls/index.ts +3 -0
  192. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  193. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  194. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  195. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  196. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -1,8 +1,6 @@
1
1
  import { expect, test } from "@playwright/test";
2
- import {
3
- expectToolcraftBackgroundOutputSemantics,
4
- expectToolcraftConditionalControlVisibility,
5
- } from "./browser-conditional-output-evidence-helpers";
2
+ import { expectToolcraftBackgroundOutputSemantics } from "./browser-background-output-evidence";
3
+ import { expectToolcraftControlApplicabilityState } from "./browser-control-applicability-evidence";
6
4
  import {
7
5
  expectToolcraftLayerReorder,
8
6
  expectToolcraftLayerSelection,
@@ -12,6 +10,7 @@ import {
12
10
  attachedEvidenceTypes,
13
11
  setProofState,
14
12
  setSyntheticControlOwner,
13
+ setSyntheticSwitchControlOwner,
15
14
  } from "./browser-semantic-evidence-test-helpers";
16
15
  import {
17
16
  expectToolcraftMediaLifecycle,
@@ -315,34 +314,81 @@ test("timeline recipes bind duration to renderer, keyframes to output, and loops
315
314
  ]);
316
315
  });
317
316
 
318
- test("conditional visibility recipe proves target absence and restoration through the gating control", async ({
317
+ test("control applicability recipe proves exact target absence and presence", async ({
319
318
  page,
320
319
  }, testInfo) => {
321
320
  await page.goto("/");
322
321
  const session = await createToolcraftBrowserProofSession(page);
323
322
  const attachmentCount = testInfo.attachments.length;
324
- await setSyntheticControlOwner(page, "feature.enabled", true);
323
+ await setSyntheticSwitchControlOwner(page, "feature.enabled", false);
325
324
  await setSyntheticControlOwner(page, "feature.conditional", true);
326
325
 
327
- await expectToolcraftConditionalControlVisibility(
326
+ const hiddenCase = {
327
+ expectation: "hidden" as const,
328
+ selectorControlType: "switch" as const,
329
+ selectorLabel: "feature.enabled",
330
+ selectorTarget: "feature.enabled",
331
+ selectorValue: false,
332
+ target: "feature.conditional",
333
+ };
334
+ const visibleCase = {
335
+ ...hiddenCase,
336
+ expectation: "visible" as const,
337
+ selectorValue: true,
338
+ };
339
+ const mismatchedAction = session.controlAction(
340
+ "feature.other",
341
+ () => undefined,
342
+ );
343
+
344
+ await expect(
345
+ expectToolcraftControlApplicabilityState(
346
+ session,
347
+ mismatchedAction,
348
+ hiddenCase,
349
+ { baseRequirementId: "feature.conditional", timeoutMs: 50 },
350
+ ),
351
+ ).rejects.toThrow(/target-scoped selector action/);
352
+ expect(testInfo.attachments).toHaveLength(attachmentCount);
353
+
354
+ await expect(
355
+ expectToolcraftControlApplicabilityState(
356
+ session,
357
+ session.controlAction("feature.enabled", () => undefined),
358
+ hiddenCase,
359
+ { baseRequirementId: "feature.conditional", timeoutMs: 50 },
360
+ ),
361
+ ).rejects.toThrow(/should be hidden/);
362
+ expect(testInfo.attachments).toHaveLength(attachmentCount);
363
+
364
+ await expectToolcraftControlApplicabilityState(
328
365
  session,
329
366
  session.controlAction("feature.enabled", (control, currentPage) => {
330
367
  expect(control).toBeTruthy();
331
- return setSyntheticControlOwner(currentPage, "feature.conditional", false);
368
+ return Promise.all([
369
+ setSyntheticSwitchControlOwner(currentPage, "feature.enabled", false),
370
+ setSyntheticControlOwner(currentPage, "feature.conditional", false),
371
+ ]).then(() => undefined);
332
372
  }),
373
+ hiddenCase,
374
+ { baseRequirementId: "feature.conditional" },
375
+ );
376
+ await expectToolcraftControlApplicabilityState(
377
+ session,
333
378
  session.controlAction("feature.enabled", (control, currentPage) => {
334
379
  expect(control).toBeTruthy();
335
- return setSyntheticControlOwner(currentPage, "feature.conditional", true);
380
+ return Promise.all([
381
+ setSyntheticSwitchControlOwner(currentPage, "feature.enabled", true),
382
+ setSyntheticControlOwner(currentPage, "feature.conditional", true),
383
+ ]).then(() => undefined);
336
384
  }),
337
- {
338
- requirementId: "feature.conditional",
339
- target: "feature.conditional",
340
- },
385
+ visibleCase,
386
+ { baseRequirementId: "feature.conditional" },
341
387
  );
342
388
 
343
389
  expect(attachedEvidenceTypes(testInfo, attachmentCount)).toEqual([
344
- "conditional-control-hidden",
345
- "conditional-control-visible",
390
+ "control-applicability-hidden",
391
+ "control-applicability-visible",
346
392
  ]);
347
393
  });
348
394
 
@@ -2,20 +2,12 @@ import { expect } from "@playwright/test";
2
2
 
3
3
  import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
4
4
  import {
5
- countToolcraftControlOwnersByTarget,
6
- getToolcraftControlFieldByTarget,
7
- } from "./browser-control-target-helpers";
8
- import {
9
- assertToolcraftBrowserActionForSession,
10
5
  assertToolcraftBrowserProofSession,
11
6
  getToolcraftBrowserActionTarget,
12
- getToolcraftBrowserProofPage,
13
7
  readToolcraftBrowserObservation,
14
- runToolcraftBrowserAction,
15
8
  runToolcraftBrowserValueAction,
16
9
  type ToolcraftBrowserAction,
17
10
  type ToolcraftBrowserObservation,
18
- type ToolcraftBrowserProofSession,
19
11
  } from "./browser-proof-session";
20
12
  import {
21
13
  assertToolcraftProducedArtifact,
@@ -77,66 +69,6 @@ function validateBackgroundPreviewObservation(
77
69
  ).not.toBe("");
78
70
  }
79
71
 
80
- export async function expectToolcraftConditionalControlVisibility(
81
- session: ToolcraftBrowserProofSession,
82
- hideControl: ToolcraftBrowserAction,
83
- showControl: ToolcraftBrowserAction,
84
- {
85
- requirementId,
86
- target,
87
- timeoutMs = 5_000,
88
- }: {
89
- requirementId: string;
90
- target: string;
91
- timeoutMs?: number;
92
- },
93
- ): Promise<void> {
94
- const normalizedTarget = target.trim();
95
- expect(
96
- normalizedTarget,
97
- "Conditional-visibility evidence requires the dependent schema target.",
98
- ).not.toBe("");
99
- assertToolcraftBrowserActionForSession(session, hideControl);
100
- assertToolcraftBrowserActionForSession(session, showControl);
101
- const hideGateTarget = requireTargetScopedAction(
102
- hideControl,
103
- "Conditional-visibility hide evidence requires a target-scoped gating control action.",
104
- );
105
- expect(
106
- requireTargetScopedAction(
107
- showControl,
108
- "Conditional-visibility show evidence requires a target-scoped gating control action.",
109
- ),
110
- "Conditional-visibility hide and show actions must exercise the same gating control.",
111
- ).toBe(hideGateTarget);
112
-
113
- const page = await getToolcraftBrowserProofPage(session);
114
- await getToolcraftControlFieldByTarget(page, normalizedTarget);
115
- await runToolcraftBrowserAction(hideControl);
116
- await expect
117
- .poll(
118
- () => countToolcraftControlOwnersByTarget(page, normalizedTarget),
119
- {
120
- message: `Conditional control "${normalizedTarget}" should be absent while its gating state is unavailable.`,
121
- timeout: timeoutMs,
122
- },
123
- )
124
- .toBe(0);
125
- await attachToolcraftBrowserRuntimeEvidence({
126
- evidenceType: "conditional-control-hidden",
127
- requirementId,
128
- target: normalizedTarget,
129
- });
130
-
131
- await runToolcraftBrowserAction(showControl);
132
- await getToolcraftControlFieldByTarget(page, normalizedTarget);
133
- await attachToolcraftBrowserRuntimeEvidence({
134
- evidenceType: "conditional-control-visible",
135
- requirementId,
136
- target: normalizedTarget,
137
- });
138
- }
139
-
140
72
  export async function expectToolcraftBackgroundOutputSemantics<
141
73
  TImageArtifact,
142
74
  TVideoArtifact = never,
@@ -0,0 +1,114 @@
1
+ import { expect, type Page } from "@playwright/test";
2
+
3
+ import {
4
+ getToolcraftApplicabilityRequirementId,
5
+ type ToolcraftControlApplicabilityCase,
6
+ } from "../src/app/starter-acceptance";
7
+ import {
8
+ countToolcraftControlOwnersByTarget,
9
+ getToolcraftControlFieldByTarget,
10
+ } from "./browser-control-target-helpers";
11
+ import {
12
+ assertToolcraftBrowserActionForSession,
13
+ getToolcraftBrowserActionTarget,
14
+ getToolcraftBrowserProofPage,
15
+ runToolcraftBrowserAction,
16
+ type ToolcraftBrowserAction,
17
+ type ToolcraftBrowserProofSession,
18
+ } from "./browser-proof-session";
19
+ import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
20
+
21
+ function requireSelectorOptionLabel(
22
+ applicabilityCase: ToolcraftControlApplicabilityCase,
23
+ ): string {
24
+ if (!applicabilityCase.selectorOptionLabel) {
25
+ throw new Error(
26
+ `Applicability case for ${applicabilityCase.selectorTarget} requires a selector option label.`,
27
+ );
28
+ }
29
+
30
+ return applicabilityCase.selectorOptionLabel;
31
+ }
32
+
33
+ async function expectToolcraftApplicabilitySelectorState(
34
+ page: Page,
35
+ applicabilityCase: ToolcraftControlApplicabilityCase,
36
+ ): Promise<void> {
37
+ const control = await getToolcraftControlFieldByTarget(
38
+ page,
39
+ applicabilityCase.selectorTarget,
40
+ );
41
+
42
+ switch (applicabilityCase.selectorControlType) {
43
+ case "checkbox":
44
+ case "switch":
45
+ await expect(
46
+ control.getByRole(applicabilityCase.selectorControlType),
47
+ ).toHaveAttribute("aria-checked", String(applicabilityCase.selectorValue));
48
+ return;
49
+ case "imagePicker":
50
+ case "segmented":
51
+ await expect(
52
+ control.getByRole("button", {
53
+ name: requireSelectorOptionLabel(applicabilityCase),
54
+ }),
55
+ ).toHaveAttribute("aria-pressed", "true");
56
+ return;
57
+ case "select":
58
+ await expect(control.getByRole("combobox")).toContainText(
59
+ requireSelectorOptionLabel(applicabilityCase),
60
+ );
61
+ return;
62
+ case "slider":
63
+ await expect(control.getByRole("slider")).toHaveAttribute(
64
+ "aria-valuenow",
65
+ String(applicabilityCase.selectorValue),
66
+ );
67
+ return;
68
+ case "tabs":
69
+ await expect(
70
+ control.getByRole("tab", {
71
+ name: requireSelectorOptionLabel(applicabilityCase),
72
+ }),
73
+ ).toHaveAttribute("aria-selected", "true");
74
+ }
75
+ }
76
+
77
+ export async function expectToolcraftControlApplicabilityState(
78
+ session: ToolcraftBrowserProofSession,
79
+ selectBranch: ToolcraftBrowserAction,
80
+ applicabilityCase: ToolcraftControlApplicabilityCase,
81
+ options: { baseRequirementId: string; timeoutMs?: number },
82
+ ): Promise<void> {
83
+ const selectorTarget = getToolcraftBrowserActionTarget(selectBranch)?.trim();
84
+ expect(
85
+ selectorTarget,
86
+ "Control-applicability evidence requires a target-scoped selector action.",
87
+ ).toBe(applicabilityCase.selectorTarget);
88
+ assertToolcraftBrowserActionForSession(session, selectBranch);
89
+
90
+ const page = await getToolcraftBrowserProofPage(session);
91
+ await runToolcraftBrowserAction(selectBranch);
92
+ await expectToolcraftApplicabilitySelectorState(page, applicabilityCase);
93
+ await expect
94
+ .poll(
95
+ () => countToolcraftControlOwnersByTarget(page, applicabilityCase.target),
96
+ {
97
+ message: `Control "${applicabilityCase.target}" should be ${applicabilityCase.expectation} for ${applicabilityCase.selectorTarget}=${JSON.stringify(applicabilityCase.selectorValue)}.`,
98
+ timeout: options.timeoutMs ?? 5_000,
99
+ },
100
+ )
101
+ .toBe(applicabilityCase.expectation === "hidden" ? 0 : 1);
102
+
103
+ await attachToolcraftBrowserRuntimeEvidence({
104
+ evidenceType:
105
+ applicabilityCase.expectation === "hidden"
106
+ ? "control-applicability-hidden"
107
+ : "control-applicability-visible",
108
+ requirementId: getToolcraftApplicabilityRequirementId(
109
+ options.baseRequirementId,
110
+ applicabilityCase,
111
+ ),
112
+ target: applicabilityCase.target,
113
+ });
114
+ }
@@ -18,6 +18,7 @@ export type InfinityCanvasObservation = Readonly<{
18
18
  finiteControlSize: { height: number; width: number } | null;
19
19
  finiteControlsPresent: boolean;
20
20
  overflow: string;
21
+ productSceneStatus: "empty" | "ready" | "unavailable" | null;
21
22
  sceneRect: { height: number; width: number; x: number; y: number } | null;
22
23
  viewportOffset: { x: number; y: number };
23
24
  }>;
@@ -91,18 +92,17 @@ export async function observeInfinityCanvasBackground(
91
92
 
92
93
  export async function observeInfinityCanvas(
93
94
  page: Page,
94
- sceneSelector?: string,
95
95
  ): Promise<InfinityCanvasObservation> {
96
- return page.evaluate((selector) => {
96
+ return page.evaluate(() => {
97
97
  const surface = document.querySelector<HTMLElement>(
98
98
  "[data-toolcraft-canvas-mode]",
99
99
  );
100
100
  const world = document.querySelector<HTMLElement>(
101
101
  "[data-toolcraft-canvas-world]",
102
102
  );
103
- const scene = selector
104
- ? document.querySelector<HTMLElement>(selector)
105
- : null;
103
+ const productScene = document.querySelector<HTMLElement>(
104
+ "[data-toolcraft-product-scene]",
105
+ );
106
106
  const finiteControlsPresent = [
107
107
  "canvas.aspectRatio",
108
108
  "canvas.size.width",
@@ -138,7 +138,7 @@ export async function observeInfinityCanvas(
138
138
  const parsed = Number.parseFloat(value ?? "");
139
139
  return Number.isFinite(parsed) ? parsed : null;
140
140
  };
141
- const style = scene?.style;
141
+ const style = productScene?.style;
142
142
  const sceneValues = style
143
143
  ? {
144
144
  height: read(style.height),
@@ -156,11 +156,11 @@ export async function observeInfinityCanvas(
156
156
  y: sceneValues.y!,
157
157
  }
158
158
  : null;
159
+ const productSceneStatus =
160
+ productScene?.dataset.toolcraftProductSceneStatus;
159
161
 
160
162
  return {
161
- artboardPresent: Boolean(
162
- editableCanvas,
163
- ),
163
+ artboardPresent: Boolean(editableCanvas),
164
164
  canvasMode: surface?.getAttribute("data-toolcraft-canvas-mode") ?? null,
165
165
  finiteCanvasSize:
166
166
  canvasWidth === null || canvasHeight === null
@@ -172,13 +172,19 @@ export async function observeInfinityCanvas(
172
172
  : { height: controlHeight, width: controlWidth },
173
173
  finiteControlsPresent,
174
174
  overflow: surface ? getComputedStyle(surface).overflow : "missing",
175
+ productSceneStatus:
176
+ productSceneStatus === "ready" ||
177
+ productSceneStatus === "empty" ||
178
+ productSceneStatus === "unavailable"
179
+ ? productSceneStatus
180
+ : null,
175
181
  sceneRect,
176
182
  viewportOffset: {
177
183
  x: Number(world?.dataset.toolcraftCanvasOffsetX ?? 0),
178
184
  y: Number(world?.dataset.toolcraftCanvasOffsetY ?? 0),
179
185
  },
180
186
  };
181
- }, sceneSelector);
187
+ });
182
188
  }
183
189
 
184
190
  export function expectInfiniteCanvasObservation(
@@ -191,7 +197,10 @@ export function expectInfiniteCanvasObservation(
191
197
  expect(observation.finiteControlSize).toEqual(null);
192
198
  expect(observation.finiteControlsPresent).toBe(false);
193
199
  expect(observation.overflow).toBe("visible");
194
- if (expectedSceneRect) expect(observation.sceneRect).toEqual(expectedSceneRect);
200
+ if (expectedSceneRect) {
201
+ expect(observation.productSceneStatus).toBe("ready");
202
+ expect(observation.sceneRect).toEqual(expectedSceneRect);
203
+ }
195
204
  expect(Number.isFinite(observation.viewportOffset.x)).toBe(true);
196
205
  expect(Number.isFinite(observation.viewportOffset.y)).toBe(true);
197
206
  }
@@ -26,6 +26,9 @@ import {
26
26
  getToolcraftOrientationAxisPosition,
27
27
  type ToolcraftOrientationAxis,
28
28
  } from "./browser-orientation-gizmo-actions";
29
+ import {
30
+ expectToolcraftOrientationLiveDragPreconditions,
31
+ } from "./browser-orientation-gizmo-live-preconditions";
29
32
 
30
33
  export type ToolcraftOrientationBrowserObservation = {
31
34
  outputSignature: string;
@@ -222,6 +225,7 @@ export async function expectToolcraftOrientationAxisDrag(
222
225
  message,
223
226
  options,
224
227
  );
228
+ await expectToolcraftOrientationLiveDragPreconditions(session);
225
229
  const before = snapshotToolcraftOutcome(await observe(), message);
226
230
 
227
231
  validateObservation(before, options);
@@ -0,0 +1,86 @@
1
+ import { expect, type Locator, type Page } from "@playwright/test";
2
+
3
+ import {
4
+ countToolcraftControlOwnersByTarget,
5
+ getToolcraftControlFieldByTarget,
6
+ } from "./browser-control-target-helpers";
7
+ import {
8
+ getToolcraftBrowserProofPage,
9
+ type ToolcraftBrowserProofSession,
10
+ } from "./browser-proof-session";
11
+
12
+ const TOOLCRAFT_APP_ROOT_SELECTOR = '[data-slot="toolcraft-runtime-app"]';
13
+ const RENDER_SCALE_TARGET = "canvas.renderScale";
14
+
15
+ async function expectPausedPlaybackWhenPresent(page: Page): Promise<void> {
16
+ const root = page.locator(TOOLCRAFT_APP_ROOT_SELECTOR);
17
+ const play = root.getByRole("button", { name: "Play playback" });
18
+ const pause = root.getByRole("button", { name: "Pause playback" });
19
+ const playCount = await play.count();
20
+ const actionCount = playCount + (await pause.count());
21
+
22
+ if (actionCount === 0) return;
23
+
24
+ expect(
25
+ actionCount,
26
+ "Orientation axis-drag proof requires exactly one standard playback action.",
27
+ ).toBe(1);
28
+ expect(
29
+ playCount,
30
+ "Orientation axis-drag proof must be paused; Play playback must be visible before its baseline.",
31
+ ).toBe(1);
32
+ await expect(play).toBeVisible();
33
+ }
34
+
35
+ async function readFiniteAriaNumber(
36
+ slider: Locator,
37
+ name: "aria-valuemax" | "aria-valuenow",
38
+ ): Promise<number> {
39
+ const raw = await slider.getAttribute(name);
40
+ const value = raw === null || raw.trim() === "" ? Number.NaN : Number(raw);
41
+
42
+ expect(
43
+ Number.isFinite(value),
44
+ `Orientation axis-drag proof requires a finite ${name} on canvas.renderScale.`,
45
+ ).toBe(true);
46
+ return value;
47
+ }
48
+
49
+ async function expectMaximumRenderScaleWhenPresent(page: Page): Promise<void> {
50
+ const ownerCount = await countToolcraftControlOwnersByTarget(
51
+ page,
52
+ RENDER_SCALE_TARGET,
53
+ );
54
+
55
+ if (ownerCount === 0) return;
56
+
57
+ expect(
58
+ ownerCount,
59
+ "Orientation axis-drag proof requires exactly one canvas.renderScale owner.",
60
+ ).toBe(1);
61
+ const field = await getToolcraftControlFieldByTarget(
62
+ page,
63
+ RENDER_SCALE_TARGET,
64
+ );
65
+ const slider = field.getByRole("slider");
66
+ await expect(
67
+ slider,
68
+ "Orientation axis-drag proof requires one canvas.renderScale slider.",
69
+ ).toHaveCount(1);
70
+ await expect(slider).toBeVisible();
71
+
72
+ const value = await readFiniteAriaNumber(slider, "aria-valuenow");
73
+ const maximum = await readFiniteAriaNumber(slider, "aria-valuemax");
74
+ expect(
75
+ value,
76
+ "Orientation axis-drag proof requires canvas.renderScale at maximum quality before its baseline.",
77
+ ).toBe(maximum);
78
+ }
79
+
80
+ export async function expectToolcraftOrientationLiveDragPreconditions(
81
+ session: ToolcraftBrowserProofSession,
82
+ ): Promise<void> {
83
+ const page = await getToolcraftBrowserProofPage(session);
84
+ await expectPausedPlaybackWhenPresent(page);
85
+ await expectMaximumRenderScaleWhenPresent(page);
86
+ }
@@ -0,0 +1,84 @@
1
+ import type { ResolvedToolcraftAppSchema } from "@repo/toolcraft-runtime";
2
+
3
+ import type { ToolcraftBrowserRuntimeRequirement } from "../src/app/test-evidence/browser-runtime-contract";
4
+ import {
5
+ getToolcraftApplicabilityRequirementId,
6
+ getToolcraftControlApplicabilityCases,
7
+ type ToolcraftComponentAcceptance,
8
+ type ToolcraftControlSectionInventoryEntry,
9
+ } from "../src/app/starter-acceptance";
10
+
11
+ type ApplicabilityAcceptanceSource = Pick<
12
+ ToolcraftComponentAcceptance,
13
+ "browserTestName" | "id" | "kind" | "target"
14
+ >;
15
+
16
+ const layoutOnlyEvidenceTypes = new Set<
17
+ ToolcraftBrowserRuntimeRequirement["evidenceType"]
18
+ >(["discrete-slider-layout", "segmented-control-layout"]);
19
+
20
+ export function expandToolcraftControlApplicabilityRequirements({
21
+ entry,
22
+ schema,
23
+ sectionInventory,
24
+ templates,
25
+ }: Readonly<{
26
+ entry: ApplicabilityAcceptanceSource;
27
+ schema?: Pick<ResolvedToolcraftAppSchema, "panels">;
28
+ sectionInventory: readonly ToolcraftControlSectionInventoryEntry[];
29
+ templates: readonly ToolcraftBrowserRuntimeRequirement[];
30
+ }>): ToolcraftBrowserRuntimeRequirement[] {
31
+ if (!schema || entry.kind !== "control" || !entry.target) {
32
+ return [...templates];
33
+ }
34
+
35
+ const cases = getToolcraftControlApplicabilityCases({
36
+ schema,
37
+ sectionInventory,
38
+ target: entry.target,
39
+ });
40
+
41
+ if (cases.length === 0) {
42
+ return [...templates];
43
+ }
44
+
45
+ const hasOutcomeRequirement = templates.some(
46
+ (requirement) => !layoutOnlyEvidenceTypes.has(requirement.evidenceType),
47
+ );
48
+
49
+ return cases.flatMap((applicabilityCase) => {
50
+ const visibilityRequirement = {
51
+ evidenceType:
52
+ applicabilityCase.expectation === "hidden"
53
+ ? ("control-applicability-hidden" as const)
54
+ : ("control-applicability-visible" as const),
55
+ requirementId: getToolcraftApplicabilityRequirementId(
56
+ entry.id,
57
+ applicabilityCase,
58
+ ),
59
+ target: entry.target,
60
+ testName: entry.browserTestName,
61
+ };
62
+
63
+ if (applicabilityCase.expectation === "hidden") {
64
+ return [visibilityRequirement];
65
+ }
66
+
67
+ if (!hasOutcomeRequirement) {
68
+ throw new Error(
69
+ `Visible applicability case for ${entry.id} has no product outcome requirement.`,
70
+ );
71
+ }
72
+
73
+ return [
74
+ visibilityRequirement,
75
+ ...templates.map((requirement) => ({
76
+ ...requirement,
77
+ requirementId: getToolcraftApplicabilityRequirementId(
78
+ requirement.requirementId,
79
+ applicabilityCase,
80
+ ),
81
+ })),
82
+ ];
83
+ });
84
+ }
@@ -19,7 +19,10 @@ import {
19
19
  type ToolcraftBrowserRuntimeRequirement,
20
20
  type ToolcraftBrowserRuntimeTest,
21
21
  } from "../src/app/test-evidence/browser-runtime-contract";
22
- import { starterAcceptance } from "../src/app/starter-acceptance";
22
+ import {
23
+ starterAcceptance,
24
+ starterControlSectionInventory,
25
+ } from "../src/app/starter-acceptance";
23
26
  import { starterPerformance } from "../src/app/starter-performance";
24
27
  import { starterSchema } from "../src/app/starter-schema";
25
28
  import {
@@ -166,7 +169,11 @@ export default class ToolcraftBrowserRuntimeEvidenceReporter implements Reporter
166
169
  this.performanceSchema = this.performanceSchemaOverride ?? starterSchema;
167
170
  this.acceptanceRequirements =
168
171
  this.acceptanceRequirementsOverride ??
169
- deriveToolcraftBrowserRuntimeRequirements(starterAcceptance, starterSchema);
172
+ deriveToolcraftBrowserRuntimeRequirements(
173
+ starterAcceptance,
174
+ starterSchema,
175
+ starterControlSectionInventory,
176
+ );
170
177
  this.canonicalPaths = deriveToolcraftPerformancePaths(
171
178
  this.performanceSchema,
172
179
  this.performanceConfig,