@pixel-point/toolcraft 0.0.9 → 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 +14 -6
- package/src/generate.test.mjs +28 -0
- package/templates/runtime/contracts/component-contracts.test.ts +148 -34
- package/templates/runtime/contracts/component-contracts.ts +79 -38
- package/templates/runtime/contracts/decision-contracts.test.ts +33 -1
- package/templates/runtime/contracts/decision-contracts.ts +19 -6
- 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 +474 -27
- package/templates/runtime/react/controls-panel.tsx +71 -26
- 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 +225 -173
- package/templates/runtime/schema/define-toolcraft.ts +117 -247
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +41 -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 +657 -2
- package/templates/runtime/testing/performance.ts +789 -49
- package/templates/starter/AGENTS.md +22 -16
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +38 -7
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +51 -20
- package/templates/starter/docs/toolcraft/component-rules.md +49 -37
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +30 -10
- package/templates/starter/docs/toolcraft/schema-reference.md +134 -33
- 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/index.html +1 -0
- package/templates/starter/package.json +1 -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 +82 -16
- package/templates/starter/scripts/toolcraft-port.mjs +178 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +147 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +2573 -313
- package/templates/starter/src/app/starter-acceptance.ts +978 -81
- 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/controls/actions/actions-control.tsx +46 -3
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- 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 +2 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +4 -26
- package/templates/ui/components/controls/vector/vector-control.tsx +25 -4
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# Toolcraft CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create standalone Toolcraft apps from the command line.
|
|
4
|
+
|
|
5
|
+
Toolcraft is a Pixel Point starter kit and UI/runtime library for building
|
|
6
|
+
personal creative design tools with AI. It gives generated apps a ready canvas,
|
|
7
|
+
control panel, export flow, AI workflow instructions, and verification checks so
|
|
8
|
+
you can focus prompts on the visual idea instead of rebuilding common creative
|
|
9
|
+
tool plumbing.
|
|
10
|
+
|
|
11
|
+
You use Toolcraft when a custom tool is faster or clearer than forcing a broad
|
|
12
|
+
design app to fit one specific job: procedural graphics, image effects, branded
|
|
13
|
+
asset generators, animation controllers, shader experiments, or quick visual
|
|
14
|
+
tools for client work.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
4
17
|
|
|
5
18
|
```bash
|
|
6
19
|
npx @pixel-point/toolcraft create
|
|
@@ -12,14 +25,20 @@ After dependencies are installed, Toolcraft installs the required workflow skill
|
|
|
12
25
|
in a batch through the `skills` CLI. The skill installer uses the same agent,
|
|
13
26
|
scope, and installation prompts as `npx skills add`.
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
Example:
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
```bash
|
|
31
|
+
npx @pixel-point/toolcraft create my-ascii-tool
|
|
32
|
+
cd my-ascii-tool
|
|
33
|
+
pnpm dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then open the generated folder in Codex, Claude Code, Cursor, or another AI
|
|
37
|
+
coding agent and prompt for the creative output you want, for example:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Build an app that applies an ASCII effect to an uploaded image.
|
|
41
|
+
```
|
|
23
42
|
|
|
24
43
|
Scripted usage:
|
|
25
44
|
|
|
@@ -50,5 +69,19 @@ Local tarball test, matching the published package layout:
|
|
|
50
69
|
cd cli
|
|
51
70
|
npm pack --pack-destination /tmp
|
|
52
71
|
cd ..
|
|
53
|
-
TOOLCRAFT_SKIP_INSTALL=1 TOOLCRAFT_SKIP_SKILLS=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.
|
|
72
|
+
TOOLCRAFT_SKIP_INSTALL=1 TOOLCRAFT_SKIP_SKILLS=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.10.tgz -- toolcraft create /tmp/toolcraft-pack-exec-test --name pack-exec-test --yes --force
|
|
54
73
|
```
|
|
74
|
+
|
|
75
|
+
## Learn more
|
|
76
|
+
|
|
77
|
+
- [How to craft personal design tools with AI](https://pixelpoint.io/blog/how-to-craft-personal-design-tools-with-toolcraft/)
|
|
78
|
+
- [Toolcraft YouTube tutorial](https://youtu.be/-QlmkGZLzFo)
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Toolcraft is distributed under the Toolcraft Designer License in `LICENSE.md`.
|
|
83
|
+
Designer client work is permitted under that license. Using AI coding assistants
|
|
84
|
+
or agents such as Codex, Claude, ChatGPT, Cursor, or similar tools to work on
|
|
85
|
+
generated apps is permitted. Platform, generator, AI software product,
|
|
86
|
+
app-builder, website-builder, template-marketplace, and resale uses require a
|
|
87
|
+
separate commercial license from Pixel Point.
|
package/package.json
CHANGED
package/src/generate.mjs
CHANGED
|
@@ -32,6 +32,10 @@ function escapeHtmlText(value) {
|
|
|
32
32
|
.replaceAll(">", ">");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function escapeHtmlAttribute(value) {
|
|
36
|
+
return escapeHtmlText(value).replaceAll('"', """);
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
async function readJson(filePath) {
|
|
36
40
|
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
37
41
|
}
|
|
@@ -114,12 +118,16 @@ async function restoreGeneratedGitignore(targetDir) {
|
|
|
114
118
|
async function writeGeneratedProjectTitle(targetDir, title) {
|
|
115
119
|
const indexPath = path.join(targetDir, "index.html");
|
|
116
120
|
const source = await fs.readFile(indexPath, "utf8");
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const escapedTitle = escapeHtmlText(title);
|
|
122
|
+
const escapedTitleAttribute = escapeHtmlAttribute(title);
|
|
123
|
+
const withTitle = source.replace(/<title>.*?<\/title>/, `<title>${escapedTitle}</title>`);
|
|
124
|
+
const identityMeta = `<meta name="toolcraft-app-title" content="${escapedTitleAttribute}" />`;
|
|
125
|
+
const identityMetaPattern = /<meta\b(?=[^>]*\bname=["']toolcraft-app-title["'])[^>]*>/i;
|
|
126
|
+
const nextSource = identityMetaPattern.test(withTitle)
|
|
127
|
+
? withTitle.replace(identityMetaPattern, identityMeta)
|
|
128
|
+
: withTitle.replace(/<head>/i, `<head>\n ${identityMeta}`);
|
|
129
|
+
|
|
130
|
+
if (!nextSource.includes(`<title>${escapedTitle}</title>`) || !nextSource.includes(identityMeta)) {
|
|
123
131
|
throw new Error(`Unable to update generated app title in ${indexPath}.`);
|
|
124
132
|
}
|
|
125
133
|
|
package/src/generate.test.mjs
CHANGED
|
@@ -101,6 +101,7 @@ describe("generateToolcraft", () => {
|
|
|
101
101
|
assert.equal(packageJson.scripts["verify:quick"], "pnpm ai:check && pnpm test");
|
|
102
102
|
assert.equal(packageJson.scripts["verify:ui"], "pnpm test:browser");
|
|
103
103
|
assert.equal(packageJson.scripts["verify:perf"], "pnpm test:browser:perf");
|
|
104
|
+
assert.equal(packageJson.scripts["verify:perf:playwright"], "pnpm test:browser:perf");
|
|
104
105
|
assert.equal(
|
|
105
106
|
packageJson.scripts["verify:final"],
|
|
106
107
|
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser",
|
|
@@ -108,6 +109,7 @@ describe("generateToolcraft", () => {
|
|
|
108
109
|
|
|
109
110
|
const indexHtmlSource = await fs.readFile(path.join(targetDir, "index.html"), "utf8");
|
|
110
111
|
assert.match(indexHtmlSource, /<title>Generated App<\/title>/);
|
|
112
|
+
assert.match(indexHtmlSource, /<meta name="toolcraft-app-title" content="Generated App" \/>/);
|
|
111
113
|
|
|
112
114
|
assert.ok(await fs.stat(path.join(targetDir, "playwright.config.ts")));
|
|
113
115
|
const playwrightConfigSource = await fs.readFile(
|
|
@@ -158,6 +160,9 @@ describe("generateToolcraft", () => {
|
|
|
158
160
|
"utf8",
|
|
159
161
|
);
|
|
160
162
|
assert.match(appPerformanceSource, /defineToolcraftPerformance/);
|
|
163
|
+
assert.match(appPerformanceSource, /preferredRunner:\s*"agent-browser"/);
|
|
164
|
+
assert.match(appPerformanceSource, /fallbackRunner:\s*"playwright"/);
|
|
165
|
+
assert.match(appPerformanceSource, /agent-browser-unavailable/);
|
|
161
166
|
assert.doesNotMatch(appPerformanceSource, /function\s+validateToolcraftPerformanceCoverage/);
|
|
162
167
|
assert.doesNotMatch(
|
|
163
168
|
appPerformanceSource,
|
|
@@ -337,4 +342,27 @@ describe("generateToolcraft", () => {
|
|
|
337
342
|
/Target directory is not empty/,
|
|
338
343
|
);
|
|
339
344
|
});
|
|
345
|
+
|
|
346
|
+
it("writes the app identity marker when the generated title matches the template title", async () => {
|
|
347
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "toolcraft-cli-"));
|
|
348
|
+
tempRoots.push(tempRoot);
|
|
349
|
+
|
|
350
|
+
await generateToolcraft({
|
|
351
|
+
cwd: tempRoot,
|
|
352
|
+
force: true,
|
|
353
|
+
name: "toolcraft-app-template",
|
|
354
|
+
targetDir: "toolcraft-app-template",
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const indexHtmlSource = await fs.readFile(
|
|
358
|
+
path.join(tempRoot, "toolcraft-app-template", "index.html"),
|
|
359
|
+
"utf8",
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
assert.match(indexHtmlSource, /<title>Toolcraft App Template<\/title>/);
|
|
363
|
+
assert.match(
|
|
364
|
+
indexHtmlSource,
|
|
365
|
+
/<meta name="toolcraft-app-title" content="Toolcraft App Template" \/>/,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
340
368
|
});
|
|
@@ -89,11 +89,17 @@ describe("Toolcraft template component contracts", () => {
|
|
|
89
89
|
"Do not place Segmented beside Switch, Color, Select, or another control in an inline row; use Select when a finite choice must occupy a half-width column.",
|
|
90
90
|
);
|
|
91
91
|
expect(select?.useWhen.join(" ")).toMatch(/long labels|many options/i);
|
|
92
|
+
expect(select?.useWhen).toContain(
|
|
93
|
+
"Use Select when a dropdown choice is more readable than a row of segmented cells.",
|
|
94
|
+
);
|
|
95
|
+
expect(select?.layoutConstraints).toContain(
|
|
96
|
+
"Standalone Select controls render stacked full-width with the label above the dropdown; do not use the compact side-label row with label left and dropdown right.",
|
|
97
|
+
);
|
|
92
98
|
expect(select?.layoutConstraints).toContain(
|
|
93
|
-
"Prefer compact two-column inline layout for related short Select pairs that tune one workflow or entity.",
|
|
99
|
+
"Prefer compact two-column inline layout only for related short Select pairs that tune one workflow or entity.",
|
|
94
100
|
);
|
|
95
101
|
expect(select?.layoutConstraints).toContain(
|
|
96
|
-
"Use vertical one-select-per-row layout
|
|
102
|
+
"Use vertical one-select-per-row layout for single Select controls and as the fallback when a Select pair label, selected value, or option text would clip, truncate, or lose internal padding in the compact row.",
|
|
97
103
|
);
|
|
98
104
|
expect(select?.layoutConstraints).toContain(
|
|
99
105
|
"If a compact Select pair falls back to vertical layout, record the fit reason in the spec or worklog.",
|
|
@@ -160,7 +166,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
160
166
|
'Default spatial vector pads use coordinateMode: "screen": dragging left/up makes vector.x and vector.y smaller so canvas objects move left/up without renderer-side Y inversion.',
|
|
161
167
|
);
|
|
162
168
|
expect(contract.aiUsageRules).toContain(
|
|
163
|
-
"
|
|
169
|
+
"Vector pad value labels render compact rounded coordinates. Do not expose raw floating-point strings such as -0.07070312499999998 in the controls panel.",
|
|
170
|
+
);
|
|
171
|
+
expect(contract.aiUsageRules).toContain(
|
|
172
|
+
"Double-clicking a vector pad resets both axes to the control default through the normal runtime value update, matching section header reset semantics; if no default is defined, the fallback is 0,0. Do not add a separate custom reset UI for this basic pad reset behavior.",
|
|
173
|
+
);
|
|
174
|
+
expect(contract.aiUsageRules).toContain(
|
|
175
|
+
"Holding Shift while dragging a vector pad locks movement to the dominant axis and must not select text or page content; do not build a custom pad just to support axis-constrained movement.",
|
|
164
176
|
);
|
|
165
177
|
expect(contract.aiUsageRules).toContain(
|
|
166
178
|
'Use coordinateMode: "cartesian" only when the product intentionally exposes mathematical Y-up coordinates instead of canvas/screen movement.',
|
|
@@ -361,6 +373,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
361
373
|
expect(contract.aiUsageRules).toContain(
|
|
362
374
|
"Do not use Palette when opacity belongs to the same color entity; use ColorOpacity instead.",
|
|
363
375
|
);
|
|
376
|
+
expect(contract.decisionCatalog?.requiredAcceptance).toContain(
|
|
377
|
+
"Prove Palette family and shade selections update runtime state immediately, before delayed persistence/commit settles.",
|
|
378
|
+
);
|
|
379
|
+
expect(contract.aiUsageRules).toContain(
|
|
380
|
+
"Palette is a live control like Color and Slider: family and shade changes must update runtime state immediately so the next canvas interaction uses the selected token without waiting for delayed commit or persistence timers.",
|
|
381
|
+
);
|
|
364
382
|
expect(contract.aiUsageRules).toContain(
|
|
365
383
|
"Do not use Palette for gradients or color transitions; use Gradient instead.",
|
|
366
384
|
);
|
|
@@ -402,13 +420,20 @@ describe("Toolcraft template component contracts", () => {
|
|
|
402
420
|
expect(contract.commands).toContain("canvas.panBy");
|
|
403
421
|
expect(contract.commands).toContain("canvas.setOffset");
|
|
404
422
|
expect(contract.commands).toContain("media.import");
|
|
423
|
+
expect(contract.commands).toContain("media.transform");
|
|
405
424
|
expect(contract.capabilities).toContain("editable-size");
|
|
406
425
|
expect(contract.aiUsageRules).toContain(
|
|
407
|
-
"
|
|
426
|
+
"Do not let canvas.upload choose intrinsic-media by default. Upload without explicit sizing resolves to editable-output so source/background images do not own product output size.",
|
|
427
|
+
);
|
|
428
|
+
expect(contract.aiUsageRules).toContain(
|
|
429
|
+
"Use intrinsic-media only for true media-viewer or source-native apps where the natural uploaded/generated media size is the product output; record the reason and prove it with intrinsic-media-size acceptance.",
|
|
408
430
|
);
|
|
409
431
|
expect(contract.aiUsageRules).toContain(
|
|
410
432
|
"Use editable-output for generated, exportable, shader, poster, badge, wall, banner, thumbnail, procedural, reference-clone, and product-output apps so users always see Aspect ratio, Canvas width, and Canvas height.",
|
|
411
433
|
);
|
|
434
|
+
expect(contract.aiUsageRules).toContain(
|
|
435
|
+
"When an uploaded image is a background/source inside the product canvas, keep the current canvas.size, keep Setup/canvas controls visible, and render the image as cover/crop inside the current canvas bounds without letterbox or aspect distortion.",
|
|
436
|
+
);
|
|
412
437
|
expect(contract.aiUsageRules).toContain(
|
|
413
438
|
"A user-provided, reference, fixed-format, or base/default size is not a reason to remove size controls; model it as canvas.size plus editable-output so the size is an initial value, not a hidden lock.",
|
|
414
439
|
);
|
|
@@ -419,13 +444,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
419
444
|
"A reference or previous app lacking a size editor, or defining a fixed-size baseline, is not a fixed-output reason for a generated product app; product-output clones still use editable-output.",
|
|
420
445
|
);
|
|
421
446
|
expect(contract.aiUsageRules).toContain(
|
|
422
|
-
"Resolved canvas.size exists for every canvas app, but visible Canvas width and Canvas height controls are mandatory only for editable-output sizing and
|
|
447
|
+
"Resolved canvas.size exists for every canvas app, but visible Canvas width and Canvas height controls are mandatory only for editable-output sizing and live in the mandatory runtime Setup section.",
|
|
423
448
|
);
|
|
424
449
|
expect(contract.aiUsageRules).toContain(
|
|
425
450
|
"If canvas.size is provided without an explicit sizing mode, defineToolcraft treats it as editable-output and adds Canvas width and Canvas height controls.",
|
|
426
451
|
);
|
|
427
452
|
expect(contract.aiUsageRules).toContain(
|
|
428
|
-
"The runtime Canvas width and Canvas height block uses the
|
|
453
|
+
"The runtime Canvas width and Canvas height block uses the mandatory headerless Setup controls block; do not add a separate Canvas section label above these fields.",
|
|
429
454
|
);
|
|
430
455
|
expect(contract.aiUsageRules).toContain(
|
|
431
456
|
"When the user manually edits Canvas width or Canvas height, the runtime keeps the typed dimension, keeps the other dimension unchanged, switches Aspect ratio to Custom, and shows the reduced current ratio in the custom ratio inputs.",
|
|
@@ -458,7 +483,10 @@ describe("Toolcraft template component contracts", () => {
|
|
|
458
483
|
"Use schema persistence policy for app state that should survive reload.",
|
|
459
484
|
);
|
|
460
485
|
expect(contract.aiUsageRules).toContain(
|
|
461
|
-
"Persistence may include values, canvas, panels, timeline,
|
|
486
|
+
"Persistence may include values, canvas, panels, timeline, layers, and media; history is not persisted.",
|
|
487
|
+
);
|
|
488
|
+
expect(contract.aiUsageRules).toContain(
|
|
489
|
+
"Use persistence include: [\"media\"] only when runtime media state must survive reload, such as predefined attached files that users can delete, reorder, or transform. Do not use ad hoc storage for media state.",
|
|
462
490
|
);
|
|
463
491
|
expect(contract.aiUsageRules).toContain(
|
|
464
492
|
'Apps with visible runtime panels and localStorage persistence must include "panels" so dragged panel positions survive reload.',
|
|
@@ -467,32 +495,35 @@ describe("Toolcraft template component contracts", () => {
|
|
|
467
495
|
'Apps with localStorage persistence must include acceptance coverage for changing a user setting, reloading the browser page, and seeing the restored value or product output.',
|
|
468
496
|
);
|
|
469
497
|
expect(contract.aiUsageRules).toContain(
|
|
470
|
-
"Settings import/export is a preset transfer feature
|
|
498
|
+
"Settings import/export is a mandatory runtime preset transfer feature; it must not be used to hide or replace broken persistence reload behavior.",
|
|
471
499
|
);
|
|
472
500
|
});
|
|
473
501
|
|
|
474
|
-
it("documents settings transfer as a runtime-owned
|
|
502
|
+
it("documents settings transfer as a mandatory runtime-owned feature", () => {
|
|
475
503
|
const contract = getToolcraftComponentContract("settingsTransfer");
|
|
476
504
|
|
|
477
505
|
expect(contract.kind).toBe("settings");
|
|
478
506
|
expect(contract.stateMode).toBe("runtime-owned");
|
|
479
507
|
expect(contract.aiUsageRules).toContain(
|
|
480
|
-
|
|
508
|
+
"Generated apps keep a controls panel so runtime Setup is visible from the first run; product controls are added after that mandatory runtime section.",
|
|
481
509
|
);
|
|
482
510
|
expect(contract.aiUsageRules).toContain(
|
|
483
|
-
"
|
|
511
|
+
"Do not gate Export Settings / Import Settings behind complexity thresholds, app size, or prompt wording.",
|
|
484
512
|
);
|
|
485
513
|
expect(contract.aiUsageRules).toContain(
|
|
486
514
|
"Do not hand-roll settings import/export through app routes, hidden file inputs, or panelActions.",
|
|
487
515
|
);
|
|
488
516
|
expect(contract.aiUsageRules).toContain(
|
|
489
|
-
"Settings transfer appears
|
|
517
|
+
"Settings transfer appears in the first visible headerless Setup controls-panel block; it imports and exports control values, canvas size, and timeline state.",
|
|
518
|
+
);
|
|
519
|
+
expect(contract.aiUsageRules).toContain(
|
|
520
|
+
"App-authored sections must not declare runtime Setup targets such as runtime.settingsTransfer, canvas.aspectRatio, canvas.size.width, canvas.size.height, canvas.renderScale, or panels.timeline.extended; those controls never suppress the mandatory runtime Setup controls.",
|
|
490
521
|
);
|
|
491
522
|
expect(contract.aiUsageRules).toContain(
|
|
492
|
-
"
|
|
523
|
+
"When editable-output canvas sizing is enabled, the first visible Setup runtime section contains Export Settings, Import Settings, Aspect ratio, Canvas width, Canvas height, and optional Resolution scale in that order.",
|
|
493
524
|
);
|
|
494
525
|
expect(contract.aiUsageRules).toContain(
|
|
495
|
-
"When
|
|
526
|
+
"When panels.timeline is enabled, Setup appends the Timeline switch as the last control; when panels.timeline is omitted, the Timeline switch must not appear.",
|
|
496
527
|
);
|
|
497
528
|
});
|
|
498
529
|
|
|
@@ -564,13 +595,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
564
595
|
"Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
|
|
565
596
|
);
|
|
566
597
|
expect(slider.aiUsageRules).toContain(
|
|
567
|
-
"Use
|
|
598
|
+
"Use visibleWhen for sliders that are meaningful only in some mode/type/source/include/count states; inactive branches should disappear so the panel shows only controls usable in the current state.",
|
|
568
599
|
);
|
|
569
600
|
expect(slider.aiUsageRules).toContain(
|
|
570
|
-
"
|
|
601
|
+
"Do not use schema disabled: true or disabledWhen for product sliders; product panels should show only controls usable in the current state. Use visibleWhen for unavailable product states instead of rendering disabled controls.",
|
|
571
602
|
);
|
|
572
603
|
expect(slider.aiUsageRules).toContain(
|
|
573
|
-
"Do not leave
|
|
604
|
+
"Do not leave an inactive conditional slider visible while making the renderer ignore it; hide it with visibleWhen.",
|
|
574
605
|
);
|
|
575
606
|
expect(rangeSlider.decisionCatalog.requiredAcceptance).toContain(
|
|
576
607
|
"Prove dragging rangeSlider.lower and rangeSlider.upper both affect product output while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.",
|
|
@@ -627,13 +658,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
627
658
|
"Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
|
|
628
659
|
);
|
|
629
660
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
630
|
-
"Use
|
|
661
|
+
"Use visibleWhen for range sliders that are meaningful only in some mode/type/source/include/count states; inactive branches should disappear so the panel shows only controls usable in the current state.",
|
|
631
662
|
);
|
|
632
663
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
633
|
-
"
|
|
664
|
+
"Do not use schema disabled: true or disabledWhen for product range sliders; product panels should show only controls usable in the current state. Use visibleWhen for unavailable product states instead of rendering disabled controls.",
|
|
634
665
|
);
|
|
635
666
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
636
|
-
"Do not leave
|
|
667
|
+
"Do not leave an inactive conditional range slider visible while making the renderer ignore it; hide it with visibleWhen.",
|
|
637
668
|
);
|
|
638
669
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
639
670
|
"Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
|
|
@@ -803,6 +834,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
803
834
|
expect(contract.aiUsageRules).toContain(
|
|
804
835
|
"Every app entity introduced by the AI must have an acceptance test that proves its product responsibility.",
|
|
805
836
|
);
|
|
837
|
+
expect(contract.aiUsageRules).toContain(
|
|
838
|
+
"Any supplied video, GIF, screen-recording, contact-sheet, or extracted-frame reference requires a Video Reference Study before implementation: storyboard frames, frame-to-frame transition analysis, behavior decomposition, and acceptance mapping. Do not implement video references from a single screenshot or high-level summary.",
|
|
839
|
+
);
|
|
806
840
|
expect(contract.aiUsageRules).toContain(
|
|
807
841
|
"Compound control browser tests must explicitly exercise each required value part, not only one visible sub-control.",
|
|
808
842
|
);
|
|
@@ -822,16 +856,19 @@ describe("Toolcraft template component contracts", () => {
|
|
|
822
856
|
"Conditional entities require fixtures that make the condition observable.",
|
|
823
857
|
);
|
|
824
858
|
expect(contract.aiUsageRules).toContain(
|
|
825
|
-
"Use visibleWhen for mode-, type-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
|
|
859
|
+
"Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
|
|
826
860
|
);
|
|
827
861
|
expect(contract.aiUsageRules).toContain(
|
|
828
862
|
"When a count/quantity control determines how many sibling controls are available, hide unavailable siblings with visibleWhen; do not render all possible controls while the renderer reads only the first N.",
|
|
829
863
|
);
|
|
830
864
|
expect(contract.aiUsageRules).toContain(
|
|
831
|
-
"
|
|
865
|
+
"If a switch/select/segmented/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use visibleWhen, not disabledWhen.",
|
|
832
866
|
);
|
|
833
867
|
expect(contract.aiUsageRules).toContain(
|
|
834
|
-
"Do not
|
|
868
|
+
"Do not use schema disabled: true or disabledWhen for generated product controls; the panel should show only controls usable in the current state. Runtime primitives may still have disabled styling internally, but app schemas should model product availability with visibleWhen.",
|
|
869
|
+
);
|
|
870
|
+
expect(contract.aiUsageRules).toContain(
|
|
871
|
+
"Do not leave inactive conditional controls visible while making the renderer ignore them.",
|
|
835
872
|
);
|
|
836
873
|
});
|
|
837
874
|
|
|
@@ -867,7 +904,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
867
904
|
"Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
|
|
868
905
|
);
|
|
869
906
|
expect(contract.aiUsageRules).toContain(
|
|
870
|
-
"A full performance checkpoint must run
|
|
907
|
+
"A full performance checkpoint must run only when the first working app version exists or the user requests performance, lag, jank, animation speed, drag/zoom stabilization work, or otherwise complains about performance; use the agent-controlled browser first and pnpm verify:perf only as the Playwright fallback.",
|
|
871
908
|
);
|
|
872
909
|
expect(contract.aiUsageRules).toContain(
|
|
873
910
|
"Renderer, canvas, animation, export, timeline, layers, canvas.renderScale, bug fixes, and performance-sensitive control changes use targeted functional/browser checks first and targeted performance scenarios only for touched workload, viewport, or export paths.",
|
|
@@ -992,12 +1029,21 @@ describe("Toolcraft template component contracts", () => {
|
|
|
992
1029
|
expect(contract.aiUsageRules).toContain(
|
|
993
1030
|
"Reference clone specs must list every detected transport behavior explicitly, including pause-resume, restart, time-progress, export-at-time, playback, scrub, duration, loop, and keyframes when present.",
|
|
994
1031
|
);
|
|
1032
|
+
expect(contract.aiUsageRules).toContain(
|
|
1033
|
+
'Toolcraft reference timelines must declare referenceTimeline.loopDuration with source "reference", "user-request", or "product-derived", plus seconds and evidence; runtime/template fallback 8s is not a valid source.',
|
|
1034
|
+
);
|
|
1035
|
+
expect(contract.aiUsageRules).toContain(
|
|
1036
|
+
"panels.timeline.defaultDurationSeconds must match referenceTimeline.loopDuration.seconds for referenceTimeline.mode toolcraft-playback/toolcraft-keyframes.",
|
|
1037
|
+
);
|
|
995
1038
|
expect(contract.aiUsageRules).toContain(
|
|
996
1039
|
'Do not downgrade custom reference timelines to panels.timeline mode "playback". State buttons, trim handles, selected-range playback, or range export require referenceTimeline.mode "custom-reference-timeline" and dedicated acceptance.',
|
|
997
1040
|
);
|
|
998
1041
|
expect(contract.aiUsageRules).toContain(
|
|
999
1042
|
"Reference clone acceptance must include referenceCoverage rows for canvas sizing, control mapping, renderer state, and any renderer loop, spawn/update cadence, pause/resume, export/copy, or media lifecycle behavior in the reference.",
|
|
1000
1043
|
);
|
|
1044
|
+
expect(contract.aiUsageRules).toContain(
|
|
1045
|
+
"Video reference assets used in a reference clone also require starterTransferMode.videoReferenceStudy with storyboard frames, frame-to-frame transition analysis, behavior decomposition, and acceptance mapping.",
|
|
1046
|
+
);
|
|
1001
1047
|
});
|
|
1002
1048
|
|
|
1003
1049
|
it("documents concise control label rules", () => {
|
|
@@ -1024,22 +1070,22 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1024
1070
|
"Controls-panel sections should stay discrete: two to seven product controls is the normal size, and larger sections must split by product sub-entity or workflow stage.",
|
|
1025
1071
|
);
|
|
1026
1072
|
expect(contract.aiUsageRules).toContain(
|
|
1027
|
-
"Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created
|
|
1073
|
+
"Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created Setup renders as the first visible headerless controls block with no title, reset action, collapse button, or collapsed state; sticky footer action sections use the technical title Export but render without a visible heading.",
|
|
1028
1074
|
);
|
|
1029
1075
|
expect(contract.aiUsageRules).toContain(
|
|
1030
|
-
"Every visible controls-panel section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon; generated apps must not hand-build section headers.",
|
|
1076
|
+
"Every visible app-authored controls-panel section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon; generated apps must not hand-build section headers.",
|
|
1031
1077
|
);
|
|
1032
1078
|
expect(contract.aiUsageRules).toContain(
|
|
1033
1079
|
"Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
|
|
1034
1080
|
);
|
|
1035
1081
|
expect(contract.aiUsageRules).toContain(
|
|
1036
|
-
"
|
|
1082
|
+
"Controls-panel section collapsed/expanded state persists as a runtime UI preference per app. It is not undo/redo state, not settings import/export state, and Reset controls must not clear it. Runtime Setup is not collapsible; sticky footer Export sections are not collapsible.",
|
|
1037
1083
|
);
|
|
1038
1084
|
expect(contract.aiUsageRules).toContain(
|
|
1039
1085
|
"Ordinary controls-panel section headers expose the runtime section reset action before the collapse button; it dispatches controls.resetTargets and restores only that section's control targets to their schema defaultValue.",
|
|
1040
1086
|
);
|
|
1041
1087
|
expect(contract.aiUsageRules).toContain(
|
|
1042
|
-
"
|
|
1088
|
+
"Runtime Setup and ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Sticky footer action sections keep their dedicated spacing.",
|
|
1043
1089
|
);
|
|
1044
1090
|
expect(contract.aiUsageRules).toContain(
|
|
1045
1091
|
"Broad section titles such as Flow, Icon, Shapes, Scene, Text, Typography, or Motion are only valid for small cohesive groups; use specific titles such as Flow Motion, Flow Geometry, Letter Burst, Shape Colors, Logo Glow, Logo Plate, or Text Block for larger groups.",
|
|
@@ -1129,21 +1175,51 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1129
1175
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.commands).toContain(
|
|
1130
1176
|
"timeline.togglePlayback",
|
|
1131
1177
|
);
|
|
1178
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.commands).toContain(
|
|
1179
|
+
"panels.setHidden",
|
|
1180
|
+
);
|
|
1132
1181
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.capabilities).toContain("keyframes");
|
|
1133
1182
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1134
|
-
"
|
|
1183
|
+
"Any product output animation must enable the top Toolcraft timeline; do not ship animated product output with only local requestAnimationFrame playback.",
|
|
1135
1184
|
);
|
|
1136
1185
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1137
1186
|
"Before choosing no timeline for any animated product, write an Animation Intent Inventory: product transport, editable keyframes, or autonomous decorative output, plus the user-facing time behaviors present or intentionally absent.",
|
|
1138
1187
|
);
|
|
1139
1188
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1140
|
-
'User-requested product animation defaults to panels.timeline mode "playback" unless the spec explicitly declares autonomous decorative/self-running output with no play, pause, scrub, duration, loop,
|
|
1189
|
+
'User-requested product animation defaults to panels.timeline mode "playback" unless the spec explicitly declares autonomous decorative/self-running output with no play, pause, scrub, duration, loop, export-at-time behavior, or video export.',
|
|
1190
|
+
);
|
|
1191
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1192
|
+
'Any product app with Export Video must enable the top Toolcraft timeline: use panels.timeline mode "playback" for product animation transport, or mode "keyframes" when exported animation is driven by keyframes.',
|
|
1193
|
+
);
|
|
1194
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1195
|
+
"Video export renderers must render deterministic frames from runtime timeline timestamps and duration; do not treat autonomous wall-clock time or captureStream recording time as the source of product duration.",
|
|
1141
1196
|
);
|
|
1142
1197
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1143
1198
|
"Playback renderers must consume runtime timeline state; pause freezes output, scrubbing renders a deterministic frame, and the full animation cycle maps to state.timeline.durationSeconds instead of a local fixed duration.",
|
|
1144
1199
|
);
|
|
1145
1200
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1146
|
-
|
|
1201
|
+
'When panels.timeline is enabled for a new Toolcraft app, appTransferMode.animationIntent must match it: mode "timeline-playback" for playback, or mode "timeline-keyframes" for keyframes.',
|
|
1202
|
+
);
|
|
1203
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1204
|
+
"When the product has a known loop duration, declare it as panels.timeline.defaultDurationSeconds; the runtime timeline duration starts from that loop duration instead of an unrelated 8s default.",
|
|
1205
|
+
);
|
|
1206
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1207
|
+
'Timeline animation intent must declare loopDuration with source "reference", "user-request", or "product-derived", plus seconds and evidence; runtime/template fallback 8s is not a valid source.',
|
|
1208
|
+
);
|
|
1209
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1210
|
+
"panels.timeline.defaultDurationSeconds must match animationIntent.loopDuration.seconds for playback/keyframe animation, so the initial timeline UI shows the declared product loop instead of a generic default.",
|
|
1211
|
+
);
|
|
1212
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1213
|
+
"Playback renderers may compute an initial loop duration default during app initialization or reset, but must not watch state.timeline.durationSeconds and dispatch timeline.setDuration back to a computed local duration. User-edited timeline duration becomes the loop duration source of truth after initialization or reset.",
|
|
1214
|
+
);
|
|
1215
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1216
|
+
"Playback renderers should use getToolcraftTimelineLoopTime or getToolcraftTimelineLoopProgress to derive product loop phase from state.timeline.currentTimeSeconds and state.timeline.durationSeconds; do not hand-roll wall-clock, fixed-duration, mirror, yoyo, ping-pong, or reverse phase math.",
|
|
1217
|
+
);
|
|
1218
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1219
|
+
"Product animation loop means a seamless forward-only cycle by default: motion advances in one direction, first and last frames stitch without a visible jump, and mirror/yoyo/ping-pong/reverse loops require explicit user request.",
|
|
1220
|
+
);
|
|
1221
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1222
|
+
"Changing timeline duration must preserve seamless forward-loop semantics: one full product animation cycle maps from 0 to state.timeline.durationSeconds, first and last frames still stitch, direction does not reverse, and changing duration must not switch the renderer to wall-clock or fixed local speed.",
|
|
1147
1223
|
);
|
|
1148
1224
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1149
1225
|
"When non-looping playback reaches the end, pressing Play again restarts playback from time 0.",
|
|
@@ -1187,6 +1263,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1187
1263
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1188
1264
|
'Custom timeline UI is allowed only for explicit referenceTimeline.mode "custom-reference-timeline" transfers with browser-backed referenceTimelineCoverage.',
|
|
1189
1265
|
);
|
|
1266
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1267
|
+
'When panels.timeline is enabled, runtime inserts a Setup switch labeled "Timeline" as the last runtime setup control after Resolution scale.',
|
|
1268
|
+
);
|
|
1269
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1270
|
+
"The Timeline Setup switch controls only compact versus extended runtime presentation: off shows the compact Play-only transport, on shows the extended timeline with scrubber, duration, loop, and keyframe UI. It must not stop playback, change keyframes, affect export, enter product values, or be reset by Reset controls/settings transfer.",
|
|
1271
|
+
);
|
|
1190
1272
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1191
1273
|
"If timeline verification fails, wire the renderer to runtime timeline state or remove panels.timeline.",
|
|
1192
1274
|
);
|
|
@@ -1215,6 +1297,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1215
1297
|
expect(contract.aiUsageRules).toContain(
|
|
1216
1298
|
"Product-output apps must always include export in panelActions.",
|
|
1217
1299
|
);
|
|
1300
|
+
expect(contract.aiUsageRules).toContain(
|
|
1301
|
+
'Export-labeled panelActions use icon "upload-simple", matching the Setup "Export Settings" action; do not use "download", "download-simple", or "export" icons for Export PNG or Export Video.',
|
|
1302
|
+
);
|
|
1218
1303
|
expect(contract.aiUsageRules).toContain(
|
|
1219
1304
|
"Static or still-output apps include Export PNG as the primary footer action.",
|
|
1220
1305
|
);
|
|
@@ -1236,6 +1321,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1236
1321
|
expect(contract.aiUsageRules).toContain(
|
|
1237
1322
|
"Animated apps include Export Video as the primary footer action and Export PNG as a secondary footer action.",
|
|
1238
1323
|
);
|
|
1324
|
+
expect(contract.aiUsageRules).toContain(
|
|
1325
|
+
"Any app with Export Video must enable the top Toolcraft timeline; video duration, loop, and rendered timestamps come from runtime timeline state.",
|
|
1326
|
+
);
|
|
1239
1327
|
expect(contract.aiUsageRules).toContain(
|
|
1240
1328
|
'Animated apps with Export Video must expose a separate "Video Export" controls section.',
|
|
1241
1329
|
);
|
|
@@ -1266,6 +1354,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1266
1354
|
expect(contract.aiUsageRules).toContain(
|
|
1267
1355
|
"Video export code must choose the actual MIME/container through MediaRecorder.isTypeSupported or an equivalent encoder capability check, then fall back safely.",
|
|
1268
1356
|
);
|
|
1357
|
+
expect(contract.aiUsageRules).toContain(
|
|
1358
|
+
"Video export must use getToolcraftVideoExportSize for current and 4K dimensions. Current video export uses the current canvas/output size with even encoder-safe rounding; 4K video fits inside an encoder-safe 3840x2160 box, preserves canvas aspect ratio, and uses even pixel dimensions. Do not hand-roll 4096px long-edge video sizing.",
|
|
1359
|
+
);
|
|
1360
|
+
expect(contract.aiUsageRules).toContain(
|
|
1361
|
+
"Video export must set recording canvas dimensions before captureStream, MediaRecorder, VideoEncoder, or equivalent encoder setup, and must reject recorder/encoder errors instead of returning corrupt blobs.",
|
|
1362
|
+
);
|
|
1269
1363
|
expect(contract.aiUsageRules).toContain(
|
|
1270
1364
|
"Offline video export duration must be encoded from runtime timeline timestamps. Do not rely on canvas.captureStream plus MediaRecorder wall-clock recording time as the only duration mechanism for rendered-frame export.",
|
|
1271
1365
|
);
|
|
@@ -1288,7 +1382,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1288
1382
|
"PNG export must use createToolcraftPngExportCanvas so background transparency and selected image dimensions or retina fallback are applied consistently; turning Include off makes preview product background and PNG alpha transparent without hiding the Toolcraft canvas backing or video background.",
|
|
1289
1383
|
);
|
|
1290
1384
|
expect(contract.aiUsageRules).toContain(
|
|
1291
|
-
"Video export must keep product background and use
|
|
1385
|
+
"Video export must keep product background and use getToolcraftVideoExportSize for current/4K output dimensions.",
|
|
1292
1386
|
);
|
|
1293
1387
|
expect(contract.aiUsageRules).toContain(
|
|
1294
1388
|
"Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
|
|
@@ -1310,10 +1404,21 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1310
1404
|
it("documents file upload ownership across single-layer and multi-layer apps", () => {
|
|
1311
1405
|
const contract = getToolcraftComponentContract("fileDrop");
|
|
1312
1406
|
|
|
1313
|
-
expect(contract.commands).toEqual([
|
|
1407
|
+
expect(contract.commands).toEqual([
|
|
1408
|
+
"media.delete",
|
|
1409
|
+
"media.import",
|
|
1410
|
+
"media.reorder",
|
|
1411
|
+
"media.transform",
|
|
1412
|
+
]);
|
|
1314
1413
|
expect(contract.aiUsageRules).toContain(
|
|
1315
1414
|
'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
|
|
1316
1415
|
);
|
|
1416
|
+
expect(contract.aiUsageRules).toContain(
|
|
1417
|
+
"If an app ships with predefined source files or background images, declare them as schema media.defaultAssets with sourceTarget matching the fileDrop control. They must render as ordinary attached files in fileDrop, not as hidden renderer constants or canvas placeholder artwork.",
|
|
1418
|
+
);
|
|
1419
|
+
expect(contract.aiUsageRules).toContain(
|
|
1420
|
+
"Predefined media files are default runtime state: users can remove them to get an empty source/canvas state, persistence may keep that removal with include: [\"media\"], and global or section Reset restores the default attached files.",
|
|
1421
|
+
);
|
|
1317
1422
|
expect(contract.aiUsageRules).toContain(
|
|
1318
1423
|
"When uploaded/imported content is part of the source-material flow, the canvas must not show agent-invented artwork, CTA text, fake sample output, decorative placeholders, or preset source designs before real content exists; keep the canvas neutral/runtime-backed and put upload affordance in fileDrop.",
|
|
1319
1424
|
);
|
|
@@ -1323,11 +1428,17 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1323
1428
|
expect(contract.aiUsageRules).toContain(
|
|
1324
1429
|
"In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
|
|
1325
1430
|
);
|
|
1431
|
+
expect(contract.aiUsageRules).toContain(
|
|
1432
|
+
"In image mode, the runtime owns image transform actions: 90° Right, Flip horizontal, and Flip vertical. These actions render through the built-in actions-control in one three-column row with compact visible labels: 90°, Flip H, Flip V; keep a 6px vertical gap between the uploader and action row. Do not create a custom image action button grid. They update runtime mediaAssets transform metadata, and product preview/export must consume that metadata instead of keeping a separate transform state.",
|
|
1433
|
+
);
|
|
1434
|
+
expect(contract.aiUsageRules).toContain(
|
|
1435
|
+
"When exactly one uploaded image is present, image transform actions are visible immediately. When multiple images are present, users select a thumbnail first; no transform actions render until a thumbnail is selected, and the action applies only to that selected image.",
|
|
1436
|
+
);
|
|
1326
1437
|
expect(contract.aiUsageRules).toContain(
|
|
1327
1438
|
"In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
|
|
1328
1439
|
);
|
|
1329
1440
|
expect(contract.aiUsageRules).toContain(
|
|
1330
|
-
"In single-layer apps, global Reset controls and section reset must
|
|
1441
|
+
"In single-layer apps, global Reset controls and section reset must restore fileDrop source media to schema media.defaultAssets for that target; when no default asset exists, Reset removes uploaded media and returns the fileDrop target to defaultValue.",
|
|
1331
1442
|
);
|
|
1332
1443
|
expect(contract.aiUsageRules).toContain(
|
|
1333
1444
|
"Use fileDrop with multiple: true when the app needs several uploaded images as one source set; do not build a custom thumbnail uploader for this.",
|
|
@@ -1341,6 +1452,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1341
1452
|
expect(contract.aiUsageRules).toContain(
|
|
1342
1453
|
"Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
|
|
1343
1454
|
);
|
|
1455
|
+
expect(contract.aiUsageRules).toContain(
|
|
1456
|
+
"When uploaded images are used as canvas/background source material, draw them with cover/crop behavior: scale proportionally until the current canvas bounds are fully covered, keep the canvas size/settings unchanged, and crop overflow at the canvas bounds.",
|
|
1457
|
+
);
|
|
1344
1458
|
expect(contract.aiUsageRules).toContain(
|
|
1345
1459
|
"Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
|
|
1346
1460
|
);
|