@pixel-point/toolcraft 0.0.8 → 0.0.11
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/README.md +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +42 -5
- package/src/generate.test.mjs +40 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +251 -47
- package/templates/runtime/contracts/component-contracts.ts +130 -57
- package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
- package/templates/runtime/contracts/decision-contracts.ts +22 -9
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +916 -108
- package/templates/runtime/react/controls-panel.tsx +136 -30
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
- package/templates/runtime/schema/define-toolcraft.ts +140 -246
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +44 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +810 -21
- package/templates/runtime/testing/performance.ts +823 -53
- package/templates/starter/AGENTS.md +24 -18
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
- package/templates/starter/docs/toolcraft/component-rules.md +75 -42
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +37 -11
- package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
- package/templates/starter/scripts/toolcraft-port.mjs +280 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
- package/templates/starter/src/app/starter-acceptance.ts +1453 -97
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +8 -25
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
- package/templates/ui/components/panel/panel-section.tsx +29 -5
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { toolcraftRuntimeCommandTypes } from "../state/types";
|
|
4
|
-
import {
|
|
5
|
-
defineToolcraft,
|
|
6
|
-
getToolcraftSettingsTransferEligibility,
|
|
7
|
-
} from "./define-toolcraft";
|
|
4
|
+
import { defineToolcraft } from "./define-toolcraft";
|
|
8
5
|
import {
|
|
9
6
|
toolcraftReservedTargets,
|
|
10
7
|
toolcraftRuntimeOwnedTargets,
|
|
@@ -27,17 +24,8 @@ describe("defineToolcraft", () => {
|
|
|
27
24
|
);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
function
|
|
31
|
-
return
|
|
32
|
-
controls: {
|
|
33
|
-
opacity: {
|
|
34
|
-
defaultValue: index,
|
|
35
|
-
target: `section${index}.opacity`,
|
|
36
|
-
type: "slider",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
title: `Section ${index + 1}`,
|
|
40
|
-
}));
|
|
27
|
+
function getProductSections(app: ReturnType<typeof defineToolcraft>) {
|
|
28
|
+
return app.panels.controls?.sections.filter((section) => section.title !== "Setup") ?? [];
|
|
41
29
|
}
|
|
42
30
|
|
|
43
31
|
it("defaults enabled canvas to draggable with canvas toolbar features", () => {
|
|
@@ -53,6 +41,30 @@ describe("defineToolcraft", () => {
|
|
|
53
41
|
expect(app.toolbar).toEqual({ history: true, radar: true, theme: true, zoom: true });
|
|
54
42
|
});
|
|
55
43
|
|
|
44
|
+
it("defaults upload canvases to editable output unless intrinsic media is explicit", () => {
|
|
45
|
+
const app = defineToolcraft({
|
|
46
|
+
canvas: { enabled: true, upload: true },
|
|
47
|
+
panels: {},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(app.canvas.size).toEqual({ height: 1080, unit: "px", width: 1920 });
|
|
51
|
+
expect(app.canvas.sizeSource).toBe("runtime-default");
|
|
52
|
+
expect(app.canvas.sizing).toEqual({ mode: "editable-output" });
|
|
53
|
+
expect(app.assembly.capabilities).toContain("canvas.editableSize");
|
|
54
|
+
expect(app.assembly.commands).toContain("canvas.setSize");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("preserves explicit intrinsic media sizing for source-native upload apps", () => {
|
|
58
|
+
const app = defineToolcraft({
|
|
59
|
+
canvas: { enabled: true, sizing: { mode: "intrinsic-media" }, upload: true },
|
|
60
|
+
panels: {},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(app.canvas.sizing).toEqual({ mode: "intrinsic-media" });
|
|
64
|
+
expect(app.assembly.capabilities).not.toContain("canvas.editableSize");
|
|
65
|
+
expect(app.assembly.commands).not.toContain("canvas.setSize");
|
|
66
|
+
});
|
|
67
|
+
|
|
56
68
|
it("disables app state persistence by default", () => {
|
|
57
69
|
const app = defineToolcraft({
|
|
58
70
|
canvas: { enabled: true },
|
|
@@ -82,7 +94,7 @@ describe("defineToolcraft", () => {
|
|
|
82
94
|
});
|
|
83
95
|
});
|
|
84
96
|
|
|
85
|
-
it("keeps settings transfer
|
|
97
|
+
it("keeps settings transfer visible for small auto schemas", () => {
|
|
86
98
|
const app = defineToolcraft({
|
|
87
99
|
canvas: { enabled: true, size: { height: 320, unit: "px", width: 320 } },
|
|
88
100
|
panels: {
|
|
@@ -107,11 +119,15 @@ describe("defineToolcraft", () => {
|
|
|
107
119
|
|
|
108
120
|
expect(app.settingsTransfer).toMatchObject({
|
|
109
121
|
appId: "mini-app",
|
|
110
|
-
enabled:
|
|
122
|
+
enabled: true,
|
|
111
123
|
fileName: "mini-app-settings.json",
|
|
112
124
|
mode: "auto",
|
|
113
125
|
});
|
|
114
126
|
expect(app.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
127
|
+
expect(app.panels.controls?.sections[0]?.controls.settingsTransfer).toMatchObject({
|
|
128
|
+
target: "runtime.settingsTransfer",
|
|
129
|
+
type: "settingsTransfer",
|
|
130
|
+
});
|
|
115
131
|
expect(app.panels.controls?.sections[0]?.controls.canvasAspectRatio).toMatchObject({
|
|
116
132
|
target: "canvas.aspectRatio",
|
|
117
133
|
type: "aspectRatio",
|
|
@@ -119,124 +135,7 @@ describe("defineToolcraft", () => {
|
|
|
119
135
|
expect(app.panels.controls?.sections[0]?.controls.canvasWidth).toBeTruthy();
|
|
120
136
|
});
|
|
121
137
|
|
|
122
|
-
it("
|
|
123
|
-
const small = getToolcraftSettingsTransferEligibility({
|
|
124
|
-
panels: {
|
|
125
|
-
controls: {
|
|
126
|
-
sections: [
|
|
127
|
-
{
|
|
128
|
-
controls: createSliderControls(1),
|
|
129
|
-
title: "Small",
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
title: "Small App",
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
const manyControls = getToolcraftSettingsTransferEligibility({
|
|
137
|
-
panels: {
|
|
138
|
-
controls: {
|
|
139
|
-
sections: [
|
|
140
|
-
{
|
|
141
|
-
controls: createSliderControls(12),
|
|
142
|
-
title: "Many Controls",
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
title: "Many Controls App",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
const manySections = getToolcraftSettingsTransferEligibility({
|
|
150
|
-
panels: {
|
|
151
|
-
controls: {
|
|
152
|
-
sections: createSections(5),
|
|
153
|
-
title: "Many Sections App",
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
const heavyControls = getToolcraftSettingsTransferEligibility({
|
|
158
|
-
panels: {
|
|
159
|
-
controls: {
|
|
160
|
-
sections: [
|
|
161
|
-
{
|
|
162
|
-
controls: Object.fromEntries(
|
|
163
|
-
Array.from({ length: 6 }, (_, index) => [
|
|
164
|
-
`gradient${index}`,
|
|
165
|
-
{
|
|
166
|
-
label: `Gradient ${index + 1}`,
|
|
167
|
-
target: `gradient${index}`,
|
|
168
|
-
type: "gradient",
|
|
169
|
-
},
|
|
170
|
-
]),
|
|
171
|
-
),
|
|
172
|
-
title: "Gradients",
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
title: "Heavy Controls App",
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
const timelineLayersScore = getToolcraftSettingsTransferEligibility({
|
|
180
|
-
panels: {
|
|
181
|
-
controls: {
|
|
182
|
-
sections: [
|
|
183
|
-
{
|
|
184
|
-
controls: Object.fromEntries(
|
|
185
|
-
Array.from({ length: 5 }, (_, index) => [
|
|
186
|
-
`gradient${index}`,
|
|
187
|
-
{
|
|
188
|
-
label: `Gradient ${index + 1}`,
|
|
189
|
-
target: `gradient${index}`,
|
|
190
|
-
type: "gradient",
|
|
191
|
-
},
|
|
192
|
-
]),
|
|
193
|
-
),
|
|
194
|
-
title: "Gradients",
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
title: "Timeline Layers App",
|
|
198
|
-
},
|
|
199
|
-
layers: true,
|
|
200
|
-
timeline: { mode: "playback" },
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
expect(small).toEqual({
|
|
205
|
-
controlCount: 1,
|
|
206
|
-
eligible: false,
|
|
207
|
-
reasons: [],
|
|
208
|
-
score: 1,
|
|
209
|
-
sectionCount: 1,
|
|
210
|
-
});
|
|
211
|
-
expect(manyControls).toMatchObject({
|
|
212
|
-
controlCount: 12,
|
|
213
|
-
eligible: true,
|
|
214
|
-
reasons: ["control-count"],
|
|
215
|
-
sectionCount: 1,
|
|
216
|
-
});
|
|
217
|
-
expect(manySections).toMatchObject({
|
|
218
|
-
controlCount: 5,
|
|
219
|
-
eligible: true,
|
|
220
|
-
reasons: ["section-count"],
|
|
221
|
-
sectionCount: 5,
|
|
222
|
-
});
|
|
223
|
-
expect(heavyControls).toMatchObject({
|
|
224
|
-
controlCount: 6,
|
|
225
|
-
eligible: true,
|
|
226
|
-
reasons: ["score"],
|
|
227
|
-
score: 18,
|
|
228
|
-
sectionCount: 1,
|
|
229
|
-
});
|
|
230
|
-
expect(timelineLayersScore).toMatchObject({
|
|
231
|
-
controlCount: 5,
|
|
232
|
-
eligible: true,
|
|
233
|
-
reasons: ["score"],
|
|
234
|
-
score: 19,
|
|
235
|
-
sectionCount: 1,
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("ignores runtime canvas setup controls in settings transfer eligibility", () => {
|
|
138
|
+
it("keeps runtime setup controls outside product sections", () => {
|
|
240
139
|
const app = defineToolcraft({
|
|
241
140
|
canvas: { enabled: true, size: { height: 720, unit: "px", width: 1280 } },
|
|
242
141
|
panels: {
|
|
@@ -253,16 +152,16 @@ describe("defineToolcraft", () => {
|
|
|
253
152
|
settingsTransfer: false,
|
|
254
153
|
});
|
|
255
154
|
|
|
155
|
+
expect(app.settingsTransfer.enabled).toBe(true);
|
|
156
|
+
expect(app.panels.controls?.sections[0]?.controls.settingsTransfer).toMatchObject({
|
|
157
|
+
target: "runtime.settingsTransfer",
|
|
158
|
+
type: "settingsTransfer",
|
|
159
|
+
});
|
|
256
160
|
expect(app.panels.controls?.sections[0]?.controls.canvasAspectRatio).toBeTruthy();
|
|
257
161
|
expect(app.panels.controls?.sections[0]?.controls.canvasWidth).toBeTruthy();
|
|
258
162
|
expect(app.panels.controls?.sections[0]?.controls.canvasHeight).toBeTruthy();
|
|
259
|
-
expect(
|
|
260
|
-
|
|
261
|
-
eligible: false,
|
|
262
|
-
reasons: [],
|
|
263
|
-
score: 10,
|
|
264
|
-
sectionCount: 1,
|
|
265
|
-
});
|
|
163
|
+
expect(getProductSections(app)).toHaveLength(1);
|
|
164
|
+
expect(getProductSections(app)[0]?.title).toBe("Product Controls");
|
|
266
165
|
});
|
|
267
166
|
|
|
268
167
|
it("injects settings transfer as the first section for complex auto schemas", () => {
|
|
@@ -335,6 +234,7 @@ describe("defineToolcraft", () => {
|
|
|
335
234
|
"canvasAspectRatio",
|
|
336
235
|
"canvasWidth",
|
|
337
236
|
"canvasHeight",
|
|
237
|
+
"timelineExtended",
|
|
338
238
|
]);
|
|
339
239
|
expect(runtimeSettingsSection?.controls.canvasAspectRatio).toMatchObject({
|
|
340
240
|
defaultValue: {
|
|
@@ -362,7 +262,7 @@ describe("defineToolcraft", () => {
|
|
|
362
262
|
expect(firstProductSection?.title).toBe("Text");
|
|
363
263
|
});
|
|
364
264
|
|
|
365
|
-
it("
|
|
265
|
+
it("uses explicit settings transfer naming while keeping the setup controls mandatory", () => {
|
|
366
266
|
const forced = defineToolcraft({
|
|
367
267
|
canvas: { enabled: false },
|
|
368
268
|
panels: {
|
|
@@ -410,10 +310,13 @@ describe("defineToolcraft", () => {
|
|
|
410
310
|
expect(forced.panels.controls?.sections[0]?.controls.settingsTransfer?.type).toBe(
|
|
411
311
|
"settingsTransfer",
|
|
412
312
|
);
|
|
413
|
-
expect(disabled.settingsTransfer
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
)
|
|
313
|
+
expect(disabled.settingsTransfer).toMatchObject({
|
|
314
|
+
enabled: true,
|
|
315
|
+
mode: false,
|
|
316
|
+
});
|
|
317
|
+
expect(disabled.panels.controls?.sections[0]?.controls.settingsTransfer?.type).toBe(
|
|
318
|
+
"settingsTransfer",
|
|
319
|
+
);
|
|
417
320
|
});
|
|
418
321
|
|
|
419
322
|
it("preserves conditional visibility on sections and controls", () => {
|
|
@@ -445,7 +348,7 @@ describe("defineToolcraft", () => {
|
|
|
445
348
|
},
|
|
446
349
|
});
|
|
447
350
|
|
|
448
|
-
const [section] = app
|
|
351
|
+
const [section] = getProductSections(app);
|
|
449
352
|
|
|
450
353
|
expect(section?.visibleWhen).toEqual({
|
|
451
354
|
equals: "co-brand",
|
|
@@ -540,6 +443,7 @@ describe("defineToolcraft", () => {
|
|
|
540
443
|
"media.delete",
|
|
541
444
|
"media.import",
|
|
542
445
|
"media.reorder",
|
|
446
|
+
"media.transform",
|
|
543
447
|
"layers.reorder",
|
|
544
448
|
"timeline.setCurrentTime",
|
|
545
449
|
"canvas.center",
|
|
@@ -548,7 +452,11 @@ describe("defineToolcraft", () => {
|
|
|
548
452
|
);
|
|
549
453
|
expect(app.assembly.surfaces.panels.layers?.defaultPlacement).toBe("left");
|
|
550
454
|
expect(app.assembly.surfaces.panels.timeline?.snapEdges).toEqual(["top", "bottom"]);
|
|
551
|
-
expect(app.panels.timeline).toEqual({
|
|
455
|
+
expect(app.panels.timeline).toEqual({
|
|
456
|
+
defaultDurationSeconds: 8,
|
|
457
|
+
enabled: true,
|
|
458
|
+
mode: "keyframes",
|
|
459
|
+
});
|
|
552
460
|
expect(app.assembly.surfaces.panels.toolbar.enabled).toBe(true);
|
|
553
461
|
});
|
|
554
462
|
|
|
@@ -560,7 +468,11 @@ describe("defineToolcraft", () => {
|
|
|
560
468
|
},
|
|
561
469
|
});
|
|
562
470
|
|
|
563
|
-
expect(app.panels.timeline).toEqual({
|
|
471
|
+
expect(app.panels.timeline).toEqual({
|
|
472
|
+
defaultDurationSeconds: 8,
|
|
473
|
+
enabled: true,
|
|
474
|
+
mode: "playback",
|
|
475
|
+
});
|
|
564
476
|
expect(app.assembly.components).toContain("timelinePanel");
|
|
565
477
|
expect(app.assembly.capabilities).toEqual(
|
|
566
478
|
expect.arrayContaining([
|
|
@@ -584,6 +496,30 @@ describe("defineToolcraft", () => {
|
|
|
584
496
|
expect(app.assembly.commands).not.toContain("timeline.moveKeyframe");
|
|
585
497
|
});
|
|
586
498
|
|
|
499
|
+
it("uses schema timeline default duration as the animation loop duration", () => {
|
|
500
|
+
const app = defineToolcraft({
|
|
501
|
+
canvas: { enabled: true },
|
|
502
|
+
panels: {
|
|
503
|
+
timeline: {
|
|
504
|
+
defaultDurationSeconds: 12,
|
|
505
|
+
mode: "playback",
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
const clamped = defineToolcraft({
|
|
510
|
+
canvas: { enabled: true },
|
|
511
|
+
panels: {
|
|
512
|
+
timeline: {
|
|
513
|
+
defaultDurationSeconds: 120,
|
|
514
|
+
mode: "playback",
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
expect(app.panels.timeline?.defaultDurationSeconds).toBe(12);
|
|
520
|
+
expect(clamped.panels.timeline?.defaultDurationSeconds).toBe(60);
|
|
521
|
+
});
|
|
522
|
+
|
|
587
523
|
it("only publishes commands supported by the runtime command bus", () => {
|
|
588
524
|
const app = defineToolcraft({
|
|
589
525
|
canvas: { enabled: true, upload: true },
|
|
@@ -617,12 +553,18 @@ describe("defineToolcraft", () => {
|
|
|
617
553
|
"canvas.renderScale",
|
|
618
554
|
"canvas.size.width",
|
|
619
555
|
"canvas.size.height",
|
|
556
|
+
"runtime.settingsTransfer",
|
|
557
|
+
"panels.timeline.extended",
|
|
558
|
+
"panels.timeline.visible",
|
|
620
559
|
]);
|
|
621
560
|
expect(toolcraftReservedTargets).toEqual([
|
|
622
561
|
"canvas.aspectRatio",
|
|
623
562
|
"canvas.renderScale",
|
|
624
563
|
"canvas.size.width",
|
|
625
564
|
"canvas.size.height",
|
|
565
|
+
"runtime.settingsTransfer",
|
|
566
|
+
"panels.timeline.extended",
|
|
567
|
+
"panels.timeline.visible",
|
|
626
568
|
"selectedLayer.opacity",
|
|
627
569
|
"selectedLayer.visible",
|
|
628
570
|
]);
|
|
@@ -630,6 +572,9 @@ describe("defineToolcraft", () => {
|
|
|
630
572
|
expect(getToolcraftCanvasSizeTargetDimension("canvas.size.height")).toBe("height");
|
|
631
573
|
expect(getToolcraftCanvasSizeTargetDimension("generation.prompt")).toBeNull();
|
|
632
574
|
expect(isToolcraftRuntimeOwnedTarget("canvas.aspectRatio")).toBe(true);
|
|
575
|
+
expect(isToolcraftRuntimeOwnedTarget("runtime.settingsTransfer")).toBe(true);
|
|
576
|
+
expect(isToolcraftRuntimeOwnedTarget("panels.timeline.extended")).toBe(true);
|
|
577
|
+
expect(isToolcraftRuntimeOwnedTarget("panels.timeline.visible")).toBe(true);
|
|
633
578
|
expect(isToolcraftReservedTarget("selectedLayer.opacity")).toBe(true);
|
|
634
579
|
expect(isToolcraftReservedTarget("generation.prompt")).toBe(false);
|
|
635
580
|
});
|
|
@@ -823,6 +768,7 @@ describe("defineToolcraft", () => {
|
|
|
823
768
|
});
|
|
824
769
|
expect(app.assembly.capabilities).toContain("canvas.renderScale");
|
|
825
770
|
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
771
|
+
"settingsTransfer",
|
|
826
772
|
"canvasAspectRatio",
|
|
827
773
|
"canvasWidth",
|
|
828
774
|
"canvasHeight",
|
|
@@ -837,11 +783,107 @@ describe("defineToolcraft", () => {
|
|
|
837
783
|
step: 0.25,
|
|
838
784
|
target: "canvas.renderScale",
|
|
839
785
|
type: "slider",
|
|
840
|
-
unit: "x",
|
|
841
786
|
variant: "discrete",
|
|
842
787
|
});
|
|
843
788
|
});
|
|
844
789
|
|
|
790
|
+
it("appends timeline visibility to the bottom of setup controls when timeline is enabled", () => {
|
|
791
|
+
const app = defineToolcraft({
|
|
792
|
+
canvas: {
|
|
793
|
+
enabled: true,
|
|
794
|
+
renderScale: true,
|
|
795
|
+
size: { height: 900, unit: "px", width: 1440 },
|
|
796
|
+
sizing: { mode: "editable-output" },
|
|
797
|
+
},
|
|
798
|
+
panels: {
|
|
799
|
+
controls: {
|
|
800
|
+
sections: [],
|
|
801
|
+
title: "Controls",
|
|
802
|
+
},
|
|
803
|
+
timeline: { mode: "playback" },
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const setupSection = app.panels.controls?.sections[0];
|
|
808
|
+
|
|
809
|
+
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
810
|
+
"settingsTransfer",
|
|
811
|
+
"canvasAspectRatio",
|
|
812
|
+
"canvasWidth",
|
|
813
|
+
"canvasHeight",
|
|
814
|
+
"canvasRenderScale",
|
|
815
|
+
"timelineExtended",
|
|
816
|
+
]);
|
|
817
|
+
expect(setupSection?.controls.timelineExtended).toMatchObject({
|
|
818
|
+
defaultValue: false,
|
|
819
|
+
description:
|
|
820
|
+
"Shows the extended runtime timeline with scrubber, duration, loop, and keyframe controls; compact mode keeps only Play visible.",
|
|
821
|
+
label: "Timeline",
|
|
822
|
+
target: "panels.timeline.extended",
|
|
823
|
+
type: "switch",
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("does not let app-authored runtime targets suppress mandatory setup controls", () => {
|
|
828
|
+
const app = defineToolcraft({
|
|
829
|
+
canvas: {
|
|
830
|
+
enabled: true,
|
|
831
|
+
renderScale: true,
|
|
832
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
833
|
+
sizing: { mode: "editable-output" },
|
|
834
|
+
},
|
|
835
|
+
panels: {
|
|
836
|
+
controls: {
|
|
837
|
+
sections: [
|
|
838
|
+
{
|
|
839
|
+
controls: {
|
|
840
|
+
manualWidth: {
|
|
841
|
+
defaultValue: 1280,
|
|
842
|
+
label: "Width",
|
|
843
|
+
target: "canvas.size.width",
|
|
844
|
+
type: "text",
|
|
845
|
+
},
|
|
846
|
+
manualRenderScale: {
|
|
847
|
+
defaultValue: 1,
|
|
848
|
+
label: "Scale",
|
|
849
|
+
max: 2,
|
|
850
|
+
min: 1,
|
|
851
|
+
target: "canvas.renderScale",
|
|
852
|
+
type: "slider",
|
|
853
|
+
},
|
|
854
|
+
manualTimeline: {
|
|
855
|
+
defaultValue: true,
|
|
856
|
+
label: "Timeline",
|
|
857
|
+
target: "panels.timeline.extended",
|
|
858
|
+
type: "switch",
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
title: "Manual Runtime Duplicates",
|
|
862
|
+
},
|
|
863
|
+
],
|
|
864
|
+
title: "Controls",
|
|
865
|
+
},
|
|
866
|
+
timeline: { mode: "playback" },
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const [setupSection, duplicateSection] = app.panels.controls?.sections ?? [];
|
|
871
|
+
|
|
872
|
+
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
873
|
+
"settingsTransfer",
|
|
874
|
+
"canvasAspectRatio",
|
|
875
|
+
"canvasWidth",
|
|
876
|
+
"canvasHeight",
|
|
877
|
+
"canvasRenderScale",
|
|
878
|
+
"timelineExtended",
|
|
879
|
+
]);
|
|
880
|
+
expect(duplicateSection?.controls.manualWidth?.target).toBe("canvas.size.width");
|
|
881
|
+
expect(duplicateSection?.controls.manualRenderScale?.target).toBe("canvas.renderScale");
|
|
882
|
+
expect(duplicateSection?.controls.manualTimeline?.target).toBe(
|
|
883
|
+
"panels.timeline.extended",
|
|
884
|
+
);
|
|
885
|
+
});
|
|
886
|
+
|
|
845
887
|
it("can prepend render scale without editable output sizing", () => {
|
|
846
888
|
const app = defineToolcraft({
|
|
847
889
|
canvas: {
|
|
@@ -874,6 +916,7 @@ describe("defineToolcraft", () => {
|
|
|
874
916
|
const intrinsicApp = defineToolcraft({
|
|
875
917
|
canvas: {
|
|
876
918
|
enabled: true,
|
|
919
|
+
sizing: { mode: "intrinsic-media" },
|
|
877
920
|
upload: true,
|
|
878
921
|
},
|
|
879
922
|
panels: {
|
|
@@ -904,10 +947,14 @@ describe("defineToolcraft", () => {
|
|
|
904
947
|
|
|
905
948
|
expect(intrinsicApp.canvas.sizing).toEqual({ mode: "intrinsic-media" });
|
|
906
949
|
expect(fixedApp.canvas.sizing).toEqual({ mode: "fixed-output" });
|
|
907
|
-
expect(intrinsicApp.panels.controls?.sections[0]?.title).toBe("
|
|
908
|
-
expect(fixedApp.panels.controls?.sections[0]?.title).toBe("
|
|
950
|
+
expect(intrinsicApp.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
951
|
+
expect(fixedApp.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
952
|
+
expect(intrinsicApp.panels.controls?.sections[1]?.title).toBe("Generation");
|
|
953
|
+
expect(fixedApp.panels.controls?.sections.at(-1)?.title).toBe("Generation");
|
|
909
954
|
expect(intrinsicApp.panels.controls?.sections[0]?.controls.canvasWidth).toBeUndefined();
|
|
910
955
|
expect(fixedApp.panels.controls?.sections[0]?.controls.canvasWidth).toBeUndefined();
|
|
956
|
+
expect(intrinsicApp.panels.controls?.sections[0]?.controls.settingsTransfer).toBeTruthy();
|
|
957
|
+
expect(fixedApp.panels.controls?.sections[0]?.controls.settingsTransfer).toBeTruthy();
|
|
911
958
|
});
|
|
912
959
|
|
|
913
960
|
it("adds runtime layout groups only for compact numeric text pairs", () => {
|
|
@@ -971,7 +1018,7 @@ describe("defineToolcraft", () => {
|
|
|
971
1018
|
},
|
|
972
1019
|
});
|
|
973
1020
|
|
|
974
|
-
const outputSection = app
|
|
1021
|
+
const [outputSection] = getProductSections(app);
|
|
975
1022
|
|
|
976
1023
|
expect(outputSection?.layoutGroups).toEqual([
|
|
977
1024
|
{
|
|
@@ -1011,7 +1058,7 @@ describe("defineToolcraft", () => {
|
|
|
1011
1058
|
},
|
|
1012
1059
|
});
|
|
1013
1060
|
|
|
1014
|
-
const maskSection = app
|
|
1061
|
+
const [maskSection] = getProductSections(app);
|
|
1015
1062
|
|
|
1016
1063
|
expect(maskSection?.layoutGroups).toEqual([
|
|
1017
1064
|
{
|
|
@@ -1122,7 +1169,7 @@ describe("defineToolcraft", () => {
|
|
|
1122
1169
|
},
|
|
1123
1170
|
});
|
|
1124
1171
|
|
|
1125
|
-
const [formatPairSection, outputSection] = app
|
|
1172
|
+
const [formatPairSection, outputSection] = getProductSections(app);
|
|
1126
1173
|
|
|
1127
1174
|
expect(formatPairSection?.layoutGroups).toEqual([
|
|
1128
1175
|
{
|
|
@@ -1169,7 +1216,7 @@ describe("defineToolcraft", () => {
|
|
|
1169
1216
|
},
|
|
1170
1217
|
});
|
|
1171
1218
|
|
|
1172
|
-
const [basicSection, anchorSection] = app
|
|
1219
|
+
const [basicSection, anchorSection] = getProductSections(app);
|
|
1173
1220
|
|
|
1174
1221
|
expect(Object.keys(basicSection?.controls ?? {})).toEqual(["opacity", "threshold"]);
|
|
1175
1222
|
expect(basicSection?.title).toBe("Basic");
|
|
@@ -1212,8 +1259,8 @@ describe("defineToolcraft", () => {
|
|
|
1212
1259
|
},
|
|
1213
1260
|
});
|
|
1214
1261
|
|
|
1215
|
-
expect(app
|
|
1216
|
-
expect(app
|
|
1262
|
+
expect(getProductSections(app)[0]?.layout).toBe("standalone");
|
|
1263
|
+
expect(getProductSections(app)[0]?.title).toBe("Palette");
|
|
1217
1264
|
});
|
|
1218
1265
|
|
|
1219
1266
|
it("adds semantic implicit titles to standalone color sections", () => {
|
|
@@ -1245,8 +1292,8 @@ describe("defineToolcraft", () => {
|
|
|
1245
1292
|
},
|
|
1246
1293
|
});
|
|
1247
1294
|
|
|
1248
|
-
expect(app
|
|
1249
|
-
expect(app
|
|
1295
|
+
expect(getProductSections(app)[0]?.layout).toBe("standalone");
|
|
1296
|
+
expect(getProductSections(app)[0]?.title).toBe("Fill & Stroke");
|
|
1250
1297
|
|
|
1251
1298
|
const inferredLayoutApp = defineToolcraft({
|
|
1252
1299
|
canvas: { enabled: true },
|
|
@@ -1269,7 +1316,7 @@ describe("defineToolcraft", () => {
|
|
|
1269
1316
|
},
|
|
1270
1317
|
});
|
|
1271
1318
|
|
|
1272
|
-
expect(inferredLayoutApp
|
|
1319
|
+
expect(getProductSections(inferredLayoutApp)[0]?.title).toBe("Fill");
|
|
1273
1320
|
});
|
|
1274
1321
|
|
|
1275
1322
|
it("replaces generic color section titles with a semantic fallback", () => {
|
|
@@ -1345,9 +1392,9 @@ describe("defineToolcraft", () => {
|
|
|
1345
1392
|
},
|
|
1346
1393
|
});
|
|
1347
1394
|
|
|
1348
|
-
expect(genericColorApp
|
|
1349
|
-
expect(genericColorsApp
|
|
1350
|
-
expect(explicitGenericTitleApp
|
|
1395
|
+
expect(getProductSections(genericColorApp)[0]?.title).toBe("Appearance");
|
|
1396
|
+
expect(getProductSections(genericColorsApp)[0]?.title).toBe("Appearance");
|
|
1397
|
+
expect(getProductSections(explicitGenericTitleApp)[0]?.title).toBe("Appearance");
|
|
1351
1398
|
});
|
|
1352
1399
|
|
|
1353
1400
|
it("keeps color controls inside semantic mixed sections", () => {
|
|
@@ -1388,15 +1435,64 @@ describe("defineToolcraft", () => {
|
|
|
1388
1435
|
},
|
|
1389
1436
|
});
|
|
1390
1437
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
expect(
|
|
1438
|
+
const productSections = getProductSections(app);
|
|
1439
|
+
|
|
1440
|
+
expect(productSections).toHaveLength(1);
|
|
1441
|
+
expect(productSections[0]?.title).toBe("Square 1 (Right)");
|
|
1442
|
+
expect(Object.keys(productSections[0]?.controls ?? {})).toEqual([
|
|
1394
1443
|
"connections",
|
|
1395
1444
|
"hoverRadius",
|
|
1396
1445
|
"color",
|
|
1397
1446
|
]);
|
|
1398
1447
|
});
|
|
1399
1448
|
|
|
1449
|
+
it("keeps mode-gated standalone controls inside their semantic section", () => {
|
|
1450
|
+
const app = defineToolcraft({
|
|
1451
|
+
canvas: { enabled: true },
|
|
1452
|
+
panels: {
|
|
1453
|
+
controls: {
|
|
1454
|
+
sections: [
|
|
1455
|
+
{
|
|
1456
|
+
controls: {
|
|
1457
|
+
sourceMode: {
|
|
1458
|
+
defaultValue: "preset",
|
|
1459
|
+
label: "Source",
|
|
1460
|
+
options: [
|
|
1461
|
+
{ label: "Preset", value: "preset" },
|
|
1462
|
+
{ label: "Image", value: "image" },
|
|
1463
|
+
],
|
|
1464
|
+
target: "source.mode",
|
|
1465
|
+
type: "segmented",
|
|
1466
|
+
},
|
|
1467
|
+
sourceUpload: {
|
|
1468
|
+
accept: "image/*",
|
|
1469
|
+
assetKind: "image",
|
|
1470
|
+
defaultValue: null,
|
|
1471
|
+
label: "Image",
|
|
1472
|
+
target: "source.upload",
|
|
1473
|
+
type: "fileDrop",
|
|
1474
|
+
visibleWhen: { equals: "image", target: "source.mode" },
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
title: "Source",
|
|
1478
|
+
},
|
|
1479
|
+
],
|
|
1480
|
+
title: "Controls",
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
const productSections = getProductSections(app);
|
|
1486
|
+
|
|
1487
|
+
expect(productSections).toHaveLength(1);
|
|
1488
|
+
expect(productSections[0]?.title).toBe("Source");
|
|
1489
|
+
expect(productSections[0]?.layout).toBeUndefined();
|
|
1490
|
+
expect(Object.keys(productSections[0]?.controls ?? {})).toEqual([
|
|
1491
|
+
"sourceMode",
|
|
1492
|
+
"sourceUpload",
|
|
1493
|
+
]);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1400
1496
|
it("preserves controls panel action sections and render metadata", () => {
|
|
1401
1497
|
const app = defineToolcraft({
|
|
1402
1498
|
canvas: { enabled: true },
|
|
@@ -1449,7 +1545,7 @@ describe("defineToolcraft", () => {
|
|
|
1449
1545
|
},
|
|
1450
1546
|
});
|
|
1451
1547
|
|
|
1452
|
-
const [slidersSection, actionsSection] = app
|
|
1548
|
+
const [slidersSection, actionsSection] = getProductSections(app);
|
|
1453
1549
|
|
|
1454
1550
|
expect(slidersSection?.controls.opacity?.markerCount).toBe(101);
|
|
1455
1551
|
expect(slidersSection?.controls.opacity?.variant).toBe("discrete");
|
|
@@ -1519,7 +1615,7 @@ describe("defineToolcraft", () => {
|
|
|
1519
1615
|
},
|
|
1520
1616
|
});
|
|
1521
1617
|
|
|
1522
|
-
const controls = app
|
|
1618
|
+
const controls = getProductSections(app)[0]?.controls;
|
|
1523
1619
|
|
|
1524
1620
|
expect(controls?.speed?.variant).toBeUndefined();
|
|
1525
1621
|
expect(controls?.speed?.markerCount).toBe(6);
|