@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.
- package/package.json +1 -1
- package/src/generate-test-doc-assertions.mjs +58 -68
- package/src/generate-test-doc-assertions.test.mjs +43 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +26 -75
- package/src/generate-test-product-iteration.test.mjs +17 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
- package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/export/export-background.ts +0 -19
- package/templates/runtime/export/export.test.ts +45 -42
- package/templates/runtime/export/index.ts +44 -16
- package/templates/runtime/index.ts +2 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +31 -17
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/types.ts +68 -13
- package/templates/starter/AGENTS.md +12 -10
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
- package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
- package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +22 -10
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +23 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +11 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- 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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
218
|
+
"Product artifact delivery must match the required productReadiness.exportIntent declaration.",
|
|
219
219
|
desiredBehavior:
|
|
220
|
-
'
|
|
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
|
|
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
|
|
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,
|
|
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("
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
export
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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<
|
|
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
|
|
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
|
-
<
|
|
172
|
-
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
<
|
|
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>,
|