@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
@@ -58,7 +58,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
58
58
  area: "runtime-shell",
59
59
  currentConstraint: "Apps must assemble through defineToolcraft and ToolcraftApp.",
60
60
  desiredBehavior:
61
- "Generated apps use the Toolcraft runtime shell and keep product-specific rendering inside supported extension points: schema controls, canvasContent product output, controlRenderers for true custom controls, one shared ToolcraftAppComposition.exportRenderer for typed image/video actions, ToolcraftApp onPanelAction for non-export product actions, and runtime commands. App-specific code must not import or render low-level runtime surfaces or built-in control components directly, instantiate encoders, or own artifact download mechanics.",
61
+ "Generated apps use the Toolcraft runtime shell and keep product-specific rendering inside supported extension points: schema controls, bounded canvasContent product output, editor-only full-viewport infiniteCanvasContent product output, controlRenderers for true custom controls, one shared ToolcraftAppComposition.exportRenderer for typed image/video actions, ToolcraftApp onPanelAction for non-export product actions, and runtime commands. App-specific code must not import or render low-level runtime surfaces or built-in control components directly, instantiate encoders, or own artifact download mechanics.",
62
62
  enforcement: [
63
63
  "cli-integrity-check",
64
64
  "acceptance-validator",
@@ -87,7 +87,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
87
87
  currentConstraint:
88
88
  "The Toolcraft canvas shell owns the visible workspace backing behind product output.",
89
89
  desiredBehavior:
90
- "Generated apps preserve the runtime canvas surface and do not replace or hide it when product renderers customize their own background. In infinite mode the shell fills the complete viewport with the selected standard Background color, while bounded product background pixels remain suppressed.",
90
+ "Generated apps preserve the runtime canvas surface and do not replace or hide it when product renderers customize their own background. In infinite mode the shell fills the complete viewport with the selected standard Background color, while bounded product background pixels remain suppressed. Preview-only product environments may layer through infiniteCanvasContent across the complete viewport under the transformed world; that layer is pointer-transparent and excluded from scene bounds and export.",
91
91
  enforcement: ["browser-helper", "starter-agents", "spec-checklist"],
92
92
  id: "canvas-surface-preserved",
93
93
  level: "invariant",
@@ -99,7 +99,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
99
99
  currentConstraint:
100
100
  "Editable output can switch from a finite artboard to an unbounded workspace, but product renderers and export actions still need one canonical world-space frame contract.",
101
101
  desiredBehavior:
102
- "Editable-output apps expose one runtime-owned Infinity canvas mode. Enabling it removes finite artboard clipping and size controls while preserving the dormant finite canvas size for exact restoration. When the standard Background pair exists, its selected color fills the complete infinite viewport and Background off atomically resolves finite mode and disables Infinity. Runtime image and model assets retain explicit world-space frames. ToolcraftAppComposition.sceneBoundsProvider supplies product-renderer bounds for one exact state through one direct composition boundary; image export resolves the current frame and video export unions every state from the runtime-owned timestamp schedule. Hidden layers, suppressed default consumers, and editor-only UI do not expand output bounds. Finite mode continues to export the full finite canvas size, while empty, unavailable, and unsafe oversized infinite exports return typed visible failures.",
102
+ "Editable-output apps expose one runtime-owned Infinity canvas mode. Enabling it removes finite artboard clipping and size controls while preserving the dormant finite canvas size for exact restoration. When the standard Background pair exists, its selected color fills the complete infinite viewport and Background off atomically resolves finite mode and disables Infinity. Runtime image and model assets retain explicit world-space frames. ToolcraftAppComposition.sceneBoundsProvider supplies product-renderer bounds for one exact state through one direct composition boundary. Runtime applies that same product frame to the live infinite preview, and custom raster/WebGL renderers consume useToolcraftProductSceneFrame for backing size and world-to-local translation instead of the dormant finite canvas size. ToolcraftAppComposition.infiniteCanvasContent is reserved for preview-only product environments that fill the complete Infinity viewport without world transforms, pointer input, scene-bounds participation, or export inclusion. Image export resolves the current frame and video export unions every state from the runtime-owned timestamp schedule. Hidden layers, suppressed default consumers, viewport environments, and editor-only UI do not expand preview or output bounds. Finite preview and export remain the full finite canvas and do not call the provider, while empty, unavailable, and unsafe oversized infinite exports return typed visible failures.",
103
103
  enforcement: [
104
104
  "acceptance-validator",
105
105
  "browser-helper",
@@ -203,9 +203,9 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
203
203
  {
204
204
  area: "controls",
205
205
  currentConstraint:
206
- "Every visible schema control must bind to runtime state, reset from schema defaults, and have acceptance and browser coverage.",
206
+ "Every product control explicitly declares always or conditional applicability; every visible finite sibling branch binds to runtime state, resets from schema defaults, and passes its accepted browser outcome.",
207
207
  desiredBehavior:
208
- "Controls are not decorative; each visible control proves its product responsibility through runtime and output observables.",
208
+ "Non-applicable controls are absent with values preserved, and no always or conditional control remains visible in a finite product branch where its value is ignored.",
209
209
  enforcement: ["acceptance-validator", "browser-helper"],
210
210
  id: "controls-product-coverage",
211
211
  level: "invariant",
@@ -215,13 +215,13 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
215
215
  {
216
216
  area: "controls",
217
217
  currentConstraint:
218
- "Product-output apps expose final output delivery through sticky footer panelActions.",
218
+ "Product artifact delivery must match the required productReadiness.exportIntent declaration.",
219
219
  desiredBehavior:
220
- 'Product apps include Export PNG plus an "Image Export" section for format and 2K/4K/8K resolution. Apps whose requested delivery includes animation also include Export Video plus "Video Export" settings; a playback or keyframe timeline may exist for interactive preview without inventing video delivery. Copy can be secondary, but it does not replace export. Product apps declare the standard background pair and runtime places it in Setup: Background beside Infinity canvas, Color below, and Timeline last. One shared product exportRenderer draws deterministic scene-coordinate frames; runtime owns settings, scene crop, PNG/JPEG/video background semantics, visible runtime media/model composition, exact timestamped encoding, download, progress, and typed failures.',
220
+ 'Every product declares productReadiness.exportIntent. Image export starts as the Toolcraft default and is removed only with explicit user-removal evidence; video export requires explicit user request evidence. Animation, playback, keyframes, or timeline presence never changes artifact delivery intent. Resolved intent capabilities correspond exactly to schema actions, settings sections, and acceptance coverage: image-only, image-plus-video, video-only, and explicit no-export products expose only the runtime-owned artifact surfaces their intent enables. Product apps declare the standard background pair and runtime places it in Setup: Background beside Infinity canvas, Color below, and Timeline last. One shared product exportRenderer draws deterministic scene-coordinate frames for enabled artifact types; runtime owns settings, scene crop, PNG/JPEG/video background semantics, visible runtime media/model composition, exact timestamped encoding, download, progress, and typed failures.',
221
221
  enforcement: ["acceptance-validator", "performance-validator", "browser-helper", "starter-agents"],
222
222
  id: "output-export-required",
223
223
  level: "invariant",
224
- title: "Product output always has export",
224
+ title: "Product output follows artifact export intent",
225
225
  verdict: "move-to-validator",
226
226
  },
227
227
  {
@@ -229,7 +229,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
229
229
  currentConstraint:
230
230
  "Every product controls section must be represented in the exported Control Section Inventory before schema authoring.",
231
231
  desiredBehavior:
232
- "Generated product apps publish a typed Control Section Inventory with section title, product entity or workflow stage, exact targets, and a concrete grouping reason so section ownership is machine-checkable without prescribing the final product layout.",
232
+ "Generated product apps publish a typed Control Section Inventory with stable entityId, human-readable entity, exact targets, and a concrete grouping reason. One entity stays in one section through ten controls; larger entities use balanced two-to-ten-control workflow stages with explicit split evidence, so section ownership and reset boundaries are machine-checkable without runtime rewriting.",
233
233
  enforcement: ["acceptance-validator", "docs", "starter-agents", "spec-checklist"],
234
234
  id: "controls-section-inventory-required",
235
235
  level: "invariant",
@@ -253,7 +253,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
253
253
  currentConstraint:
254
254
  "Labels, color placement, section grouping, selector order, and inline density need product-aware decisions.",
255
255
  desiredBehavior:
256
- "Given a valid Control Section Inventory, ordering, section cohesion, labels, color placement, and compact density remain product-aware recommendations. Validators report questionable grouping without rejecting a coherent product-specific choice solely because it differs from a preferred heuristic.",
256
+ "Given a valid Control Section Inventory, ordering, labels, color placement, and compact density remain product-aware recommendations. The ten-control maximum and entity cohesion are inventory invariants; heuristics may report questionable presentation without rejecting a coherent product-specific choice solely because it differs from a preferred layout.",
257
257
  enforcement: ["acceptance-validator", "schema-normalization", "docs", "starter-agents"],
258
258
  id: "controls-layout-heuristics",
259
259
  level: "heuristic",
@@ -1,29 +1,10 @@
1
- import type { ResolvedToolcraftAppSchema } from "../schema/types";
2
1
  import type { ToolcraftState } from "../state/types";
3
2
 
4
- export type ToolcraftExportFormat = "png" | "video";
5
-
6
- export type ToolcraftExportBackgroundOptions = {
7
- format: ToolcraftExportFormat;
8
- schema: ResolvedToolcraftAppSchema;
9
- };
10
-
11
3
  export type ToolcraftPreviewBackgroundOptions = {
12
4
  includeBackgroundTarget?: string;
13
5
  state: ToolcraftState;
14
6
  };
15
7
 
16
- export function shouldIncludeToolcraftExportBackground({
17
- format,
18
- schema,
19
- }: ToolcraftExportBackgroundOptions): boolean {
20
- if (format === "video") {
21
- return true;
22
- }
23
-
24
- return schema.export.png.background !== "transparent";
25
- }
26
-
27
8
  export function shouldIncludeToolcraftPreviewBackground({
28
9
  includeBackgroundTarget = "export.includeBackground",
29
10
  state,
@@ -1,9 +1,18 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, expectTypeOf, it } from "vitest";
2
2
 
3
+ import * as toolcraftRuntimeRoot from "../index";
3
4
  import { defineToolcraft } from "../schema/define-toolcraft";
4
5
  import type { ToolcraftState } from "../state/types";
6
+ import type {
7
+ ToolcraftArtifactSize,
8
+ ToolcraftExportFrame,
9
+ ToolcraftPreviewBackgroundOptions,
10
+ ToolcraftProductExportFrameContext,
11
+ ToolcraftProductExportFrameRenderer,
12
+ ToolcraftProductExportRenderer,
13
+ ToolcraftRetinaExportSize,
14
+ } from "../index";
5
15
  import {
6
- shouldIncludeToolcraftExportBackground,
7
16
  shouldIncludeToolcraftPreviewBackground,
8
17
  } from "./export-background";
9
18
  import {
@@ -50,29 +59,41 @@ function createState(schema = defineToolcraft({ canvas: { enabled: true }, panel
50
59
  }
51
60
 
52
61
  describe("Toolcraft export helpers", () => {
53
- it("normalizes png export background to include by default", () => {
54
- const schema = defineToolcraft({
55
- canvas: { enabled: true },
56
- panels: {},
62
+ it("keeps the runtime root limited to product-safe export primitives", () => {
63
+ expect(toolcraftRuntimeRoot).toMatchObject({
64
+ getToolcraftImageExportSize: expect.any(Function),
65
+ getToolcraftRetinaExportPixelRatio: expect.any(Function),
66
+ getToolcraftRetinaExportSize: expect.any(Function),
67
+ getToolcraftVideoExportSize: expect.any(Function),
68
+ shouldIncludeToolcraftPreviewBackground: expect.any(Function),
69
+ TOOLCRAFT_MAX_EXPORT_EDGE_PX: 8192,
70
+ TOOLCRAFT_MAX_EXPORT_PIXELS: 8192 * 8192,
71
+ toolcraftImageExportFormatTarget: "export.image.format",
72
+ toolcraftImageExportResolutionTarget: "export.image.resolution",
73
+ toolcraftVideoExportFormatTarget: "export.video.format",
74
+ toolcraftVideoExportResolutionTarget: "export.video.resolution",
75
+ validateToolcraftArtifactSize: expect.any(Function),
57
76
  });
58
-
59
- expect(schema.export.png.background).toBe("include");
60
- });
61
-
62
- it("preserves transparent png export background option", () => {
63
- const schema = defineToolcraft({
64
- canvas: { enabled: true },
65
- export: {
66
- png: {
67
- background: "transparent",
68
- },
69
- },
70
- panels: {},
71
- });
72
-
73
- expect(schema.export.png.background).toBe("transparent");
74
- expect(shouldIncludeToolcraftExportBackground({ format: "png", schema })).toBe(false);
75
- expect(shouldIncludeToolcraftExportBackground({ format: "video", schema })).toBe(true);
77
+ expect(toolcraftRuntimeRoot).not.toHaveProperty("exportToolcraftImageArtifact");
78
+ expect(toolcraftRuntimeRoot).not.toHaveProperty("exportToolcraftVideoArtifact");
79
+ expect(toolcraftRuntimeRoot).not.toHaveProperty(
80
+ "createToolcraftVideoEncoderBackend",
81
+ );
82
+ expect(toolcraftRuntimeRoot).not.toHaveProperty(
83
+ "createToolcraftVideoFrameSchedule",
84
+ );
85
+
86
+ expectTypeOf<ToolcraftArtifactSize>().toMatchTypeOf<
87
+ Readonly<{ height: number; width: number }>
88
+ >();
89
+ expectTypeOf<ToolcraftExportFrame>().toMatchTypeOf<
90
+ Readonly<{ height: number; width: number; x: number; y: number }>
91
+ >();
92
+ expectTypeOf<ToolcraftPreviewBackgroundOptions>().toHaveProperty("state");
93
+ expectTypeOf<ToolcraftProductExportFrameContext>().toHaveProperty("frame");
94
+ expectTypeOf<ToolcraftProductExportFrameRenderer>().toBeFunction();
95
+ expectTypeOf<ToolcraftProductExportRenderer>().toHaveProperty("renderFrame");
96
+ expectTypeOf<ToolcraftRetinaExportSize>().toHaveProperty("pixelRatio");
76
97
  });
77
98
 
78
99
  it("uses include-background runtime state for live preview background", () => {
@@ -103,24 +124,6 @@ describe("Toolcraft export helpers", () => {
103
124
  expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true);
104
125
  });
105
126
 
106
- it("keeps video export background independent from preview include state", () => {
107
- const schema = defineToolcraft({
108
- canvas: { enabled: true },
109
- export: {
110
- png: {
111
- background: "transparent",
112
- },
113
- },
114
- panels: {},
115
- });
116
- const state = createState(schema);
117
-
118
- state.values["export.includeBackground"] = false;
119
-
120
- expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false);
121
- expect(shouldIncludeToolcraftExportBackground({ format: "video", schema })).toBe(true);
122
- });
123
-
124
127
  it("uses at least 2x pixel ratio for retina export", () => {
125
128
  expect(getToolcraftRetinaExportPixelRatio(1)).toBe(2);
126
129
  expect(getToolcraftRetinaExportPixelRatio(1.5)).toBe(2);
@@ -1,16 +1,44 @@
1
- export * from "./artifact-export-settings";
2
- export * from "./artifact-export-request";
3
- export * from "./artifact-download";
4
- export * from "./artifact-frame-renderer";
5
- export * from "./artifact-scene-frame";
6
- export * from "./artifact-frame-state";
7
- export * from "./export-error";
8
- export * from "./export-background";
9
- export * from "./export-frame";
10
- export * from "./export-sizing";
11
- export * from "./image-artifact-export";
12
- export * from "./product-export-renderer";
13
- export * from "./video-artifact-export";
14
- export * from "./video-encoding-backend";
15
- export * from "./video-encoding-policy";
16
- export * from "./video-frame-schedule";
1
+ export {
2
+ toolcraftImageExportFormatTarget,
3
+ toolcraftImageExportResolutionTarget,
4
+ toolcraftVideoExportFormatTarget,
5
+ toolcraftVideoExportResolutionTarget,
6
+ } from "./artifact-export-settings";
7
+ export type {
8
+ ToolcraftImageExportFormat,
9
+ ToolcraftImageExportPresetResolution,
10
+ ToolcraftResolvedImageExportSettings,
11
+ ToolcraftResolvedVideoExportSettings,
12
+ ToolcraftVideoExportFormat,
13
+ ToolcraftVideoExportPresetResolution,
14
+ } from "./artifact-export-settings";
15
+ export { shouldIncludeToolcraftPreviewBackground } from "./export-background";
16
+ export type { ToolcraftPreviewBackgroundOptions } from "./export-background";
17
+ export {
18
+ TOOLCRAFT_MAX_EXPORT_EDGE_PX,
19
+ TOOLCRAFT_MAX_EXPORT_PIXELS,
20
+ validateToolcraftArtifactSize,
21
+ } from "./export-frame";
22
+ export type {
23
+ ToolcraftArtifactSize,
24
+ ToolcraftExportFrame,
25
+ } from "./export-frame";
26
+ export {
27
+ getToolcraftImageExportSize,
28
+ getToolcraftRetinaExportPixelRatio,
29
+ getToolcraftRetinaExportSize,
30
+ getToolcraftVideoExportSize,
31
+ } from "./export-sizing";
32
+ export type {
33
+ ToolcraftExportSizeOptions,
34
+ ToolcraftImageExportResolution,
35
+ ToolcraftImageExportSizeOptions,
36
+ ToolcraftRetinaExportSize,
37
+ ToolcraftVideoExportResolution,
38
+ ToolcraftVideoExportSizeOptions,
39
+ } from "./export-sizing";
40
+ export type {
41
+ ToolcraftProductExportFrameContext,
42
+ ToolcraftProductExportFrameRenderer,
43
+ ToolcraftProductExportRenderer,
44
+ } from "./product-export-renderer";
@@ -13,6 +13,8 @@ export type {
13
13
  } from "./react/model-rendering/model-render-binding";
14
14
  export * from "./schema/define-toolcraft";
15
15
  export { TOOLCRAFT_CANVAS_RENDER_SCALE } from "./schema/canvas-render-scale";
16
+ export * from "./schema/collection-item-controls";
17
+ export * from "./schema/control-applicability";
16
18
  export * from "./schema/keyframe-capability";
17
19
  export * from "./schema/runtime-targets";
18
20
  export * from "./schema/types";
@@ -1,5 +1,4 @@
1
1
  import type {
2
- ResolvedToolcraftModelFileDropSchema,
3
2
  ToolcraftModelFormat,
4
3
  ToolcraftModelFileDropSchema,
5
4
  ToolcraftModelImportLimits,
@@ -143,9 +142,21 @@ function normalizeTopologyProfile(
143
142
  return normalizedProfile;
144
143
  }
145
144
 
146
- export function normalizeToolcraftModelFileDrop(
147
- control: ToolcraftModelFileDropSchema,
148
- ): ResolvedToolcraftModelFileDropSchema {
145
+ type NormalizedToolcraftModelFileDropSchema<
146
+ TControl extends ToolcraftModelFileDropSchema,
147
+ > = Omit<
148
+ TControl,
149
+ "modelFormats" | "modelLimits" | "multiple" | "topologyProfile"
150
+ > & {
151
+ modelFormats: readonly ToolcraftModelFormat[];
152
+ modelLimits: ToolcraftModelImportLimits;
153
+ multiple: false;
154
+ topologyProfile: ToolcraftModelTopologyProfile;
155
+ };
156
+
157
+ export function normalizeToolcraftModelFileDrop<
158
+ TControl extends ToolcraftModelFileDropSchema,
159
+ >(control: TControl): NormalizedToolcraftModelFileDropSchema<TControl> {
149
160
  const multiple: unknown = control.multiple;
150
161
 
151
162
  if (multiple !== undefined && multiple !== false) {
@@ -9,6 +9,7 @@ import * as ToolcraftReact from "../index";
9
9
  import {
10
10
  isToolcraftLayerVisibleInTree,
11
11
  useToolcraftMediaPresentationUrls,
12
+ useToolcraftProductSceneFrame,
12
13
  } from "../index";
13
14
  import { CanvasShell } from "../canvas/canvas-shell";
14
15
  import { ControlsPanel } from "../controls-panel/controls-panel";
@@ -53,6 +54,10 @@ describe("Toolcraft template runtime public API", () => {
53
54
  isToolcraftLayerVisibleInTree,
54
55
  );
55
56
  expect(isToolcraftLayerVisibleInTree).toEqual(expect.any(Function));
57
+ expect(ToolcraftReact.useToolcraftProductSceneFrame).toBe(
58
+ useToolcraftProductSceneFrame,
59
+ );
60
+ expect(useToolcraftProductSceneFrame).toEqual(expect.any(Function));
56
61
  expect(ToolcraftReact).not.toHaveProperty("ToolcraftStoreContext");
57
62
  expect(ToolcraftReact).not.toHaveProperty("useToolcraftStore");
58
63
  });
@@ -71,6 +76,7 @@ describe("Toolcraft template runtime public API", () => {
71
76
  | "canvasContent"
72
77
  | "controlRenderers"
73
78
  | "exportRenderer"
79
+ | "infiniteCanvasContent"
74
80
  | "modelPresentation"
75
81
  | "onPanelAction"
76
82
  | "renderDefaultCanvasMedia"
@@ -85,6 +91,7 @@ describe("Toolcraft template runtime public API", () => {
85
91
  | "className"
86
92
  | "controlRenderers"
87
93
  | "exportRenderer"
94
+ | "infiniteCanvasContent"
88
95
  | "modelPresentation"
89
96
  | "onPanelAction"
90
97
  | "renderDefaultCanvasMedia"
@@ -153,7 +160,9 @@ describe("Toolcraft template runtime public API", () => {
153
160
  it("keeps shell and panel host props separate", () => {
154
161
  expectTypeOf<
155
162
  keyof React.ComponentProps<typeof CanvasShell>
156
- >().toEqualTypeOf<"children" | "renderDefaultMedia">();
163
+ >().toEqualTypeOf<
164
+ "children" | "infiniteCanvasContent" | "renderDefaultMedia"
165
+ >();
157
166
  expectTypeOf<React.ComponentProps<typeof PanelHost>>().toHaveProperty(
158
167
  "panelType",
159
168
  );
@@ -211,6 +220,7 @@ describe("Toolcraft template runtime public API", () => {
211
220
  };
212
221
  const canvasWithSlotProps: React.ComponentProps<typeof CanvasShell> = {
213
222
  children: <div />,
223
+ infiniteCanvasContent: <div />,
214
224
  renderDefaultMedia: false,
215
225
  };
216
226
  // @ts-expect-error Canvas upload belongs to schema.canvas.upload.
@@ -10,6 +10,35 @@ afterEach(() => {
10
10
  });
11
11
 
12
12
  describe("ToolcraftApp scene export boundary", () => {
13
+ it("applies composition scene bounds to the live infinite product surface", () => {
14
+ const schema = defineToolcraft({
15
+ canvas: {
16
+ enabled: true,
17
+ size: { height: 180, unit: "px", width: 320 },
18
+ sizing: { defaultMode: "infinite", mode: "editable-output" },
19
+ },
20
+ panels: {},
21
+ persistence: { storage: "none" },
22
+ });
23
+ const { container } = render(
24
+ <ToolcraftApp
25
+ canvasContent={<div data-testid="product-scene" />}
26
+ sceneBoundsProvider={() => [
27
+ { height: 240, width: 400, x: -200, y: -120 },
28
+ ]}
29
+ schema={schema}
30
+ />,
31
+ );
32
+ const productScene = container.querySelector<HTMLElement>(
33
+ "[data-toolcraft-product-scene]",
34
+ );
35
+
36
+ expect(productScene?.style.height).toBe("240px");
37
+ expect(productScene?.style.left).toBe("-200px");
38
+ expect(productScene?.style.top).toBe("-120px");
39
+ expect(productScene?.style.width).toBe("400px");
40
+ });
41
+
13
42
  it("fails before mount when a typed product export has no shared renderer", () => {
14
43
  const schema = defineToolcraft({
15
44
  canvas: {
@@ -12,8 +12,9 @@ import type { ToolcraftState } from "../../state/types";
12
12
  import {
13
13
  getToolcraftExportRendererCoverageErrors,
14
14
  type ToolcraftProductExportRenderer,
15
- } from "../../export";
15
+ } from "../../export/product-export-renderer";
16
16
  import { CanvasShell } from "../canvas/canvas-shell";
17
+ import { ToolcraftProductSceneBoundsBoundary } from "../canvas/product-scene-surface";
17
18
  import {
18
19
  ControlsPanel,
19
20
  type ToolcraftPanelActionHandler,
@@ -33,6 +34,7 @@ export type ToolcraftAppComposition = {
33
34
  canvasContent?: React.ReactNode;
34
35
  controlRenderers?: ToolcraftControlRendererMap;
35
36
  exportRenderer?: ToolcraftProductExportRenderer;
37
+ infiniteCanvasContent?: React.ReactNode;
36
38
  modelPresentation?: ToolcraftModelPresentationMode;
37
39
  onPanelAction?: ToolcraftPanelActionHandler;
38
40
  renderDefaultCanvasMedia?: boolean;
@@ -61,6 +63,7 @@ function ToolcraftAppContent({
61
63
  canvasContent,
62
64
  className,
63
65
  controlRenderers,
66
+ infiniteCanvasContent,
64
67
  onPanelAction,
65
68
  renderDefaultCanvasMedia = true,
66
69
  sceneExport,
@@ -100,7 +103,10 @@ function ToolcraftAppContent({
100
103
  }}
101
104
  >
102
105
  {surfaces.canvas.enabled ? (
103
- <CanvasShell renderDefaultMedia={renderDefaultCanvasMedia}>
106
+ <CanvasShell
107
+ infiniteCanvasContent={infiniteCanvasContent}
108
+ renderDefaultMedia={renderDefaultCanvasMedia}
109
+ >
104
110
  {canvasContent}
105
111
  </CanvasShell>
106
112
  ) : null}
@@ -168,12 +174,16 @@ export function ToolcraftApp({
168
174
  rendererPipelineRegistration={rendererPipelineRegistration}
169
175
  schema={schema}
170
176
  >
171
- <ToolcraftAppContent
172
- {...props}
173
- canvasContent={canvasContent}
174
- renderDefaultCanvasMedia={renderDefaultCanvasMedia}
175
- sceneExport={sceneExport}
176
- />
177
+ <ToolcraftProductSceneBoundsBoundary
178
+ boundsProvider={sceneBoundsProvider}
179
+ >
180
+ <ToolcraftAppContent
181
+ {...props}
182
+ canvasContent={canvasContent}
183
+ renderDefaultCanvasMedia={renderDefaultCanvasMedia}
184
+ sceneExport={sceneExport}
185
+ />
186
+ </ToolcraftProductSceneBoundsBoundary>
177
187
  </ToolcraftRoot>
178
188
  );
179
189
  }
@@ -8,12 +8,49 @@ import {
8
8
  createUploadDefaultSchema,
9
9
  renderCanvasShell,
10
10
  } from "./canvas-shell-test-utils";
11
+ import { useToolcraftProductSceneFrame } from "./product-scene-surface";
11
12
 
12
13
  afterEach(() => {
13
14
  cleanup();
14
15
  });
15
16
 
16
17
  describe("CanvasShell rendering", () => {
18
+ it("uses product bounds as the live infinite preview frame", () => {
19
+ function ProductFrameProbe(): React.JSX.Element {
20
+ const frame = useToolcraftProductSceneFrame();
21
+ return <output data-testid="product-frame">{JSON.stringify(frame)}</output>;
22
+ }
23
+
24
+ const { container } = renderCanvasShell({
25
+ children: <ProductFrameProbe />,
26
+ initialState: {
27
+ canvas: {
28
+ mode: "infinite",
29
+ offset: { x: 0, y: 0 },
30
+ size: { height: 200, unit: "px", width: 300 },
31
+ zoom: 100,
32
+ },
33
+ },
34
+ productSceneBoundsProvider: () => [
35
+ { height: 260, width: 420, x: -210, y: -130 },
36
+ ],
37
+ });
38
+ const productScene = container.querySelector<HTMLElement>(
39
+ "[data-toolcraft-product-scene]",
40
+ );
41
+
42
+ expect(productScene?.style.height).toBe("260px");
43
+ expect(productScene?.style.left).toBe("-210px");
44
+ expect(productScene?.style.top).toBe("-130px");
45
+ expect(productScene?.style.width).toBe("420px");
46
+ expect(screen.getByTestId("product-frame").textContent).toBe(
47
+ JSON.stringify({
48
+ kind: "infinite",
49
+ rect: { height: 260, width: 420, x: -210, y: -130 },
50
+ }),
51
+ );
52
+ });
53
+
17
54
  it("renders the editable canvas at the schema size", () => {
18
55
  const { container } = renderCanvasShell();
19
56
 
@@ -59,6 +96,45 @@ describe("CanvasShell rendering", () => {
59
96
  ).toBe("");
60
97
  });
61
98
 
99
+ it("mounts product viewport content only in Infinity behind the transformed world", () => {
100
+ const finite = renderCanvasShell({
101
+ infiniteCanvasContent: <div data-testid="infinite-viewport-content" />,
102
+ });
103
+
104
+ expect(
105
+ finite.container.querySelector("[data-toolcraft-infinite-canvas-content]"),
106
+ ).toBeNull();
107
+ finite.unmount();
108
+
109
+ const { container } = renderCanvasShell({
110
+ children: <div data-testid="bounded-product-content" />,
111
+ infiniteCanvasContent: <div data-testid="infinite-viewport-content" />,
112
+ initialState: {
113
+ canvas: {
114
+ mode: "infinite",
115
+ offset: { x: 42, y: -18 },
116
+ size: { height: 200, unit: "px", width: 300 },
117
+ zoom: 75,
118
+ },
119
+ },
120
+ });
121
+ const backdrop = container.querySelector<HTMLElement>(
122
+ "[data-toolcraft-infinite-canvas-content]",
123
+ );
124
+ const world = container.querySelector<HTMLElement>(
125
+ "[data-toolcraft-canvas-world]",
126
+ );
127
+
128
+ expect(backdrop).toBeTruthy();
129
+ expect(backdrop?.className).toContain("pointer-events-none");
130
+ expect(backdrop?.className).toContain("absolute");
131
+ expect(backdrop?.className).toContain("inset-0");
132
+ expect(backdrop?.style.transform).toBe("");
133
+ expect(backdrop?.nextElementSibling).toBe(world);
134
+ expect(screen.getByTestId("infinite-viewport-content")).toBeTruthy();
135
+ expect(screen.getByTestId("bounded-product-content")).toBeTruthy();
136
+ });
137
+
62
138
  it("fills the complete infinite viewport with the selected Background color", () => {
63
139
  const { container } = renderCanvasShell({
64
140
  initialState: {
@@ -3,10 +3,12 @@ import type * as React from "react";
3
3
  import { vi } from "vitest";
4
4
 
5
5
  import { defineToolcraft } from "../../schema/define-toolcraft";
6
+ import type { ToolcraftProductSceneBoundsProvider } from "../../scene";
6
7
  import type { ToolcraftInitialState } from "../../state/types";
7
8
  import { ToolcraftRoot } from "../app-shell/toolcraft-root";
8
9
  import { useToolcraft } from "../app-shell/use-toolcraft";
9
10
  import { CanvasShell } from "./canvas-shell";
11
+ import { ToolcraftProductSceneBoundsBoundary } from "./product-scene-surface";
10
12
 
11
13
  export function createSchema() {
12
14
  return defineToolcraft({
@@ -121,7 +123,9 @@ type RenderCanvasShellOptions = {
121
123
  afterCanvas?: React.ReactNode;
122
124
  children?: React.ReactNode;
123
125
  frameProps?: React.HTMLAttributes<HTMLDivElement>;
126
+ infiniteCanvasContent?: React.ReactNode;
124
127
  initialState?: ToolcraftInitialState;
128
+ productSceneBoundsProvider?: ToolcraftProductSceneBoundsProvider;
125
129
  schema?: ReturnType<typeof defineToolcraft>;
126
130
  };
127
131
 
@@ -129,7 +133,9 @@ export function renderCanvasShell({
129
133
  afterCanvas,
130
134
  children,
131
135
  frameProps,
136
+ infiniteCanvasContent,
132
137
  initialState,
138
+ productSceneBoundsProvider,
133
139
  schema = createSchema(),
134
140
  }: RenderCanvasShellOptions = {}) {
135
141
  const { style, ...restFrameProps } = frameProps ?? {};
@@ -140,7 +146,13 @@ export function renderCanvasShell({
140
146
  {...restFrameProps}
141
147
  style={{ height: 640, position: "relative", width: 640, ...style }}
142
148
  >
143
- <CanvasShell>{children}</CanvasShell>
149
+ <ToolcraftProductSceneBoundsBoundary
150
+ boundsProvider={productSceneBoundsProvider}
151
+ >
152
+ <CanvasShell infiniteCanvasContent={infiniteCanvasContent}>
153
+ {children}
154
+ </CanvasShell>
155
+ </ToolcraftProductSceneBoundsBoundary>
144
156
  {afterCanvas}
145
157
  </div>
146
158
  </ToolcraftRoot>,