@pixel-point/toolcraft 0.0.9 → 0.0.12
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 +2 -1
- package/src/cli.mjs +4 -2
- package/src/cli.test.mjs +5 -2
- package/src/generate.mjs +14 -6
- package/src/generate.test.mjs +33 -2
- package/templates/runtime/contracts/component-contracts.test.ts +175 -36
- package/templates/runtime/contracts/component-contracts.ts +90 -45
- 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 +478 -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 +41 -8
- 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 +58 -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 +57 -4
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -2
- 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 +130 -8
- 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 +19 -5
- 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
|
@@ -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.",
|
|
@@ -147,6 +153,28 @@ describe("Toolcraft template component contracts", () => {
|
|
|
147
153
|
expect(contract.aiUsageRules).toContain(
|
|
148
154
|
"Multiple vector controls should live in separate semantic sections unless they intentionally belong to the same entity with other related controls.",
|
|
149
155
|
);
|
|
156
|
+
expect(contract.decisionCatalog?.ownsValueModel).toEqual(
|
|
157
|
+
expect.arrayContaining([
|
|
158
|
+
"user-authored stable position",
|
|
159
|
+
"user-authored stable offset",
|
|
160
|
+
"user-authored stable direction",
|
|
161
|
+
]),
|
|
162
|
+
);
|
|
163
|
+
expect(contract.decisionCatalog?.useWhen).toContain(
|
|
164
|
+
"Use Vector for paired X/Y values only when the user is meant to manually author a stable two-axis product parameter such as position, offset, direction, focus, anchor, light direction, or color-balance movement.",
|
|
165
|
+
);
|
|
166
|
+
expect(contract.decisionCatalog?.doNotReplaceWith).toContain(
|
|
167
|
+
"Do not replace animation, keyboard input, pointer input, physics, timeline phase, velocity, or simulated pose state with Vector just because the internal value has x/y coordinates.",
|
|
168
|
+
);
|
|
169
|
+
expect(contract.decisionCatalog?.acceptableAlternatives).toContain(
|
|
170
|
+
"Use timeline, keyboard/pointer handlers, motion sliders, path/step controls, or renderer simulation state when movement is generated by animation or user input rather than authored as a stable panel value.",
|
|
171
|
+
);
|
|
172
|
+
expect(contract.aiUsageRules).toContain(
|
|
173
|
+
"Use Vector only for user-authored stable two-axis product parameters. Do not expose Vector for current animation state, keyboard movement, pointer movement, physics state, timeline phase, velocity, target pose, current pose, or simulated position/direction.",
|
|
174
|
+
);
|
|
175
|
+
expect(contract.aiUsageRules).toContain(
|
|
176
|
+
"Before adding a Vector control to an animated or interactive product, classify movement ownership as direct-authored, timeline-driven, keyboard/pointer-driven, or simulation-owned. Only direct-authored movement may become a visible Vector control; the other ownership modes stay in renderer/runtime interaction state and use controls such as Speed, Step, Spread, Path, Duration, or Timeline when the user needs tuning.",
|
|
177
|
+
);
|
|
150
178
|
expect(contract.aiUsageRules).toContain(
|
|
151
179
|
'Use variant: "whiteBalance" for temperature/tint pads: X maps cool blue to warm amber, Y maps green to magenta.',
|
|
152
180
|
);
|
|
@@ -160,13 +188,19 @@ describe("Toolcraft template component contracts", () => {
|
|
|
160
188
|
'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
189
|
);
|
|
162
190
|
expect(contract.aiUsageRules).toContain(
|
|
163
|
-
"
|
|
191
|
+
"Vector pad value labels render compact rounded coordinates. Do not expose raw floating-point strings such as -0.07070312499999998 in the controls panel.",
|
|
192
|
+
);
|
|
193
|
+
expect(contract.aiUsageRules).toContain(
|
|
194
|
+
"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.",
|
|
195
|
+
);
|
|
196
|
+
expect(contract.aiUsageRules).toContain(
|
|
197
|
+
"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
198
|
);
|
|
165
199
|
expect(contract.aiUsageRules).toContain(
|
|
166
200
|
'Use coordinateMode: "cartesian" only when the product intentionally exposes mathematical Y-up coordinates instead of canvas/screen movement.',
|
|
167
201
|
);
|
|
168
202
|
expect(contract.aiUsageRules).toContain(
|
|
169
|
-
"Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output.",
|
|
203
|
+
"Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output and that the vector represents a user-authored stable two-axis parameter rather than current animation, input, or simulation state.",
|
|
170
204
|
);
|
|
171
205
|
});
|
|
172
206
|
|
|
@@ -361,6 +395,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
361
395
|
expect(contract.aiUsageRules).toContain(
|
|
362
396
|
"Do not use Palette when opacity belongs to the same color entity; use ColorOpacity instead.",
|
|
363
397
|
);
|
|
398
|
+
expect(contract.decisionCatalog?.requiredAcceptance).toContain(
|
|
399
|
+
"Prove Palette family and shade selections update runtime state immediately, before delayed persistence/commit settles.",
|
|
400
|
+
);
|
|
401
|
+
expect(contract.aiUsageRules).toContain(
|
|
402
|
+
"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.",
|
|
403
|
+
);
|
|
364
404
|
expect(contract.aiUsageRules).toContain(
|
|
365
405
|
"Do not use Palette for gradients or color transitions; use Gradient instead.",
|
|
366
406
|
);
|
|
@@ -402,13 +442,20 @@ describe("Toolcraft template component contracts", () => {
|
|
|
402
442
|
expect(contract.commands).toContain("canvas.panBy");
|
|
403
443
|
expect(contract.commands).toContain("canvas.setOffset");
|
|
404
444
|
expect(contract.commands).toContain("media.import");
|
|
445
|
+
expect(contract.commands).toContain("media.transform");
|
|
405
446
|
expect(contract.capabilities).toContain("editable-size");
|
|
406
447
|
expect(contract.aiUsageRules).toContain(
|
|
407
|
-
"
|
|
448
|
+
"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.",
|
|
449
|
+
);
|
|
450
|
+
expect(contract.aiUsageRules).toContain(
|
|
451
|
+
"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
452
|
);
|
|
409
453
|
expect(contract.aiUsageRules).toContain(
|
|
410
454
|
"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
455
|
);
|
|
456
|
+
expect(contract.aiUsageRules).toContain(
|
|
457
|
+
"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.",
|
|
458
|
+
);
|
|
412
459
|
expect(contract.aiUsageRules).toContain(
|
|
413
460
|
"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
461
|
);
|
|
@@ -419,13 +466,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
419
466
|
"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
467
|
);
|
|
421
468
|
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
|
|
469
|
+
"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
470
|
);
|
|
424
471
|
expect(contract.aiUsageRules).toContain(
|
|
425
472
|
"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
473
|
);
|
|
427
474
|
expect(contract.aiUsageRules).toContain(
|
|
428
|
-
"The runtime Canvas width and Canvas height block uses the
|
|
475
|
+
"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
476
|
);
|
|
430
477
|
expect(contract.aiUsageRules).toContain(
|
|
431
478
|
"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 +505,10 @@ describe("Toolcraft template component contracts", () => {
|
|
|
458
505
|
"Use schema persistence policy for app state that should survive reload.",
|
|
459
506
|
);
|
|
460
507
|
expect(contract.aiUsageRules).toContain(
|
|
461
|
-
"Persistence may include values, canvas, panels, timeline,
|
|
508
|
+
"Persistence may include values, canvas, panels, timeline, layers, and media; history is not persisted.",
|
|
509
|
+
);
|
|
510
|
+
expect(contract.aiUsageRules).toContain(
|
|
511
|
+
"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
512
|
);
|
|
463
513
|
expect(contract.aiUsageRules).toContain(
|
|
464
514
|
'Apps with visible runtime panels and localStorage persistence must include "panels" so dragged panel positions survive reload.',
|
|
@@ -467,32 +517,35 @@ describe("Toolcraft template component contracts", () => {
|
|
|
467
517
|
'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
518
|
);
|
|
469
519
|
expect(contract.aiUsageRules).toContain(
|
|
470
|
-
"Settings import/export is a preset transfer feature
|
|
520
|
+
"Settings import/export is a mandatory runtime preset transfer feature; it must not be used to hide or replace broken persistence reload behavior.",
|
|
471
521
|
);
|
|
472
522
|
});
|
|
473
523
|
|
|
474
|
-
it("documents settings transfer as a runtime-owned
|
|
524
|
+
it("documents settings transfer as a mandatory runtime-owned feature", () => {
|
|
475
525
|
const contract = getToolcraftComponentContract("settingsTransfer");
|
|
476
526
|
|
|
477
527
|
expect(contract.kind).toBe("settings");
|
|
478
528
|
expect(contract.stateMode).toBe("runtime-owned");
|
|
479
529
|
expect(contract.aiUsageRules).toContain(
|
|
480
|
-
|
|
530
|
+
"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
531
|
);
|
|
482
532
|
expect(contract.aiUsageRules).toContain(
|
|
483
|
-
"
|
|
533
|
+
"Do not gate Export Settings / Import Settings behind complexity thresholds, app size, or prompt wording.",
|
|
484
534
|
);
|
|
485
535
|
expect(contract.aiUsageRules).toContain(
|
|
486
536
|
"Do not hand-roll settings import/export through app routes, hidden file inputs, or panelActions.",
|
|
487
537
|
);
|
|
488
538
|
expect(contract.aiUsageRules).toContain(
|
|
489
|
-
"Settings transfer appears
|
|
539
|
+
"Settings transfer appears in the first visible headerless Setup controls-panel block; it imports and exports control values, canvas size, and timeline state.",
|
|
490
540
|
);
|
|
491
541
|
expect(contract.aiUsageRules).toContain(
|
|
492
|
-
"
|
|
542
|
+
"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.",
|
|
493
543
|
);
|
|
494
544
|
expect(contract.aiUsageRules).toContain(
|
|
495
|
-
"When
|
|
545
|
+
"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.",
|
|
546
|
+
);
|
|
547
|
+
expect(contract.aiUsageRules).toContain(
|
|
548
|
+
"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
549
|
);
|
|
497
550
|
});
|
|
498
551
|
|
|
@@ -564,13 +617,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
564
617
|
"Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
|
|
565
618
|
);
|
|
566
619
|
expect(slider.aiUsageRules).toContain(
|
|
567
|
-
"Use
|
|
620
|
+
"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
621
|
);
|
|
569
622
|
expect(slider.aiUsageRules).toContain(
|
|
570
|
-
"
|
|
623
|
+
"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
624
|
);
|
|
572
625
|
expect(slider.aiUsageRules).toContain(
|
|
573
|
-
"Do not leave
|
|
626
|
+
"Do not leave an inactive conditional slider visible while making the renderer ignore it; hide it with visibleWhen.",
|
|
574
627
|
);
|
|
575
628
|
expect(rangeSlider.decisionCatalog.requiredAcceptance).toContain(
|
|
576
629
|
"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.",
|
|
@@ -621,19 +674,19 @@ describe("Toolcraft template component contracts", () => {
|
|
|
621
674
|
"RangeSlider defaultValue must start with different lower and upper values so the two-thumb control does not collapse into a single-value slider.",
|
|
622
675
|
);
|
|
623
676
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
624
|
-
"Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes
|
|
677
|
+
"Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes, including when values include unit suffixes such as 30%-150% or 30% - 90%; do not create custom parsers for RangeSlider labels.",
|
|
625
678
|
);
|
|
626
679
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
627
680
|
"Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
|
|
628
681
|
);
|
|
629
682
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
630
|
-
"Use
|
|
683
|
+
"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
684
|
);
|
|
632
685
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
633
|
-
"
|
|
686
|
+
"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
687
|
);
|
|
635
688
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
636
|
-
"Do not leave
|
|
689
|
+
"Do not leave an inactive conditional range slider visible while making the renderer ignore it; hide it with visibleWhen.",
|
|
637
690
|
);
|
|
638
691
|
expect(rangeSlider.aiUsageRules).toContain(
|
|
639
692
|
"Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
|
|
@@ -763,6 +816,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
763
816
|
expect(anchorGrid.aiUsageRules).toContain(
|
|
764
817
|
"AnchorGrid is a position selector; acceptance must prove anchorGrid.position changes product placement, not only selected button state.",
|
|
765
818
|
);
|
|
819
|
+
expect(anchorGrid.decisionCatalog?.acceptableAlternatives).toContain(
|
|
820
|
+
"Use Vector only for stable direct-authored continuous position or direction parameters.",
|
|
821
|
+
);
|
|
766
822
|
expect(channelMixer.aiUsageRules).toContain(
|
|
767
823
|
"ChannelMixer is RGB-specific: it renders R/G/B tabs and Red, Green, Blue sliders for an RGB channel matrix.",
|
|
768
824
|
);
|
|
@@ -803,6 +859,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
803
859
|
expect(contract.aiUsageRules).toContain(
|
|
804
860
|
"Every app entity introduced by the AI must have an acceptance test that proves its product responsibility.",
|
|
805
861
|
);
|
|
862
|
+
expect(contract.aiUsageRules).toContain(
|
|
863
|
+
"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.",
|
|
864
|
+
);
|
|
806
865
|
expect(contract.aiUsageRules).toContain(
|
|
807
866
|
"Compound control browser tests must explicitly exercise each required value part, not only one visible sub-control.",
|
|
808
867
|
);
|
|
@@ -822,16 +881,19 @@ describe("Toolcraft template component contracts", () => {
|
|
|
822
881
|
"Conditional entities require fixtures that make the condition observable.",
|
|
823
882
|
);
|
|
824
883
|
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.",
|
|
884
|
+
"Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
|
|
826
885
|
);
|
|
827
886
|
expect(contract.aiUsageRules).toContain(
|
|
828
887
|
"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
888
|
);
|
|
830
889
|
expect(contract.aiUsageRules).toContain(
|
|
831
|
-
"
|
|
890
|
+
"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
891
|
);
|
|
833
892
|
expect(contract.aiUsageRules).toContain(
|
|
834
|
-
"Do not
|
|
893
|
+
"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.",
|
|
894
|
+
);
|
|
895
|
+
expect(contract.aiUsageRules).toContain(
|
|
896
|
+
"Do not leave inactive conditional controls visible while making the renderer ignore them.",
|
|
835
897
|
);
|
|
836
898
|
});
|
|
837
899
|
|
|
@@ -867,7 +929,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
867
929
|
"Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
|
|
868
930
|
);
|
|
869
931
|
expect(contract.aiUsageRules).toContain(
|
|
870
|
-
"A full performance checkpoint must run
|
|
932
|
+
"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
933
|
);
|
|
872
934
|
expect(contract.aiUsageRules).toContain(
|
|
873
935
|
"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 +1054,21 @@ describe("Toolcraft template component contracts", () => {
|
|
|
992
1054
|
expect(contract.aiUsageRules).toContain(
|
|
993
1055
|
"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
1056
|
);
|
|
1057
|
+
expect(contract.aiUsageRules).toContain(
|
|
1058
|
+
'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.',
|
|
1059
|
+
);
|
|
1060
|
+
expect(contract.aiUsageRules).toContain(
|
|
1061
|
+
"panels.timeline.defaultDurationSeconds must match referenceTimeline.loopDuration.seconds for referenceTimeline.mode toolcraft-playback/toolcraft-keyframes.",
|
|
1062
|
+
);
|
|
995
1063
|
expect(contract.aiUsageRules).toContain(
|
|
996
1064
|
'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
1065
|
);
|
|
998
1066
|
expect(contract.aiUsageRules).toContain(
|
|
999
1067
|
"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
1068
|
);
|
|
1069
|
+
expect(contract.aiUsageRules).toContain(
|
|
1070
|
+
"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.",
|
|
1071
|
+
);
|
|
1001
1072
|
});
|
|
1002
1073
|
|
|
1003
1074
|
it("documents concise control label rules", () => {
|
|
@@ -1024,22 +1095,22 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1024
1095
|
"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
1096
|
);
|
|
1026
1097
|
expect(contract.aiUsageRules).toContain(
|
|
1027
|
-
"Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created
|
|
1098
|
+
"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
1099
|
);
|
|
1029
1100
|
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.",
|
|
1101
|
+
"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
1102
|
);
|
|
1032
1103
|
expect(contract.aiUsageRules).toContain(
|
|
1033
1104
|
"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
1105
|
);
|
|
1035
1106
|
expect(contract.aiUsageRules).toContain(
|
|
1036
|
-
"
|
|
1107
|
+
"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
1108
|
);
|
|
1038
1109
|
expect(contract.aiUsageRules).toContain(
|
|
1039
1110
|
"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
1111
|
);
|
|
1041
1112
|
expect(contract.aiUsageRules).toContain(
|
|
1042
|
-
"
|
|
1113
|
+
"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
1114
|
);
|
|
1044
1115
|
expect(contract.aiUsageRules).toContain(
|
|
1045
1116
|
"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 +1200,51 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1129
1200
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.commands).toContain(
|
|
1130
1201
|
"timeline.togglePlayback",
|
|
1131
1202
|
);
|
|
1203
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.commands).toContain(
|
|
1204
|
+
"panels.setHidden",
|
|
1205
|
+
);
|
|
1132
1206
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.capabilities).toContain("keyframes");
|
|
1133
1207
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1134
|
-
"
|
|
1208
|
+
"Any product output animation must enable the top Toolcraft timeline; do not ship animated product output with only local requestAnimationFrame playback.",
|
|
1135
1209
|
);
|
|
1136
1210
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1137
1211
|
"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
1212
|
);
|
|
1139
1213
|
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,
|
|
1214
|
+
'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.',
|
|
1215
|
+
);
|
|
1216
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1217
|
+
'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.',
|
|
1218
|
+
);
|
|
1219
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1220
|
+
"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
1221
|
);
|
|
1142
1222
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1143
1223
|
"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
1224
|
);
|
|
1145
1225
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1146
|
-
|
|
1226
|
+
'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.',
|
|
1227
|
+
);
|
|
1228
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1229
|
+
"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.",
|
|
1230
|
+
);
|
|
1231
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1232
|
+
'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.',
|
|
1233
|
+
);
|
|
1234
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1235
|
+
"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.",
|
|
1236
|
+
);
|
|
1237
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1238
|
+
"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.",
|
|
1239
|
+
);
|
|
1240
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1241
|
+
"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.",
|
|
1242
|
+
);
|
|
1243
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1244
|
+
"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.",
|
|
1245
|
+
);
|
|
1246
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1247
|
+
"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
1248
|
);
|
|
1148
1249
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1149
1250
|
"When non-looping playback reaches the end, pressing Play again restarts playback from time 0.",
|
|
@@ -1187,6 +1288,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1187
1288
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1188
1289
|
'Custom timeline UI is allowed only for explicit referenceTimeline.mode "custom-reference-timeline" transfers with browser-backed referenceTimelineCoverage.',
|
|
1189
1290
|
);
|
|
1291
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1292
|
+
'When panels.timeline is enabled, runtime inserts a Setup switch labeled "Timeline" as the last runtime setup control after Resolution scale.',
|
|
1293
|
+
);
|
|
1294
|
+
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1295
|
+
"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.",
|
|
1296
|
+
);
|
|
1190
1297
|
expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
|
|
1191
1298
|
"If timeline verification fails, wire the renderer to runtime timeline state or remove panels.timeline.",
|
|
1192
1299
|
);
|
|
@@ -1215,6 +1322,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1215
1322
|
expect(contract.aiUsageRules).toContain(
|
|
1216
1323
|
"Product-output apps must always include export in panelActions.",
|
|
1217
1324
|
);
|
|
1325
|
+
expect(contract.aiUsageRules).toContain(
|
|
1326
|
+
'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.',
|
|
1327
|
+
);
|
|
1218
1328
|
expect(contract.aiUsageRules).toContain(
|
|
1219
1329
|
"Static or still-output apps include Export PNG as the primary footer action.",
|
|
1220
1330
|
);
|
|
@@ -1236,6 +1346,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1236
1346
|
expect(contract.aiUsageRules).toContain(
|
|
1237
1347
|
"Animated apps include Export Video as the primary footer action and Export PNG as a secondary footer action.",
|
|
1238
1348
|
);
|
|
1349
|
+
expect(contract.aiUsageRules).toContain(
|
|
1350
|
+
"Any app with Export Video must enable the top Toolcraft timeline; video duration, loop, and rendered timestamps come from runtime timeline state.",
|
|
1351
|
+
);
|
|
1239
1352
|
expect(contract.aiUsageRules).toContain(
|
|
1240
1353
|
'Animated apps with Export Video must expose a separate "Video Export" controls section.',
|
|
1241
1354
|
);
|
|
@@ -1266,6 +1379,12 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1266
1379
|
expect(contract.aiUsageRules).toContain(
|
|
1267
1380
|
"Video export code must choose the actual MIME/container through MediaRecorder.isTypeSupported or an equivalent encoder capability check, then fall back safely.",
|
|
1268
1381
|
);
|
|
1382
|
+
expect(contract.aiUsageRules).toContain(
|
|
1383
|
+
"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.",
|
|
1384
|
+
);
|
|
1385
|
+
expect(contract.aiUsageRules).toContain(
|
|
1386
|
+
"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.",
|
|
1387
|
+
);
|
|
1269
1388
|
expect(contract.aiUsageRules).toContain(
|
|
1270
1389
|
"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
1390
|
);
|
|
@@ -1288,7 +1407,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1288
1407
|
"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
1408
|
);
|
|
1290
1409
|
expect(contract.aiUsageRules).toContain(
|
|
1291
|
-
"Video export must keep product background and use
|
|
1410
|
+
"Video export must keep product background and use getToolcraftVideoExportSize for current/4K output dimensions.",
|
|
1292
1411
|
);
|
|
1293
1412
|
expect(contract.aiUsageRules).toContain(
|
|
1294
1413
|
"Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
|
|
@@ -1310,10 +1429,21 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1310
1429
|
it("documents file upload ownership across single-layer and multi-layer apps", () => {
|
|
1311
1430
|
const contract = getToolcraftComponentContract("fileDrop");
|
|
1312
1431
|
|
|
1313
|
-
expect(contract.commands).toEqual([
|
|
1432
|
+
expect(contract.commands).toEqual([
|
|
1433
|
+
"media.delete",
|
|
1434
|
+
"media.import",
|
|
1435
|
+
"media.reorder",
|
|
1436
|
+
"media.transform",
|
|
1437
|
+
]);
|
|
1314
1438
|
expect(contract.aiUsageRules).toContain(
|
|
1315
1439
|
'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
|
|
1316
1440
|
);
|
|
1441
|
+
expect(contract.aiUsageRules).toContain(
|
|
1442
|
+
"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.",
|
|
1443
|
+
);
|
|
1444
|
+
expect(contract.aiUsageRules).toContain(
|
|
1445
|
+
"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.",
|
|
1446
|
+
);
|
|
1317
1447
|
expect(contract.aiUsageRules).toContain(
|
|
1318
1448
|
"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
1449
|
);
|
|
@@ -1323,11 +1453,17 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1323
1453
|
expect(contract.aiUsageRules).toContain(
|
|
1324
1454
|
"In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
|
|
1325
1455
|
);
|
|
1456
|
+
expect(contract.aiUsageRules).toContain(
|
|
1457
|
+
"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.",
|
|
1458
|
+
);
|
|
1459
|
+
expect(contract.aiUsageRules).toContain(
|
|
1460
|
+
"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.",
|
|
1461
|
+
);
|
|
1326
1462
|
expect(contract.aiUsageRules).toContain(
|
|
1327
1463
|
"In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
|
|
1328
1464
|
);
|
|
1329
1465
|
expect(contract.aiUsageRules).toContain(
|
|
1330
|
-
"In single-layer apps, global Reset controls and section reset must
|
|
1466
|
+
"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
1467
|
);
|
|
1332
1468
|
expect(contract.aiUsageRules).toContain(
|
|
1333
1469
|
"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 +1477,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1341
1477
|
expect(contract.aiUsageRules).toContain(
|
|
1342
1478
|
"Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
|
|
1343
1479
|
);
|
|
1480
|
+
expect(contract.aiUsageRules).toContain(
|
|
1481
|
+
"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.",
|
|
1482
|
+
);
|
|
1344
1483
|
expect(contract.aiUsageRules).toContain(
|
|
1345
1484
|
"Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
|
|
1346
1485
|
);
|