@pixel-point/toolcraft 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate-performance-iteration-execution.test.mjs +2 -2
- package/src/generate-test-demand-only-lifecycle.mjs +14 -3
- package/src/generate-test-dependency-sandbox.mjs +42 -5
- package/src/generate-test-doc-assertions.mjs +68 -18
- package/src/generate-test-doc-assertions.test.mjs +22 -0
- package/src/generate-test-file-assertions.mjs +43 -76
- package/src/generate-test-output-assertions.mjs +95 -0
- package/src/generate-test-package-assertions.mjs +6 -0
- package/src/generate-test-performance-boundary-assertions.mjs +45 -0
- package/src/generate-test-product-fixture.mjs +1 -0
- package/src/generate-test-product-fixture.test.mjs +18 -0
- package/src/generate-test-product-iteration.mjs +1 -0
- package/src/generate-test-product-iteration.test.mjs +1 -0
- package/src/generate-test-standalone-delivery-stages.mjs +14 -6
- package/src/generate.test.mjs +14 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +12 -9
- package/templates/runtime/contracts/component-contracts.choices.ts +10 -9
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +17 -2
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +1 -1
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
- package/templates/runtime/contracts/component-contracts.runtime.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.test.ts +25 -4
- package/templates/runtime/contracts/decision-contracts.ts +3 -3
- package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
- package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
- package/templates/runtime/export/artifact-download.test.ts +71 -0
- package/templates/runtime/export/artifact-download.ts +80 -0
- package/templates/runtime/export/artifact-export-request.ts +23 -0
- package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
- package/templates/runtime/export/artifact-export-settings.ts +96 -0
- package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
- package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
- package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
- package/templates/runtime/export/artifact-frame-state.ts +44 -0
- package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
- package/templates/runtime/export/artifact-scene-frame.ts +118 -0
- package/templates/runtime/export/export-background.ts +65 -0
- package/templates/runtime/export/export-error.ts +43 -0
- package/templates/runtime/export/export.test.ts +6 -186
- package/templates/runtime/export/image-artifact-export.test.ts +111 -0
- package/templates/runtime/export/image-artifact-export.ts +145 -0
- package/templates/runtime/export/index.ts +14 -1
- package/templates/runtime/export/product-export-renderer.test.ts +77 -0
- package/templates/runtime/export/product-export-renderer.ts +63 -0
- package/templates/runtime/export/video-artifact-export.test.ts +166 -0
- package/templates/runtime/export/video-artifact-export.ts +164 -0
- package/templates/runtime/export/video-encoding-backend.ts +124 -0
- package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
- package/templates/runtime/export/video-encoding-policy.ts +86 -0
- package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
- package/templates/runtime/export/video-frame-schedule.ts +42 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
- package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +4 -303
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +12 -27
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +22 -4
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +17 -83
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +42 -84
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +62 -0
- package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
- package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
- package/templates/runtime/scene/scene-bounds.ts +1 -2
- package/templates/runtime/schema/canvas-render-scale.ts +62 -0
- package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +3 -3
- package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
- package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
- package/templates/runtime/schema/define-toolcraft.ts +1 -1
- package/templates/runtime/schema/schema-resolvers.ts +0 -56
- package/templates/runtime/schema/types.ts +4 -4
- package/templates/runtime/state/canvas-background-state.ts +6 -0
- package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
- package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
- package/templates/starter/AGENTS.md +8 -7
- package/templates/starter/NOTICE.md +6 -0
- package/templates/starter/docs/toolcraft/README.md +2 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +8 -6
- package/templates/starter/docs/toolcraft/agent-worklog.md +3 -3
- package/templates/starter/docs/toolcraft/assembly-workflow.md +4 -3
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -2
- package/templates/starter/docs/toolcraft/core/performance.md +14 -3
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +3 -1
- package/templates/starter/docs/toolcraft/core/setup-export.md +16 -12
- package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
- package/templates/starter/docs/toolcraft/performance.md +20 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +2 -6
- package/templates/starter/docs/toolcraft/workflow.md +7 -2
- package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +43 -1
- package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
- package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
- package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
- package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +4 -1
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +0 -1
- package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
- package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
- package/templates/starter/e2e/app-performance.spec.ts +16 -9
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +9 -330
- package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
- package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
- package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
- package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +4 -1
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +43 -9
- package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
- package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
- package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
- package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
- package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
- package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
- package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
- package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
- package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
- package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
- package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
- package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
- package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
- package/templates/starter/e2e/performance-path-helpers.ts +210 -91
- package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
- package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
- package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
- package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
- package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
- package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
- package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +24 -2
- package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +46 -0
- package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
- package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +168 -0
- package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +155 -206
- package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
- package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
- package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
- package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
- package/templates/starter/src/app/acceptance/output-background-rules.ts +1 -1
- package/templates/starter/src/app/acceptance/output-export.ts +13 -2
- package/templates/starter/src/app/acceptance/types.ts +7 -0
- package/templates/starter/src/app/acceptance/validate-coverage.ts +2 -1
- package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
- package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +42 -1
- package/templates/starter/src/app/starter-acceptance.ts +2 -0
- package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
- package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
- package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -0
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/runtime/export/export.ts +0 -166
|
@@ -39,83 +39,6 @@ function createExportSchema(
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
describe("ControlsPanel scene export actions", () => {
|
|
42
|
-
it("skips product bounds for finite export and calls them once for infinite export", () => {
|
|
43
|
-
const schema = createExportSchema();
|
|
44
|
-
const provider = vi.fn(() => [
|
|
45
|
-
{ height: 10, width: 10, x: 100, y: 50 },
|
|
46
|
-
]);
|
|
47
|
-
let finiteFrame: unknown;
|
|
48
|
-
const finite = renderControlsPanelWithSchema(schema, {
|
|
49
|
-
onPanelAction: ({ resolveSceneExportFrame }) => {
|
|
50
|
-
finiteFrame = resolveSceneExportFrame();
|
|
51
|
-
},
|
|
52
|
-
sceneExport: {
|
|
53
|
-
boundsProvider: provider,
|
|
54
|
-
boundsRequired: true,
|
|
55
|
-
visibility: defaultToolcraftRuntimeSceneVisibility,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
fireEvent.click(screen.getByRole("button", { name: "Export PNG" }));
|
|
60
|
-
expect(finiteFrame).toEqual({
|
|
61
|
-
frame: { height: 180, width: 320, x: 0, y: 0 },
|
|
62
|
-
ok: true,
|
|
63
|
-
});
|
|
64
|
-
expect(provider).not.toHaveBeenCalled();
|
|
65
|
-
finite.unmount();
|
|
66
|
-
|
|
67
|
-
let infiniteFrame: unknown;
|
|
68
|
-
renderControlsPanelWithSchema(
|
|
69
|
-
schema,
|
|
70
|
-
{
|
|
71
|
-
onPanelAction: ({ resolveSceneExportFrame }) => {
|
|
72
|
-
infiniteFrame = resolveSceneExportFrame({
|
|
73
|
-
timeRange: { endSeconds: 8, startSeconds: 0 },
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
sceneExport: {
|
|
77
|
-
boundsProvider: provider,
|
|
78
|
-
boundsRequired: true,
|
|
79
|
-
visibility: defaultToolcraftRuntimeSceneVisibility,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
canvas: { mode: "infinite" },
|
|
84
|
-
layers: [
|
|
85
|
-
{
|
|
86
|
-
displayName: "Image",
|
|
87
|
-
id: "image-layer",
|
|
88
|
-
kind: "layer",
|
|
89
|
-
name: "image",
|
|
90
|
-
visible: true,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
mediaAssets: [
|
|
94
|
-
{
|
|
95
|
-
assetKind: "image",
|
|
96
|
-
dataUrl: "data:image/png;base64,test",
|
|
97
|
-
fileName: "image.png",
|
|
98
|
-
id: "image",
|
|
99
|
-
layerId: "image-layer",
|
|
100
|
-
mimeType: "image/png",
|
|
101
|
-
position: { x: 0, y: 0 },
|
|
102
|
-
size: { height: 20, unit: "px", width: 20 },
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
fireEvent.click(screen.getByRole("button", { name: "Export PNG" }));
|
|
109
|
-
expect(provider).toHaveBeenCalledTimes(1);
|
|
110
|
-
expect(provider).toHaveBeenCalledWith(
|
|
111
|
-
expect.objectContaining({ timeRange: { endSeconds: 8, startSeconds: 0 } }),
|
|
112
|
-
);
|
|
113
|
-
expect(infiniteFrame).toEqual({
|
|
114
|
-
frame: { height: 70, width: 120, x: -10, y: -10 },
|
|
115
|
-
ok: true,
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
42
|
it("shows only feedback reported by the latest panel action", async () => {
|
|
120
43
|
const schema = createExportSchema([
|
|
121
44
|
{ label: "First export", value: "first", variant: "secondary" },
|
|
@@ -143,17 +66,27 @@ describe("ControlsPanel scene export actions", () => {
|
|
|
143
66
|
});
|
|
144
67
|
|
|
145
68
|
it("presents a missing required bounds provider as typed visible feedback", async () => {
|
|
69
|
+
const onPanelAction = vi.fn();
|
|
146
70
|
renderControlsPanelWithSchema(
|
|
147
|
-
createExportSchema(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
71
|
+
createExportSchema([
|
|
72
|
+
{
|
|
73
|
+
command: "controls.apply",
|
|
74
|
+
label: "Export PNG",
|
|
75
|
+
role: "export-image",
|
|
76
|
+
value: "export",
|
|
77
|
+
variant: "default",
|
|
152
78
|
},
|
|
79
|
+
]),
|
|
80
|
+
{
|
|
153
81
|
sceneExport: {
|
|
154
|
-
|
|
82
|
+
exportRenderer: {
|
|
83
|
+
baseFileName: "scene",
|
|
84
|
+
renderFrame: vi.fn(),
|
|
85
|
+
},
|
|
86
|
+
productSceneRequired: true,
|
|
155
87
|
visibility: defaultToolcraftRuntimeSceneVisibility,
|
|
156
88
|
},
|
|
89
|
+
onPanelAction,
|
|
157
90
|
},
|
|
158
91
|
{ canvas: { mode: "infinite" } },
|
|
159
92
|
);
|
|
@@ -164,6 +97,7 @@ describe("ControlsPanel scene export actions", () => {
|
|
|
164
97
|
"scene-bounds-unavailable",
|
|
165
98
|
);
|
|
166
99
|
expect(alert.textContent).toContain("does not provide export bounds");
|
|
100
|
+
expect(onPanelAction).not.toHaveBeenCalled();
|
|
167
101
|
});
|
|
168
102
|
|
|
169
103
|
it("presents rejected typed export failures as visible feedback", async () => {
|
|
@@ -3,18 +3,11 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
ToolcraftArtifactExportError,
|
|
7
7
|
ToolcraftSceneExportError,
|
|
8
|
-
type ToolcraftExportFrame,
|
|
9
|
-
type ToolcraftExportFrameResult,
|
|
10
8
|
} from "../../../export";
|
|
11
9
|
import type { ToolcraftRendererPipelineClient } from "../../../rendering";
|
|
12
|
-
import {
|
|
13
|
-
defaultToolcraftRuntimeSceneVisibility,
|
|
14
|
-
resolveToolcraftSceneBounds,
|
|
15
|
-
type ToolcraftProductSceneBoundsProvider,
|
|
16
|
-
type ToolcraftRuntimeSceneVisibility,
|
|
17
|
-
} from "../../../scene";
|
|
10
|
+
import { defaultToolcraftRuntimeSceneVisibility } from "../../../scene";
|
|
18
11
|
import type {
|
|
19
12
|
ToolcraftActionCommand,
|
|
20
13
|
ToolcraftActionSchema,
|
|
@@ -25,46 +18,31 @@ import type {
|
|
|
25
18
|
} from "../../../state/types";
|
|
26
19
|
import { getToolcraftCanvasFrame } from "../../../state/canvas-frame";
|
|
27
20
|
import type { ActionControlRunAction } from "../renderers/controls-panel-action-renderer";
|
|
28
|
-
import {
|
|
29
|
-
renderToolcraftRuntimeSceneToCanvas,
|
|
30
|
-
type ToolcraftRuntimeSceneExportResult,
|
|
31
|
-
} from "../../canvas/runtime-scene-export";
|
|
21
|
+
import { renderToolcraftRuntimeSceneToCanvas } from "../../canvas/runtime-scene-export";
|
|
32
22
|
import { useToolcraftPipeline } from "../../app-shell/use-toolcraft-pipeline";
|
|
33
23
|
import { useToolcraftSourceAssetCoordinator } from "../../app-shell/toolcraft-source-asset-context";
|
|
34
|
-
import {
|
|
35
|
-
renderToolcraftModelsToCanvas,
|
|
36
|
-
type ToolcraftRenderModelsToCanvas,
|
|
37
|
-
} from "../../model-rendering/model-export";
|
|
38
24
|
import { useOptionalToolcraftModelRenderHost } from "../../model-rendering/model-render-provider";
|
|
25
|
+
import {
|
|
26
|
+
runToolcraftExportAction,
|
|
27
|
+
type ToolcraftControlsSceneExport,
|
|
28
|
+
} from "./export-action-runner";
|
|
29
|
+
|
|
30
|
+
export type { ToolcraftControlsSceneExport } from "./export-action-runner";
|
|
39
31
|
|
|
40
32
|
export type ToolcraftPanelActionFeedback = Readonly<{
|
|
41
33
|
code: string;
|
|
42
34
|
message: string;
|
|
43
35
|
}>;
|
|
44
36
|
|
|
45
|
-
export type ToolcraftControlsSceneExport = Readonly<{
|
|
46
|
-
boundsProvider?: ToolcraftProductSceneBoundsProvider;
|
|
47
|
-
boundsRequired: boolean;
|
|
48
|
-
visibility: ToolcraftRuntimeSceneVisibility;
|
|
49
|
-
}>;
|
|
50
|
-
|
|
51
37
|
export type ToolcraftPanelActionContext = {
|
|
52
38
|
action: ToolcraftActionSchema;
|
|
53
39
|
dispatch: React.Dispatch<ToolcraftCommand>;
|
|
54
|
-
renderRuntimeSceneToCanvas: (
|
|
55
|
-
canvas: HTMLCanvasElement,
|
|
56
|
-
frame: ToolcraftExportFrame,
|
|
57
|
-
) => Promise<ToolcraftRuntimeSceneExportResult>;
|
|
58
40
|
reportProgress: (progress: number) => void;
|
|
59
41
|
reportFeedback: (feedback: ToolcraftPanelActionFeedback) => void;
|
|
60
|
-
renderModelsToCanvas: ToolcraftRenderModelsToCanvas;
|
|
61
42
|
resolveMediaResource: (
|
|
62
43
|
resourceRef: string,
|
|
63
44
|
options: Readonly<{ signal: AbortSignal }>,
|
|
64
45
|
) => Promise<Uint8Array | null>;
|
|
65
|
-
resolveSceneExportFrame: (options?: Readonly<{
|
|
66
|
-
timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>;
|
|
67
|
-
}>) => ToolcraftExportFrameResult;
|
|
68
46
|
rendererPipeline?: ToolcraftRendererPipelineClient | null;
|
|
69
47
|
state: ToolcraftState;
|
|
70
48
|
};
|
|
@@ -90,42 +68,10 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
90
68
|
function noopReportProgress(): void {}
|
|
91
69
|
|
|
92
70
|
const defaultSceneExport: ToolcraftControlsSceneExport = Object.freeze({
|
|
93
|
-
|
|
71
|
+
productSceneRequired: false,
|
|
94
72
|
visibility: defaultToolcraftRuntimeSceneVisibility,
|
|
95
73
|
});
|
|
96
74
|
|
|
97
|
-
function unavailableProductBounds(message: string): ToolcraftExportFrameResult {
|
|
98
|
-
return { code: "scene-bounds-unavailable", message, ok: false };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function resolveActionSceneExportFrame(
|
|
102
|
-
sceneExport: ToolcraftControlsSceneExport,
|
|
103
|
-
state: ToolcraftState,
|
|
104
|
-
timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>,
|
|
105
|
-
): ToolcraftExportFrameResult {
|
|
106
|
-
const finiteFrame = resolveToolcraftExportFrame(state, null);
|
|
107
|
-
if (finiteFrame.ok) return finiteFrame;
|
|
108
|
-
|
|
109
|
-
if (sceneExport.boundsRequired && !sceneExport.boundsProvider) {
|
|
110
|
-
return unavailableProductBounds(
|
|
111
|
-
"This infinite product scene does not provide export bounds.",
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let productRects: unknown = [];
|
|
116
|
-
try {
|
|
117
|
-
productRects = sceneExport.boundsProvider?.({ state, timeRange }) ?? [];
|
|
118
|
-
} catch {
|
|
119
|
-
return unavailableProductBounds("Product scene bounds could not be resolved.");
|
|
120
|
-
}
|
|
121
|
-
const bounds = resolveToolcraftSceneBounds(
|
|
122
|
-
state,
|
|
123
|
-
productRects,
|
|
124
|
-
sceneExport.visibility,
|
|
125
|
-
);
|
|
126
|
-
return resolveToolcraftExportFrame(state, bounds);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
75
|
function clampFooterActionProgress(progress: number): number {
|
|
130
76
|
if (!Number.isFinite(progress)) {
|
|
131
77
|
return 0;
|
|
@@ -228,7 +174,10 @@ export function useControlsPanelActions({
|
|
|
228
174
|
|
|
229
175
|
void Promise.resolve(result)
|
|
230
176
|
.catch((error: unknown) => {
|
|
231
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
error instanceof ToolcraftSceneExportError ||
|
|
179
|
+
error instanceof ToolcraftArtifactExportError
|
|
180
|
+
) {
|
|
232
181
|
reportFeedback(error.feedback);
|
|
233
182
|
} else {
|
|
234
183
|
console.error("Toolcraft panel action failed.", error);
|
|
@@ -257,7 +206,11 @@ export function useControlsPanelActions({
|
|
|
257
206
|
setPanelActionFeedback(feedback);
|
|
258
207
|
}
|
|
259
208
|
};
|
|
260
|
-
const
|
|
209
|
+
const runtimeOwnsExport =
|
|
210
|
+
action.role === "export-image" || action.role === "export-video";
|
|
211
|
+
const command = runtimeOwnsExport
|
|
212
|
+
? null
|
|
213
|
+
: action.command ?? (onPanelAction ? null : getActionCommand(action));
|
|
261
214
|
|
|
262
215
|
if (command) {
|
|
263
216
|
dispatch({ type: command });
|
|
@@ -270,36 +223,41 @@ export function useControlsPanelActions({
|
|
|
270
223
|
const actionState = getState();
|
|
271
224
|
let result: PromiseLike<unknown> | void;
|
|
272
225
|
try {
|
|
273
|
-
|
|
226
|
+
const reportProgress =
|
|
227
|
+
footerActionProgressTracker?.reportProgress ?? noopReportProgress;
|
|
228
|
+
const exportResult = runToolcraftExportAction({
|
|
274
229
|
action,
|
|
275
|
-
|
|
276
|
-
renderRuntimeSceneToCanvas: (canvas, frame) =>
|
|
230
|
+
renderRuntimeScene: (canvas, frame, state) =>
|
|
277
231
|
renderToolcraftRuntimeSceneToCanvas({
|
|
278
232
|
canvas,
|
|
279
|
-
canvasFrame: getToolcraftCanvasFrame(
|
|
233
|
+
canvasFrame: getToolcraftCanvasFrame(state.canvas),
|
|
280
234
|
exportFrame: frame,
|
|
281
235
|
host: modelRenderHost,
|
|
282
236
|
resolveImageResource: sourceAssetCoordinator.resolveResource,
|
|
283
|
-
state
|
|
237
|
+
state,
|
|
284
238
|
visibility: sceneExport.visibility,
|
|
285
239
|
}),
|
|
286
|
-
reportFeedback,
|
|
287
|
-
reportProgress:
|
|
288
|
-
footerActionProgressTracker?.reportProgress ?? noopReportProgress,
|
|
289
|
-
renderModelsToCanvas: (canvas) =>
|
|
290
|
-
renderToolcraftModelsToCanvas(modelRenderHost, actionState, canvas),
|
|
291
|
-
resolveMediaResource,
|
|
292
|
-
resolveSceneExportFrame: (resolveOptions) =>
|
|
293
|
-
resolveActionSceneExportFrame(
|
|
294
|
-
sceneExport,
|
|
295
|
-
actionState,
|
|
296
|
-
resolveOptions?.timeRange,
|
|
297
|
-
),
|
|
298
240
|
rendererPipeline,
|
|
241
|
+
reportProgress,
|
|
242
|
+
sceneExport,
|
|
299
243
|
state: actionState,
|
|
300
244
|
});
|
|
245
|
+
result =
|
|
246
|
+
exportResult ??
|
|
247
|
+
onPanelAction?.({
|
|
248
|
+
action,
|
|
249
|
+
dispatch,
|
|
250
|
+
reportFeedback,
|
|
251
|
+
reportProgress,
|
|
252
|
+
resolveMediaResource,
|
|
253
|
+
rendererPipeline,
|
|
254
|
+
state: actionState,
|
|
255
|
+
});
|
|
301
256
|
} catch (error) {
|
|
302
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
error instanceof ToolcraftSceneExportError ||
|
|
259
|
+
error instanceof ToolcraftArtifactExportError
|
|
260
|
+
) {
|
|
303
261
|
reportFeedback(error.feedback);
|
|
304
262
|
result = undefined;
|
|
305
263
|
} else {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
exportToolcraftImageArtifact,
|
|
3
|
+
exportToolcraftVideoArtifact,
|
|
4
|
+
type ToolcraftArtifactExportRequest,
|
|
5
|
+
type ToolcraftExportFrame,
|
|
6
|
+
type ToolcraftProductExportRenderer,
|
|
7
|
+
} from "../../../export";
|
|
8
|
+
import type { ToolcraftRendererPipelineClient } from "../../../rendering";
|
|
9
|
+
import type {
|
|
10
|
+
ToolcraftProductSceneBoundsProvider,
|
|
11
|
+
ToolcraftRuntimeSceneVisibility,
|
|
12
|
+
} from "../../../scene";
|
|
13
|
+
import type { ToolcraftActionSchema } from "../../../schema/types";
|
|
14
|
+
import type { ToolcraftState } from "../../../state/types";
|
|
15
|
+
|
|
16
|
+
export type ToolcraftControlsSceneExport = Readonly<{
|
|
17
|
+
boundsProvider?: ToolcraftProductSceneBoundsProvider;
|
|
18
|
+
exportRenderer?: ToolcraftProductExportRenderer;
|
|
19
|
+
productSceneRequired: boolean;
|
|
20
|
+
visibility: ToolcraftRuntimeSceneVisibility;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
export type ToolcraftExportActionRequest = Readonly<{
|
|
24
|
+
action: ToolcraftActionSchema;
|
|
25
|
+
renderRuntimeScene: (
|
|
26
|
+
canvas: HTMLCanvasElement,
|
|
27
|
+
frame: ToolcraftExportFrame,
|
|
28
|
+
state: ToolcraftState,
|
|
29
|
+
) => Promise<unknown>;
|
|
30
|
+
rendererPipeline: ToolcraftRendererPipelineClient | null;
|
|
31
|
+
reportProgress: (progress: number) => void;
|
|
32
|
+
sceneExport: ToolcraftControlsSceneExport;
|
|
33
|
+
state: ToolcraftState;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
function createSharedArtifactRequest(
|
|
37
|
+
request: ToolcraftExportActionRequest,
|
|
38
|
+
): ToolcraftArtifactExportRequest {
|
|
39
|
+
return {
|
|
40
|
+
boundsProvider: request.sceneExport.boundsProvider,
|
|
41
|
+
exportRenderer: request.sceneExport.exportRenderer,
|
|
42
|
+
productSceneRequired: request.sceneExport.productSceneRequired,
|
|
43
|
+
renderRuntimeScene: request.renderRuntimeScene,
|
|
44
|
+
rendererPipeline: request.rendererPipeline,
|
|
45
|
+
reportProgress: request.reportProgress,
|
|
46
|
+
state: request.state,
|
|
47
|
+
visibility: request.sceneExport.visibility,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function runToolcraftExportAction(
|
|
52
|
+
request: ToolcraftExportActionRequest,
|
|
53
|
+
): PromiseLike<unknown> | null {
|
|
54
|
+
switch (request.action.role) {
|
|
55
|
+
case "export-image":
|
|
56
|
+
return exportToolcraftImageArtifact(createSharedArtifactRequest(request));
|
|
57
|
+
case "export-video":
|
|
58
|
+
return exportToolcraftVideoArtifact(createSharedArtifactRequest(request));
|
|
59
|
+
default:
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -195,4 +195,34 @@ describe("ToolcraftPanelHost runtime placement", () => {
|
|
|
195
195
|
);
|
|
196
196
|
});
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
it("keeps the CSS-anchored default placement during viewport resize", async () => {
|
|
200
|
+
const { container } = renderToolbar();
|
|
201
|
+
const host = container.querySelector<HTMLElement>(
|
|
202
|
+
"[data-slot='toolcraft-runtime-panel-host']",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (!host) {
|
|
206
|
+
throw new Error("Expected the toolbar panel host to render.");
|
|
207
|
+
}
|
|
208
|
+
vi.spyOn(host, "getBoundingClientRect").mockReturnValue({
|
|
209
|
+
bottom: 900,
|
|
210
|
+
height: 100,
|
|
211
|
+
left: 1_500,
|
|
212
|
+
right: 1_700,
|
|
213
|
+
top: 800,
|
|
214
|
+
width: 200,
|
|
215
|
+
x: 1_500,
|
|
216
|
+
y: 800,
|
|
217
|
+
toJSON: () => ({}),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
fireEvent(window, new Event("resize"));
|
|
221
|
+
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(screen.getByTestId("toolbar-placement").textContent).toBe(
|
|
224
|
+
"0,0,free",
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
198
228
|
});
|
|
@@ -191,6 +191,10 @@ function usePanelSnapControls({
|
|
|
191
191
|
return;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
if (x.get() === 0 && y.get() === 0 && snapEdge === undefined) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
194
198
|
const currentPlacement = {
|
|
195
199
|
offset: { x: x.get(), y: y.get() },
|
|
196
200
|
...(snapEdge ? { snapEdge } : {}),
|
|
@@ -36,8 +36,7 @@ export function createToolcraftRuntimeSceneVisibility(
|
|
|
36
36
|
|
|
37
37
|
export type ToolcraftProductSceneBoundsProvider = (
|
|
38
38
|
context: Readonly<{
|
|
39
|
-
state: ToolcraftState
|
|
40
|
-
timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>;
|
|
39
|
+
state: Readonly<ToolcraftState>;
|
|
41
40
|
}>,
|
|
42
41
|
) => readonly ToolcraftSceneRect[];
|
|
43
42
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResolvedToolcraftCanvasRenderScaleSchema,
|
|
3
|
+
ToolcraftCanvasRenderScaleSchema,
|
|
4
|
+
} from "./types";
|
|
5
|
+
|
|
6
|
+
export const TOOLCRAFT_CANVAS_RENDER_SCALE = Object.freeze({
|
|
7
|
+
defaultValue: 2,
|
|
8
|
+
max: 2,
|
|
9
|
+
min: 1,
|
|
10
|
+
step: 0.25,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const minimumCanvasRenderScaleStep = 0.01;
|
|
14
|
+
const canvasRenderScaleStepTolerance = 1e-9;
|
|
15
|
+
|
|
16
|
+
function resolveCanvasRenderScaleStep(value: number | undefined): number {
|
|
17
|
+
if (value === undefined) {
|
|
18
|
+
return TOOLCRAFT_CANVAS_RENDER_SCALE.step;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const maximumCanvasRenderScaleStep =
|
|
22
|
+
TOOLCRAFT_CANVAS_RENDER_SCALE.max - TOOLCRAFT_CANVAS_RENDER_SCALE.min;
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
typeof value !== "number" ||
|
|
26
|
+
!Number.isFinite(value) ||
|
|
27
|
+
value < minimumCanvasRenderScaleStep ||
|
|
28
|
+
value > maximumCanvasRenderScaleStep
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Canvas render scale step must be a finite value between 0.01 and 1.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const stepCount = maximumCanvasRenderScaleStep / value;
|
|
36
|
+
|
|
37
|
+
if (Math.abs(stepCount - Math.round(stepCount)) > canvasRenderScaleStepTolerance) {
|
|
38
|
+
throw new Error("Canvas render scale step must divide the canonical range exactly.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveToolcraftCanvasRenderScale(
|
|
45
|
+
renderScale: ToolcraftCanvasRenderScaleSchema | undefined,
|
|
46
|
+
): ResolvedToolcraftCanvasRenderScaleSchema {
|
|
47
|
+
if (!renderScale) {
|
|
48
|
+
return {
|
|
49
|
+
...TOOLCRAFT_CANVAS_RENDER_SCALE,
|
|
50
|
+
enabled: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...TOOLCRAFT_CANVAS_RENDER_SCALE,
|
|
56
|
+
enabled: true,
|
|
57
|
+
step:
|
|
58
|
+
renderScale === true
|
|
59
|
+
? TOOLCRAFT_CANVAS_RENDER_SCALE.step
|
|
60
|
+
: resolveCanvasRenderScaleStep(renderScale.step),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -140,7 +140,7 @@ describe("Toolcraft control section identity", () => {
|
|
|
140
140
|
)?.id,
|
|
141
141
|
).toBe(legacyId);
|
|
142
142
|
expect(
|
|
143
|
-
defineToolcraft(resolved as ToolcraftAppSchema).panels.controls?.sections.find(
|
|
143
|
+
defineToolcraft(resolved as unknown as ToolcraftAppSchema).panels.controls?.sections.find(
|
|
144
144
|
(section) => section.id.startsWith("legacy."),
|
|
145
145
|
)?.id,
|
|
146
146
|
).toBe(legacyId);
|
|
@@ -38,7 +38,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
41
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
42
42
|
const resolvedIds = resolved.panels.controls?.sections.map((section) => section.id);
|
|
43
43
|
const roundTrippedIds = roundTripped.panels.controls?.sections.map(
|
|
44
44
|
(section) => section.id,
|
|
@@ -101,7 +101,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
101
101
|
const productIds = resolved.panels.controls?.sections
|
|
102
102
|
.map((section) => section.id)
|
|
103
103
|
.filter((id) => !id.startsWith("runtime."));
|
|
104
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
104
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
105
105
|
const roundTrippedProductIds = roundTripped.panels.controls?.sections
|
|
106
106
|
.map((section) => section.id)
|
|
107
107
|
.filter((id) => !id.startsWith("runtime."));
|
|
@@ -147,7 +147,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
147
147
|
productIds?.every((id) => (id.match(/\.part-/gu) ?? []).length === 2),
|
|
148
148
|
).toBe(true);
|
|
149
149
|
|
|
150
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
150
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
151
151
|
const roundTrippedProductIds = roundTripped.panels.controls?.sections
|
|
152
152
|
.map((section) => section.id)
|
|
153
153
|
.filter((id) => !id.startsWith("runtime."));
|
|
@@ -118,7 +118,7 @@ describe("defineToolcraft runtime setup background controls", () => {
|
|
|
118
118
|
|
|
119
119
|
it("keeps the normalized background pair when a resolved schema is defined again", () => {
|
|
120
120
|
const first = defineToolcraft(createBackgroundSchema());
|
|
121
|
-
const second = defineToolcraft(first);
|
|
121
|
+
const second = defineToolcraft(first as unknown as ToolcraftAppSchema);
|
|
122
122
|
const setupSection = second.panels.controls?.sections[0];
|
|
123
123
|
|
|
124
124
|
expect(second.panels.controls?.sections.map((section) => section.title)).toEqual([
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { defineToolcraft } from "./define-toolcraft";
|
|
4
|
+
import type { ToolcraftCanvasRenderScaleSchema } from "./types";
|
|
5
|
+
|
|
6
|
+
const authoredRenderScaleStep: ToolcraftCanvasRenderScaleSchema = { step: 0.5 };
|
|
7
|
+
const authoredRenderScaleDefaultValue = { defaultValue: 1, step: 0.5 };
|
|
8
|
+
const authoredRenderScaleEnabled = { enabled: true, step: 0.5 };
|
|
9
|
+
const authoredRenderScaleMax = { max: 1, step: 0.5 };
|
|
10
|
+
const authoredRenderScaleMin = { min: 1, step: 0.5 };
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error Authored render scale cannot select its resolved default value.
|
|
13
|
+
const authoredRenderScaleDefaultValueAssignment: ToolcraftCanvasRenderScaleSchema =
|
|
14
|
+
authoredRenderScaleDefaultValue;
|
|
15
|
+
|
|
16
|
+
// @ts-expect-error Authored render scale is always enabled when provided.
|
|
17
|
+
const authoredRenderScaleEnabledAssignment: ToolcraftCanvasRenderScaleSchema = {
|
|
18
|
+
...authoredRenderScaleEnabled,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// @ts-expect-error Authored render scale cannot change the canonical maximum.
|
|
22
|
+
const authoredRenderScaleMaxAssignment: ToolcraftCanvasRenderScaleSchema = authoredRenderScaleMax;
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error Authored render scale cannot change the canonical minimum.
|
|
25
|
+
const authoredRenderScaleMinAssignment: ToolcraftCanvasRenderScaleSchema = {
|
|
26
|
+
...authoredRenderScaleMin,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
void authoredRenderScaleStep;
|
|
30
|
+
void authoredRenderScaleDefaultValue;
|
|
31
|
+
void authoredRenderScaleEnabled;
|
|
32
|
+
void authoredRenderScaleMax;
|
|
33
|
+
void authoredRenderScaleMin;
|
|
34
|
+
void authoredRenderScaleDefaultValueAssignment;
|
|
35
|
+
void authoredRenderScaleEnabledAssignment;
|
|
36
|
+
void authoredRenderScaleMaxAssignment;
|
|
37
|
+
void authoredRenderScaleMinAssignment;
|
|
4
38
|
|
|
5
39
|
describe("defineToolcraft runtime setup render scale and timeline controls", () => {
|
|
6
40
|
it("appends raster render scale to the technical setup controls when enabled", () => {
|
|
@@ -95,11 +129,11 @@ describe("defineToolcraft runtime setup render scale and timeline controls", ()
|
|
|
95
129
|
]);
|
|
96
130
|
});
|
|
97
131
|
|
|
98
|
-
it("
|
|
132
|
+
it("uses the canonical authored raster scale with a custom step", () => {
|
|
99
133
|
const app = defineToolcraft({
|
|
100
134
|
canvas: {
|
|
101
135
|
enabled: true,
|
|
102
|
-
renderScale: {
|
|
136
|
+
renderScale: { step: 0.5 },
|
|
103
137
|
size: { height: 900, unit: "px", width: 1440 },
|
|
104
138
|
sizing: { mode: "fixed-output" },
|
|
105
139
|
},
|
|
@@ -120,10 +154,20 @@ describe("defineToolcraft runtime setup render scale and timeline controls", ()
|
|
|
120
154
|
"timelineExtended",
|
|
121
155
|
]);
|
|
122
156
|
expect(setupSection?.controls.canvasRenderScale).toMatchObject({
|
|
123
|
-
defaultValue:
|
|
157
|
+
defaultValue: 2,
|
|
158
|
+
max: 2,
|
|
159
|
+
min: 1,
|
|
160
|
+
step: 0.5,
|
|
124
161
|
target: "canvas.renderScale",
|
|
125
162
|
type: "slider",
|
|
126
163
|
});
|
|
164
|
+
expect(app.canvas.renderScale).toEqual({
|
|
165
|
+
defaultValue: 2,
|
|
166
|
+
enabled: true,
|
|
167
|
+
max: 2,
|
|
168
|
+
min: 1,
|
|
169
|
+
step: 0.5,
|
|
170
|
+
});
|
|
127
171
|
expect(setupSection?.controls.timelineExtended).toMatchObject({
|
|
128
172
|
defaultValue: false,
|
|
129
173
|
label: "Timeline",
|
|
@@ -133,4 +177,28 @@ describe("defineToolcraft runtime setup render scale and timeline controls", ()
|
|
|
133
177
|
expect(setupSection?.controls.timelineExtended).not.toHaveProperty("description");
|
|
134
178
|
expect(setupSection?.layoutGroups ?? []).toEqual([]);
|
|
135
179
|
});
|
|
180
|
+
|
|
181
|
+
it("rejects an authored step that cannot reach the canonical maximum", () => {
|
|
182
|
+
expect(() =>
|
|
183
|
+
defineToolcraft({
|
|
184
|
+
canvas: {
|
|
185
|
+
enabled: true,
|
|
186
|
+
renderScale: { step: 0.3 },
|
|
187
|
+
},
|
|
188
|
+
panels: {},
|
|
189
|
+
}),
|
|
190
|
+
).toThrow("Canvas render scale step must divide the canonical range exactly.");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it.each([0, Number.NaN])("rejects invalid authored step %s", (step) => {
|
|
194
|
+
expect(() =>
|
|
195
|
+
defineToolcraft({
|
|
196
|
+
canvas: {
|
|
197
|
+
enabled: true,
|
|
198
|
+
renderScale: { step },
|
|
199
|
+
},
|
|
200
|
+
panels: {},
|
|
201
|
+
}),
|
|
202
|
+
).toThrow("Canvas render scale step must be a finite value between 0.01 and 1.");
|
|
203
|
+
});
|
|
136
204
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createToolcraftAssemblyContract } from "./assembly-contract";
|
|
2
2
|
import { resolveToolcraftAppCapabilities } from "./app-capabilities";
|
|
3
3
|
import { resolveToolcraftAppIdentity } from "./app-identity";
|
|
4
|
+
import { resolveToolcraftCanvasRenderScale } from "./canvas-render-scale";
|
|
4
5
|
import { normalizeToolcraftPanels } from "./panels-schema-normalization";
|
|
5
6
|
import { resolveToolcraftPersistencePlan } from "./persistence-plan";
|
|
6
7
|
import { resolveToolcraftSettingsTransfer } from "./runtime-setup-section";
|
|
7
8
|
import {
|
|
8
9
|
defaultToolcraftCanvasSize,
|
|
9
|
-
resolveToolcraftCanvasRenderScale,
|
|
10
10
|
resolveToolcraftCanvasSizing,
|
|
11
11
|
resolveToolcraftExport,
|
|
12
12
|
resolveToolcraftMedia,
|