@pixel-point/toolcraft 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate.test.mjs +1 -1
- package/templates/runtime/contracts/component-contracts.test.ts +123 -15
- package/templates/runtime/contracts/component-contracts.ts +93 -19
- package/templates/runtime/contracts/decision-contracts.test.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.ts +2 -2
- package/templates/runtime/export/export.test.ts +31 -0
- package/templates/runtime/export/export.ts +41 -0
- package/templates/runtime/react/canvas-shell.test.tsx +77 -1
- package/templates/runtime/react/canvas-shell.tsx +178 -23
- package/templates/runtime/react/control-conditions.ts +166 -0
- package/templates/runtime/react/controls-panel-filedrop-reorder.test.tsx +176 -0
- package/templates/runtime/react/controls-panel.test.tsx +590 -9
- package/templates/runtime/react/controls-panel.tsx +472 -10
- package/templates/runtime/react/media-file.ts +19 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +2 -0
- package/templates/runtime/schema/define-toolcraft.ts +11 -3
- package/templates/runtime/schema/types.ts +27 -0
- package/templates/runtime/state/reducer.test.ts +357 -0
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/types.ts +12 -2
- package/templates/runtime/testing/performance.test.ts +1282 -48
- package/templates/runtime/testing/performance.ts +676 -39
- package/templates/starter/AGENTS.md +13 -12
- package/templates/starter/docs/toolcraft/README.md +4 -3
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -7
- package/templates/starter/docs/toolcraft/assembly-workflow.md +9 -7
- package/templates/starter/docs/toolcraft/component-rules.md +20 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +9 -5
- package/templates/starter/docs/toolcraft/performance.md +51 -13
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +11 -6
- package/templates/starter/docs/toolcraft/workflow.md +7 -10
- package/templates/starter/e2e/app-performance.spec.ts +136 -3
- package/templates/starter/e2e/performance-helpers.ts +197 -0
- package/templates/starter/package.json +4 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +529 -19
- package/templates/starter/src/app/starter-acceptance.ts +269 -12
- package/templates/starter/src/app/starter-performance.test.ts +67 -6
- package/templates/ui/components/controls/actions/actions-control.tsx +3 -5
- package/templates/ui/components/controls/collection-actions/collection-actions-control.tsx +60 -0
- package/templates/ui/components/controls/collection-actions/index.ts +4 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +340 -44
- package/templates/ui/components/controls/file-drop/index.ts +1 -1
- package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1 -6
- package/templates/ui/components/controls/index.ts +9 -0
- package/templates/ui/components/panel/panel-section.tsx +53 -1
- package/templates/ui/index.ts +1 -0
package/package.json
CHANGED
package/src/generate.test.mjs
CHANGED
|
@@ -95,7 +95,7 @@ describe("generateToolcraft", () => {
|
|
|
95
95
|
assert.equal(packageJson.scripts["verify:perf"], "pnpm test:browser:perf");
|
|
96
96
|
assert.equal(
|
|
97
97
|
packageJson.scripts["verify:final"],
|
|
98
|
-
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser
|
|
98
|
+
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser",
|
|
99
99
|
);
|
|
100
100
|
|
|
101
101
|
assert.ok(await fs.stat(path.join(targetDir, "playwright.config.ts")));
|
|
@@ -30,6 +30,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
30
30
|
"imagePicker",
|
|
31
31
|
"palette",
|
|
32
32
|
"actions",
|
|
33
|
+
"collectionActions",
|
|
33
34
|
"panelActions",
|
|
34
35
|
"customControl",
|
|
35
36
|
] as const) {
|
|
@@ -221,7 +222,13 @@ describe("Toolcraft template component contracts", () => {
|
|
|
221
222
|
"Every visible custom-control element must justify its space by enabling selection, ordering, preview, removal, upload, editing, or status that affects the product.",
|
|
222
223
|
);
|
|
223
224
|
expect(contract.aiUsageRules).toContain(
|
|
224
|
-
"Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
|
|
225
|
+
"Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
|
|
226
|
+
);
|
|
227
|
+
expect(contract.aiUsageRules).toContain(
|
|
228
|
+
"When a custom control owns a growable, removable, selectable, or reorderable runtime item set, its builtInFitCheck must explicitly check collectionActions and actions before choosing custom; this is based on the value model and user workflow, not on entity names such as masks or glyphs.",
|
|
229
|
+
);
|
|
230
|
+
expect(contract.aiUsageRules).toContain(
|
|
231
|
+
"Do not justify custom controls with icons, layout, styling, compactness, or custom buttons alone. The fit check must name the product interaction or value model that built-ins cannot express.",
|
|
225
232
|
);
|
|
226
233
|
expect(contract.aiUsageRules).toContain(
|
|
227
234
|
"Custom controls may use Toolcraft primitives for small app-specific chrome, but must not import or render low-level runtime surfaces or duplicate toolbar, timeline, layers, canvas, panel, or built-in control mechanics.",
|
|
@@ -253,11 +260,50 @@ describe("Toolcraft template component contracts", () => {
|
|
|
253
260
|
expect(contract.aiUsageRules).toContain(
|
|
254
261
|
"Do not use Actions for animation transport; Play, Pause, Resume, Restart, and Scrub belong to the top timeline when timeline behavior exists.",
|
|
255
262
|
);
|
|
263
|
+
expect(contract.decisionCatalog?.layoutConstraints).toContain(
|
|
264
|
+
"When an Actions button group wraps below its label, the wrapped button row starts at the left edge of the control content instead of centering or staying right-aligned.",
|
|
265
|
+
);
|
|
266
|
+
expect(contract.aiUsageRules).toContain(
|
|
267
|
+
"When an Actions button group no longer fits beside its label and wraps to a second row, align the wrapped button row to the left.",
|
|
268
|
+
);
|
|
256
269
|
expect(contract.aiUsageRules).toContain(
|
|
257
270
|
'For local reset-like actions, use product-specific values such as "reset-current-layer" or "reset-palette" and handle them through ToolcraftApp onPanelAction; do not use a bare "reset" value unless the action intentionally runs controls.reset.',
|
|
258
271
|
);
|
|
259
272
|
});
|
|
260
273
|
|
|
274
|
+
it("documents CollectionActions as canvas-backed add/remove controls", () => {
|
|
275
|
+
const contract = getToolcraftComponentContract("collectionActions");
|
|
276
|
+
|
|
277
|
+
expect(contract.stateMode).toBe("controlled");
|
|
278
|
+
expect(contract.decisionCatalog?.strictness).toBe("exact-owner");
|
|
279
|
+
expect(contract.decisionCatalog?.ownsValueModel).toContain(
|
|
280
|
+
"repeatable product entity collection",
|
|
281
|
+
);
|
|
282
|
+
expect(contract.decisionCatalog?.useWhen).toContain(
|
|
283
|
+
"Use CollectionActions instead of a count Slider when the user edits the actual set of items rather than only a numeric amount.",
|
|
284
|
+
);
|
|
285
|
+
expect(contract.decisionCatalog?.doNotReplaceWith).toContain(
|
|
286
|
+
"Do not use Slider to add or remove real collection items.",
|
|
287
|
+
);
|
|
288
|
+
expect(contract.decisionCatalog?.layoutConstraints).toContain(
|
|
289
|
+
"recommendedMaxItems is an agent/layout/performance hint, not a hard add limit; hardMaxItems is allowed only for real algorithm, format, API, export, or proven performance limits.",
|
|
290
|
+
);
|
|
291
|
+
expect(contract.decisionCatalog?.requiredAcceptance.join(" ")).toMatch(
|
|
292
|
+
/canvas preview and export/i,
|
|
293
|
+
);
|
|
294
|
+
expect(contract.aiUsageRules).toContain(
|
|
295
|
+
"Adding or removing collection items must update the runtime target array consumed by the renderer and export; do not add panel-only items.",
|
|
296
|
+
);
|
|
297
|
+
expect(contract.aiUsageRules).toContain(
|
|
298
|
+
"recommendedMaxItems is advisory only and must not disable the plus button. Use hardMaxItems only when a real product, algorithm, API, export, or measured performance limit requires it.",
|
|
299
|
+
);
|
|
300
|
+
expect(contract.aiUsageRules.join(" ")).toMatch(/TextInput/);
|
|
301
|
+
expect(contract.aiUsageRules.join(" ")).toMatch(/FontPicker/);
|
|
302
|
+
expect(contract.aiUsageRules).toContain(
|
|
303
|
+
"Use FontPicker as the collection item control when each repeated item is a typography/text-style entity; do not split its font, color, opacity, size, case, letter-spacing, or line-height into sibling collection fields.",
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
261
307
|
it("documents Palette as a constrained design-token color control", () => {
|
|
262
308
|
const contract = getToolcraftComponentContract("palette");
|
|
263
309
|
|
|
@@ -352,7 +398,7 @@ describe("Toolcraft template component contracts", () => {
|
|
|
352
398
|
"Aspect ratio presets are the only interaction that may resize both canvas dimensions from a preset; manual size inputs are exact output dimensions.",
|
|
353
399
|
);
|
|
354
400
|
expect(contract.aiUsageRules).toContain(
|
|
355
|
-
"For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1x to 2x without changing visible canvas size, and adding/enabling it requires
|
|
401
|
+
"For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1x to 2x without changing visible canvas size, and adding/enabling it requires targeted browser evidence that the canvas stays responsive at the selected scale.",
|
|
356
402
|
);
|
|
357
403
|
expect(contract.aiUsageRules).toContain(
|
|
358
404
|
"After enabling canvas.renderScale, verify that canvas preview stays responsive while dragging sliders and other high-frequency controls at the selected scale.",
|
|
@@ -538,22 +584,31 @@ describe("Toolcraft template component contracts", () => {
|
|
|
538
584
|
'Pair renderer-owned output background color with export.includeBackground in one Background section directly before export settings. Use an equal-width inline row with the export.includeBackground Switch labeled "Include" on the left and the background Color parameter with label false on the right; each control occupies one half of the row.',
|
|
539
585
|
);
|
|
540
586
|
expect(color.aiUsageRules).toContain(
|
|
541
|
-
"Preview, PNG export, and video export must read the runtime background color value instead of hardcoding that background in CSS, Canvas fillStyle, or WebGL clearColor. export.includeBackground controls
|
|
587
|
+
"Preview, PNG export, and video export must read the runtime background color value instead of hardcoding that background in CSS, Canvas fillStyle, or WebGL clearColor. export.includeBackground controls live preview product background and PNG alpha; it must not make the Toolcraft canvas shell/backing or video output transparent.",
|
|
542
588
|
);
|
|
543
589
|
expect(color.aiUsageRules).toContain(
|
|
544
590
|
"When one short numeric/text field and one Color field configure the same entity, keep them in one two-column inline layout group.",
|
|
545
591
|
);
|
|
546
592
|
expect(color.aiUsageRules).toContain(
|
|
547
|
-
'Mixed inline rows require visible labels on both controls. The required Background row is the
|
|
593
|
+
'Mixed inline rows require visible labels on both controls. The required Background row is the section-title-owned exception: use the Switch label "Include" and set the background Color control label to false. Palette variation color banks are the other exception when the group/section label already names the color bank.',
|
|
548
594
|
);
|
|
549
595
|
expect(color.aiUsageRules).toContain(
|
|
550
596
|
"Plain Color popovers must not show opacity controls. If opacity is editable, use ColorOpacity instead.",
|
|
551
597
|
);
|
|
552
598
|
expect(color.aiUsageRules).toContain(
|
|
553
|
-
"
|
|
599
|
+
"Decide color label visibility from the user's point of view. Omit labels for color banks that only add palette variety, such as Accent Shades, Bead Colors, or palette.accent1..5.",
|
|
554
600
|
);
|
|
555
601
|
expect(color.aiUsageRules).toContain(
|
|
556
|
-
"
|
|
602
|
+
"Make color label visibility a group-level decision: do not mix labeled and unlabeled items inside one semantic color bank.",
|
|
603
|
+
);
|
|
604
|
+
expect(color.aiUsageRules).toContain(
|
|
605
|
+
"Keep visible labels when colors edit distinct user-facing entities or roles, such as Fill, Stroke, Background, Connector, Object, or Highlight.",
|
|
606
|
+
);
|
|
607
|
+
expect(color.aiUsageRules).toContain(
|
|
608
|
+
"A color bank can share a section with distribution controls such as Spread, Mix, or Randomness and still keep each color item unlabeled when the section title names the palette context.",
|
|
609
|
+
);
|
|
610
|
+
expect(color.aiUsageRules).toContain(
|
|
611
|
+
"If a multi-color bank has an odd trailing plain Color, keep that last Color at half width; only ColorOpacity or intentionally full-width compound controls occupy a full row.",
|
|
557
612
|
);
|
|
558
613
|
expect(gradient.aiUsageRules).toContain(
|
|
559
614
|
"Gradient is a compound control; acceptance must prove gradient.gradientType, gradient.angle, gradient.stops.position, gradient.stops.color, and gradient.stops.opacity all affect the product output when visible.",
|
|
@@ -570,6 +625,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
570
625
|
expect(fontPicker.aiUsageRules).toContain(
|
|
571
626
|
'Do not recreate FontPicker with a plain Select plus separate sliders; use type: "fontPicker" so the popup mechanics and footer controls stay intact.',
|
|
572
627
|
);
|
|
628
|
+
expect(fontPicker.aiUsageRules).toContain(
|
|
629
|
+
"FontPicker standard/default text color is #FFFFFF with opacity 100; omit color/opacity or use those values unless the prompt or reference explicitly requires a different initial text color.",
|
|
630
|
+
);
|
|
573
631
|
expect(fontPicker.aiUsageRules).toContain(
|
|
574
632
|
"Any product text controlled by FontPicker must render fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity in preview and export; do not leave typography values as panel-only runtime state.",
|
|
575
633
|
);
|
|
@@ -713,14 +771,20 @@ describe("Toolcraft template component contracts", () => {
|
|
|
713
771
|
"Custom renderers must define performance budgets for media import, preview updates, control drags, and export/copy before implementation.",
|
|
714
772
|
);
|
|
715
773
|
expect(contract.aiUsageRules).toContain(
|
|
716
|
-
"Controls that change renderer workload
|
|
774
|
+
"Controls that change renderer workload by changing output dimensions, element count, density, sample count, iteration count, blur/filter radius, shader branch cost, media processing, text layout, or export quality must be tested at min, default, and max values.",
|
|
717
775
|
);
|
|
718
776
|
expect(contract.aiUsageRules).toContain(
|
|
719
|
-
"
|
|
777
|
+
"For workload control scenarios, stressFixture is the tested control value. If the app has an independent heavy baseline such as large media, long text, many items, high render scale, or dense source state, declare workloadFixture and apply it before the measured control interaction.",
|
|
778
|
+
);
|
|
779
|
+
expect(contract.aiUsageRules).toContain(
|
|
780
|
+
"Hash differs is not enough for workload controls; tests must assert semantic direction, such as density increasing item count or size changes reducing/increasing rendered cells.",
|
|
720
781
|
);
|
|
721
782
|
expect(contract.aiUsageRules).toContain(
|
|
722
783
|
"Performance tests must use representative fixtures and the same renderer/export path as the running app, not only tiny 32px fixtures or isolated helper state.",
|
|
723
784
|
);
|
|
785
|
+
expect(contract.aiUsageRules).toContain(
|
|
786
|
+
'Media-import workload fixtures and media workload baselines must use fixture kind "media" with numeric width and height at least 1920x1080-equivalent; 640x480 preview fixtures cannot satisfy upload, effect-control, or image-processing performance coverage.',
|
|
787
|
+
);
|
|
724
788
|
expect(contract.aiUsageRules).toContain(
|
|
725
789
|
"Slider drags and high-frequency controls must debounce or coalesce preview work, cancel stale async renders, and avoid re-decoding media on every control change.",
|
|
726
790
|
);
|
|
@@ -728,7 +792,10 @@ describe("Toolcraft template component contracts", () => {
|
|
|
728
792
|
"Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
|
|
729
793
|
);
|
|
730
794
|
expect(contract.aiUsageRules).toContain(
|
|
731
|
-
"A full performance checkpoint must run with pnpm verify:perf when the first working app version exists
|
|
795
|
+
"A full performance checkpoint must run with pnpm verify:perf 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.",
|
|
796
|
+
);
|
|
797
|
+
expect(contract.aiUsageRules).toContain(
|
|
798
|
+
"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.",
|
|
732
799
|
);
|
|
733
800
|
expect(contract.aiUsageRules).toContain(
|
|
734
801
|
"Performance fixes must preserve selected output and preview quality; do not reduce image quality, selected renderScale, export resolution, source media fidelity, or canvas backing pixels as the hidden way to pass budgets.",
|
|
@@ -757,8 +824,19 @@ describe("Toolcraft template component contracts", () => {
|
|
|
757
824
|
expect(contract.aiUsageRules).toContain(
|
|
758
825
|
'productRepresentation "mixed" is valid only when rendererTechnique.layers proves at least two different content families.',
|
|
759
826
|
);
|
|
827
|
+
expect(contract.aiUsageRules).toContain(
|
|
828
|
+
"Custom renderer apps must declare rendererPipeline with render passes, cache keys, execution location, preview/export quality, and interaction invalidation before implementation.",
|
|
829
|
+
);
|
|
830
|
+
expect(contract.aiUsageRules).toContain(
|
|
831
|
+
"Render Pipeline Inventory must explain which runtime targets invalidate each expensive pass; high-frequency interactions such as animation frames, drag, zoom, pan, timeline playback, and mask movement must not invalidate upstream decode/preprocess/pixel-transform work unless that target truly changes the upstream result.",
|
|
832
|
+
);
|
|
833
|
+
expect(contract.aiUsageRules).toContain(
|
|
834
|
+
"Cache-sensitive render passes such as decode, preprocess, pixel-transform, text-layout, rasterize, and composite must declare cache keys so tests can reject full recomputation on every control change.",
|
|
835
|
+
);
|
|
760
836
|
expect(contract.aiUsageRules.join("\n")).toMatch(/rendererTechnique/);
|
|
761
837
|
expect(contract.aiUsageRules.join("\n")).toMatch(/rendererTechnique\.layers/);
|
|
838
|
+
expect(contract.aiUsageRules.join("\n")).toMatch(/rendererPipeline/);
|
|
839
|
+
expect(contract.aiUsageRules.join("\n")).toMatch(/Render Pipeline Inventory/);
|
|
762
840
|
expect(contract.aiUsageRules.join("\n")).toMatch(/sourceRepresentation/);
|
|
763
841
|
expect(contract.aiUsageRules.join("\n")).toMatch(/productRepresentation/);
|
|
764
842
|
expect(contract.aiUsageRules.join("\n")).toMatch(/previewRenderer/);
|
|
@@ -776,11 +854,14 @@ describe("Toolcraft template component contracts", () => {
|
|
|
776
854
|
"Text-output and vector-output visible previews must preserve native output fidelity. Do not render a low-resolution offscreen canvas or texture and upscale it to the product size.",
|
|
777
855
|
);
|
|
778
856
|
expect(contract.aiUsageRules).toContain(
|
|
779
|
-
"Pixel-output renderers must
|
|
857
|
+
"Pixel-output renderers must treat WebGL/WebGPU as the default candidate even when the scene is static; Canvas 2D is allowed only when measured worst-case evidence shows the CPU path preserves quality and remains responsive.",
|
|
780
858
|
);
|
|
781
859
|
expect(contract.aiUsageRules).toContain(
|
|
782
860
|
"Procedural pixel renderers, shader-like effects, animated mesh gradients, and large exportable previews should use WebGL or WebGPU for pixel work instead of main-thread ImageData loops.",
|
|
783
861
|
);
|
|
862
|
+
expect(contract.aiUsageRules).toContain(
|
|
863
|
+
"Detail-heavy Canvas 2D pixel/media renderers may stay on CPU only when rendererTechnique records measured stress evidence for rejecting WebGL/WebGPU; if the heavy media stress fails, move pixel work to GPU instead of lowering quality.",
|
|
864
|
+
);
|
|
784
865
|
expect(contract.aiUsageRules).toContain(
|
|
785
866
|
"WebGL and WebGPU renderers must initialize contexts, programs, shaders, pipelines, textures, and large buffers once, then update uniforms or stable buffers when controls change.",
|
|
786
867
|
);
|
|
@@ -797,11 +878,14 @@ describe("Toolcraft template component contracts", () => {
|
|
|
797
878
|
"Animated preview renderers must suspend or coalesce non-essential animation work while the user drags, pans, pinches, zooms, or centers the canvas viewport, then resume from the correct timeline or autonomous time without changing the user's play/pause state.",
|
|
798
879
|
);
|
|
799
880
|
expect(contract.aiUsageRules).toContain(
|
|
800
|
-
"If a generated app uses ImageData, getImageData, or putImageData for procedural output, performance validation must fail unless the
|
|
881
|
+
"If a generated app uses ImageData, getImageData, or putImageData for procedural output, performance validation must fail unless rendererTechnique records measured WebGL/WebGPU comparison evidence that the CPU path preserves quality and responsiveness, or the renderer is moved to GPU.",
|
|
801
882
|
);
|
|
802
883
|
expect(contract.aiUsageRules).toContain(
|
|
803
884
|
"Performance matrices must declare rendererStrategy so tests can distinguish none, dom, svg, canvas-2d, webgl, and webgpu renderer paths.",
|
|
804
885
|
);
|
|
886
|
+
expect(contract.aiUsageRules).toContain(
|
|
887
|
+
"If a renderer cannot meet the budget, first optimize renderer technique, caching, invalidation, scheduling, and critical-path work. Only change exposed product ranges, work units, or controls after recording measured evidence that the requested quality ceiling is impossible; do not silently reduce product quality to pass budgets.",
|
|
888
|
+
);
|
|
805
889
|
});
|
|
806
890
|
|
|
807
891
|
it("documents reference runtime clone mode as a tested composition contract", () => {
|
|
@@ -876,6 +960,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
876
960
|
expect(contract.aiUsageRules).toContain(
|
|
877
961
|
"Ordinary 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 technical Setup/settings sections and sticky footer Export sections are not collapsible.",
|
|
878
962
|
);
|
|
963
|
+
expect(contract.aiUsageRules).toContain(
|
|
964
|
+
"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.",
|
|
965
|
+
);
|
|
879
966
|
expect(contract.aiUsageRules).toContain(
|
|
880
967
|
"Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical Setup/settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.",
|
|
881
968
|
);
|
|
@@ -1120,10 +1207,10 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1120
1207
|
'Product-output apps must expose a dedicated "Background" section directly before the first export settings section. With PNG export that first section is Image Export; with video-only export it is Video Export.',
|
|
1121
1208
|
);
|
|
1122
1209
|
expect(contract.aiUsageRules).toContain(
|
|
1123
|
-
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas
|
|
1210
|
+
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas for PNG alpha and call shouldIncludeToolcraftPreviewBackground(state) for live preview product background.",
|
|
1124
1211
|
);
|
|
1125
1212
|
expect(contract.aiUsageRules).toContain(
|
|
1126
|
-
"PNG export must use createToolcraftPngExportCanvas so background transparency and selected image dimensions or retina fallback are applied consistently
|
|
1213
|
+
"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.",
|
|
1127
1214
|
);
|
|
1128
1215
|
expect(contract.aiUsageRules).toContain(
|
|
1129
1216
|
"Video export must keep product background and use getToolcraftRetinaExportSize for retina dimensions.",
|
|
@@ -1148,15 +1235,33 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1148
1235
|
it("documents file upload ownership across single-layer and multi-layer apps", () => {
|
|
1149
1236
|
const contract = getToolcraftComponentContract("fileDrop");
|
|
1150
1237
|
|
|
1151
|
-
expect(contract.commands).toEqual(["media.delete", "media.import"]);
|
|
1238
|
+
expect(contract.commands).toEqual(["media.delete", "media.import", "media.reorder"]);
|
|
1239
|
+
expect(contract.aiUsageRules).toContain(
|
|
1240
|
+
'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
|
|
1241
|
+
);
|
|
1152
1242
|
expect(contract.aiUsageRules).toContain(
|
|
1153
1243
|
"In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
|
|
1154
1244
|
);
|
|
1245
|
+
expect(contract.aiUsageRules).toContain(
|
|
1246
|
+
"In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
|
|
1247
|
+
);
|
|
1248
|
+
expect(contract.aiUsageRules).toContain(
|
|
1249
|
+
"In single-layer apps, global Reset controls and section reset must remove uploaded fileDrop source media and return the fileDrop target to defaultValue.",
|
|
1250
|
+
);
|
|
1155
1251
|
expect(contract.aiUsageRules).toContain(
|
|
1156
1252
|
"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.",
|
|
1157
1253
|
);
|
|
1158
1254
|
expect(contract.aiUsageRules).toContain(
|
|
1159
|
-
"When multiple uploaded images are present, the runtime appends media, shows a four-column preview grid, puts the add-more tile last, and exposes per-image removal.",
|
|
1255
|
+
"When multiple uploaded images are present, the runtime appends media, shows a sortable four-column preview grid, puts the add-more tile last, and exposes per-image removal.",
|
|
1256
|
+
);
|
|
1257
|
+
expect(contract.aiUsageRules).toContain(
|
|
1258
|
+
"Canvas drops route to the first visible matching fileDrop target by asset kind: image files prefer image uploaders, non-image files prefer file uploaders, and file uploaders accept images only when no image uploader matches.",
|
|
1259
|
+
);
|
|
1260
|
+
expect(contract.aiUsageRules).toContain(
|
|
1261
|
+
"Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
|
|
1262
|
+
);
|
|
1263
|
+
expect(contract.aiUsageRules).toContain(
|
|
1264
|
+
"Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
|
|
1160
1265
|
);
|
|
1161
1266
|
expect(contract.aiUsageRules).toContain(
|
|
1162
1267
|
"In multi-layer apps, deletion and visibility belong to the Layers panel; fileDrop remains an upload target.",
|
|
@@ -1190,6 +1295,9 @@ describe("Toolcraft template component contracts", () => {
|
|
|
1190
1295
|
expect(contract.aiUsageRules).toContain(
|
|
1191
1296
|
"Render multiple related color fields in one section with at most two colors per row.",
|
|
1192
1297
|
);
|
|
1298
|
+
expect(contract.aiUsageRules).toContain(
|
|
1299
|
+
"If a multi-color bank has an odd trailing plain Color, keep that last Color at half width; only ColorOpacity or intentionally full-width compound controls occupy a full row.",
|
|
1300
|
+
);
|
|
1193
1301
|
});
|
|
1194
1302
|
|
|
1195
1303
|
it("documents CodeTextarea as generic multiline text input", () => {
|
|
@@ -375,6 +375,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
375
375
|
layoutConstraints: [
|
|
376
376
|
"Keep local actions close to the entity they affect.",
|
|
377
377
|
"Keep action labels short and scoped by the section title; prefer Randomize, Clear, Sort, Normalize, Duplicate, or Reset when the section already names the target.",
|
|
378
|
+
"When an Actions button group wraps below its label, the wrapped button row starts at the left edge of the control content instead of centering or staying right-aligned.",
|
|
378
379
|
],
|
|
379
380
|
requiredAcceptance: [
|
|
380
381
|
"Prove each action dispatches the intended command or product side effect for the nearby entity only.",
|
|
@@ -388,10 +389,61 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
388
389
|
"Do not use Actions for final product delivery actions; use sticky panelActions for Export, Copy, Download, Generate, or Apply.",
|
|
389
390
|
"Do not use Actions for global reset; the controls panel header owns global reset.",
|
|
390
391
|
"Do not use Actions for animation transport; Play, Pause, Resume, Restart, and Scrub belong to the top timeline when timeline behavior exists.",
|
|
392
|
+
"When an Actions button group no longer fits beside its label and wraps to a second row, align the wrapped button row to the left.",
|
|
391
393
|
'For local reset-like actions, use product-specific values such as "reset-current-layer" or "reset-palette" and handle them through ToolcraftApp onPanelAction; do not use a bare "reset" value unless the action intentionally runs controls.reset.',
|
|
392
394
|
"Acceptance and browser tests must click each Actions button and prove the product output or runtime state for the nearby entity changed.",
|
|
393
395
|
],
|
|
394
396
|
},
|
|
397
|
+
collectionActions: {
|
|
398
|
+
...control("collectionActions", "CollectionActions", "standalone", "component-owned"),
|
|
399
|
+
decisionCatalog: decisionCatalog({
|
|
400
|
+
strictness: "exact-owner",
|
|
401
|
+
ownsValueModel: [
|
|
402
|
+
"repeatable product entity collection",
|
|
403
|
+
"add/remove product items",
|
|
404
|
+
"dynamic list of visible controls",
|
|
405
|
+
"canvas-backed collection size",
|
|
406
|
+
],
|
|
407
|
+
useWhen: [
|
|
408
|
+
"Use CollectionActions when users can add or remove repeated product entities such as colors, glyphs, symbols, points, rules, variants, or object entries.",
|
|
409
|
+
"Use CollectionActions instead of a count Slider when the user edits the actual set of items rather than only a numeric amount.",
|
|
410
|
+
],
|
|
411
|
+
doNotReplaceWith: [
|
|
412
|
+
"Do not use Slider to add or remove real collection items.",
|
|
413
|
+
"Do not use plain Actions for add/remove collection ownership; Actions are local commands, not collection state owners.",
|
|
414
|
+
"Do not use CollectionActions for panel-only lists that do not affect canvas preview or export.",
|
|
415
|
+
],
|
|
416
|
+
acceptableAlternatives: [
|
|
417
|
+
"Use fileDrop multiple when the repeated entities are uploaded images.",
|
|
418
|
+
"Use Gradient when the repeated entities are gradient stops inside an adjustable gradient.",
|
|
419
|
+
"Use customControl only when the repeated entity needs interactions no built-in collection item control can express.",
|
|
420
|
+
],
|
|
421
|
+
layoutConstraints: [
|
|
422
|
+
"CollectionActions sits at the start of its section, renders the collection label on the left, and keeps remove/add icon buttons together on the right.",
|
|
423
|
+
"Homogeneous repeated item controls do not render visible per-item labels when the collection label already names the group.",
|
|
424
|
+
"Collection item controls follow normal density rules: plain color items use equal 50% columns when they fit, while color+opacity items stay stacked.",
|
|
425
|
+
"CollectionActions is a compound control and follows content-width compound divider rules when sharing a section with sibling controls.",
|
|
426
|
+
"recommendedMaxItems is an agent/layout/performance hint, not a hard add limit; hardMaxItems is allowed only for real algorithm, format, API, export, or proven performance limits.",
|
|
427
|
+
],
|
|
428
|
+
requiredAcceptance: [
|
|
429
|
+
"Prove plus adds a runtime item and that the new item appears in or affects canvas preview and export.",
|
|
430
|
+
"Prove minus removes a runtime item and that the removed item disappears from or stops affecting canvas preview and export.",
|
|
431
|
+
"Prove minItems prevents deleting below the minimum and recommendedMaxItems does not silently block adding more items.",
|
|
432
|
+
],
|
|
433
|
+
}),
|
|
434
|
+
stateMode: "controlled",
|
|
435
|
+
aiUsageRules: [
|
|
436
|
+
"Use CollectionActions for repeatable product entities whose actual item list can grow or shrink.",
|
|
437
|
+
"Adding or removing collection items must update the runtime target array consumed by the renderer and export; do not add panel-only items.",
|
|
438
|
+
"Do not model add/remove item behavior with a Slider count when users need to edit the actual items.",
|
|
439
|
+
"recommendedMaxItems is advisory only and must not disable the plus button. Use hardMaxItems only when a real product, algorithm, API, export, or measured performance limit requires it.",
|
|
440
|
+
"CollectionActions item controls use built-in controls whenever possible, such as Color, ColorOpacity, TextInput, Select, Segmented, Slider, Switch, Checkbox, RangeInput, or FontPicker.",
|
|
441
|
+
"Use FontPicker as the collection item control when each repeated item is a typography/text-style entity; do not split its font, color, opacity, size, case, letter-spacing, or line-height into sibling collection fields.",
|
|
442
|
+
"Do not add visible labels like Color 1, Color 2, Item 1, or Item 2 for homogeneous collection items when the collection label already explains the group.",
|
|
443
|
+
"Use compact half-width item layout whenever the child control is allowed to fit in a half row; color items without opacity are the default two-column case.",
|
|
444
|
+
"Acceptance must add and remove items through the browser UI and prove canvas/export output follows the changed collection.",
|
|
445
|
+
],
|
|
446
|
+
},
|
|
395
447
|
panelActions: {
|
|
396
448
|
...control("panelActions", "PanelActions", "standalone", "component-owned"),
|
|
397
449
|
decisionCatalog: decisionCatalog({
|
|
@@ -452,8 +504,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
452
504
|
"Video export browser coverage must load the exported blob metadata and prove video.duration matches the edited runtime timeline duration; blobSize/blobType checks alone are not enough.",
|
|
453
505
|
"Video export must report frame-based progress through reportProgress during render/encode steps. PNG export should report phase progress for render, blob, and handoff when those phases are asynchronous.",
|
|
454
506
|
'Product-output apps must expose a dedicated "Background" section directly before the first export settings section. With PNG export that first section is Image Export; with video-only export it is Video Export.',
|
|
455
|
-
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas
|
|
456
|
-
"PNG export must use createToolcraftPngExportCanvas so background transparency and selected image dimensions or retina fallback are applied consistently
|
|
507
|
+
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas for PNG alpha and call shouldIncludeToolcraftPreviewBackground(state) for live preview product background.",
|
|
508
|
+
"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.",
|
|
457
509
|
"Video export must keep product background and use getToolcraftRetinaExportSize for retina dimensions.",
|
|
458
510
|
"Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
|
|
459
511
|
"Add Copy PNG as a secondary action only when the prompt/reference includes clipboard output or the product clearly benefits from paste/share workflows.",
|
|
@@ -572,7 +624,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
572
624
|
],
|
|
573
625
|
layoutConstraints: [
|
|
574
626
|
"Plain Color controls may render two per row only when no opacity is present.",
|
|
575
|
-
"Color
|
|
627
|
+
"Odd trailing plain Color controls in a multi-color bank keep the same half-width footprint as paired colors.",
|
|
628
|
+
"Color label visibility depends on user usefulness: palette variation banks omit per-item labels; distinct color roles keep labels.",
|
|
576
629
|
],
|
|
577
630
|
requiredAcceptance: [
|
|
578
631
|
"Prove the selected color affects product output, preview, or export.",
|
|
@@ -584,18 +637,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
584
637
|
"Keep a color inside a section when it configures the same entity as nearby controls. Example: Square 1 (Right) contains Connections, Hover radius, and Color in one section.",
|
|
585
638
|
"Use a standalone color section only when the color itself is the whole semantic section; the section title must describe the product role such as Background, Object, Connector, Accent, Gradient, or Brand.",
|
|
586
639
|
"When color belongs to the same object or effect as nearby controls, keep it inside that section and use a concise field label that is unambiguous in context, such as Color in a Square section or Symbol color in a mixed Style section.",
|
|
587
|
-
"
|
|
588
|
-
"
|
|
640
|
+
"Decide color label visibility from the user's point of view. Omit labels for color banks that only add palette variety, such as Accent Shades, Bead Colors, or palette.accent1..5.",
|
|
641
|
+
"Make color label visibility a group-level decision: do not mix labeled and unlabeled items inside one semantic color bank.",
|
|
642
|
+
"Keep visible labels when colors edit distinct user-facing entities or roles, such as Fill, Stroke, Background, Connector, Object, or Highlight.",
|
|
643
|
+
"A color bank can share a section with distribution controls such as Spread, Mix, or Randomness and still keep each color item unlabeled when the section title names the palette context.",
|
|
589
644
|
"The standalone default applies only to color-only sections; mixed semantic sections keep color grouped with nearby controls.",
|
|
590
645
|
"Never use generic Color or Colors as a generated section title. If no meaningful color role exists and the colors are just basic colors, use a neutral section title such as Appearance instead of omitting the title.",
|
|
591
646
|
"Do not split a grouped object section into a separate generated Color section; if the color role is unclear, ask the user before implementation.",
|
|
592
647
|
"When one short numeric/text field and one Color field configure the same entity, keep them in one two-column inline layout group.",
|
|
593
|
-
'Mixed inline rows require visible labels on both controls. The required Background row is the
|
|
648
|
+
'Mixed inline rows require visible labels on both controls. The required Background row is the section-title-owned exception: use the Switch label "Include" and set the background Color control label to false. Palette variation color banks are the other exception when the group/section label already names the color bank.',
|
|
594
649
|
"Plain Color popovers must not show opacity controls. If opacity is editable, use ColorOpacity instead.",
|
|
595
650
|
"Product-output apps always expose renderer-owned output background color as a schema color target such as appearance.background or scene.background.",
|
|
596
651
|
'Pair renderer-owned output background color with export.includeBackground in one Background section directly before export settings. Use an equal-width inline row with the export.includeBackground Switch labeled "Include" on the left and the background Color parameter with label false on the right; each control occupies one half of the row.',
|
|
597
|
-
"Preview, PNG export, and video export must read the runtime background color value instead of hardcoding that background in CSS, Canvas fillStyle, or WebGL clearColor. export.includeBackground controls
|
|
652
|
+
"Preview, PNG export, and video export must read the runtime background color value instead of hardcoding that background in CSS, Canvas fillStyle, or WebGL clearColor. export.includeBackground controls live preview product background and PNG alpha; it must not make the Toolcraft canvas shell/backing or video output transparent.",
|
|
598
653
|
"Render multiple related color fields in one section with at most two colors per row.",
|
|
654
|
+
"If a multi-color bank has an odd trailing plain Color, keep that last Color at half width; only ColorOpacity or intentionally full-width compound controls occupy a full row.",
|
|
599
655
|
],
|
|
600
656
|
},
|
|
601
657
|
gradient: {
|
|
@@ -676,6 +732,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
676
732
|
"FontPicker owns the font preview select, virtualized font popup, category filters, search, preview loading, font-weight select, font-size input, text-case select, text color/opacity control, letter-spacing slider, and line-height slider.",
|
|
677
733
|
"Do not recreate FontPicker with a plain Select plus separate sliders; use type: \"fontPicker\" so the popup mechanics and footer controls stay intact.",
|
|
678
734
|
"Use one object value with fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity. Keep typography renderers wired to all eight parts.",
|
|
735
|
+
"FontPicker standard/default text color is #FFFFFF with opacity 100; omit color/opacity or use those values unless the prompt or reference explicitly requires a different initial text color.",
|
|
679
736
|
"Any product text controlled by FontPicker must render fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity in preview and export; do not leave typography values as panel-only runtime state.",
|
|
680
737
|
"FontPicker is an atomic compound typography control. Do not split any owned typography part into a neighboring schema control for the same product text entity.",
|
|
681
738
|
"Do not put a help tooltip on FontPicker just to list its owned fields. If the section title and FontPicker labels already make the text target clear, omit description.",
|
|
@@ -814,20 +871,27 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
814
871
|
],
|
|
815
872
|
layoutConstraints: [
|
|
816
873
|
"FileDrop lives in the controls panel; single-layer apps use its preview and clear behavior.",
|
|
817
|
-
"When fileDrop has multiple: true and more than one image is present, the runtime renders a four-column thumbnail grid with the add-more tile last.",
|
|
874
|
+
"When fileDrop has multiple: true and more than one image is present, the runtime renders a sortable four-column thumbnail grid with the add-more tile last.",
|
|
875
|
+
"When fileDrop has assetKind: file, the runtime renders a sortable file list with a paperclip icon, filename, remove button, and row separators using --border/5.",
|
|
818
876
|
],
|
|
819
877
|
requiredAcceptance: [
|
|
820
|
-
"Prove file import changes media state and product output; prove clear
|
|
878
|
+
"Prove file import changes media state and product output; prove clear, reorder, and section/global reset update source material.",
|
|
821
879
|
],
|
|
822
880
|
}),
|
|
823
881
|
aiUsageRules: [
|
|
824
882
|
"Use fileDrop for source material uploads in the controls panel, not on the canvas.",
|
|
883
|
+
'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
|
|
825
884
|
"In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
|
|
885
|
+
"In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
|
|
886
|
+
"In single-layer apps, global Reset controls and section reset must remove uploaded fileDrop source media and return the fileDrop target to defaultValue.",
|
|
826
887
|
"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.",
|
|
827
|
-
"When multiple uploaded images are present, the runtime appends media, shows a four-column preview grid, puts the add-more tile last, and exposes per-image removal.",
|
|
888
|
+
"When multiple uploaded images are present, the runtime appends media, shows a sortable four-column preview grid, puts the add-more tile last, and exposes per-image removal.",
|
|
889
|
+
"Canvas drops route to the first visible matching fileDrop target by asset kind: image files prefer image uploaders, non-image files prefer file uploaders, and file uploaders accept images only when no image uploader matches.",
|
|
890
|
+
"Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
|
|
891
|
+
"Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
|
|
828
892
|
"In multi-layer apps, deletion and visibility belong to the Layers panel; fileDrop remains an upload target.",
|
|
829
893
|
],
|
|
830
|
-
commands: ["media.delete", "media.import"],
|
|
894
|
+
commands: ["media.delete", "media.import", "media.reorder"],
|
|
831
895
|
},
|
|
832
896
|
imagePicker: {
|
|
833
897
|
...control("imagePicker", "ImagePicker", "standalone", "component-owned"),
|
|
@@ -928,7 +992,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
928
992
|
}),
|
|
929
993
|
aiUsageRules: [
|
|
930
994
|
"Use custom controls only for product interactions that built-in controls cannot express.",
|
|
931
|
-
"Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
|
|
995
|
+
"Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
|
|
996
|
+
"When a custom control owns a growable, removable, selectable, or reorderable runtime item set, its builtInFitCheck must explicitly check collectionActions and actions before choosing custom; this is based on the value model and user workflow, not on entity names such as masks or glyphs.",
|
|
997
|
+
"Do not justify custom controls with icons, layout, styling, compactness, or custom buttons alone. The fit check must name the product interaction or value model that built-ins cannot express.",
|
|
932
998
|
"Custom controls may use Toolcraft primitives for small app-specific chrome, but must not import or render low-level runtime surfaces or duplicate toolbar, timeline, layers, canvas, panel, or built-in control mechanics.",
|
|
933
999
|
"Custom controls must render the minimum UI needed to understand the value, context, and available actions; avoid decorative metadata and text that repeats what the section, label, or visible item already explains.",
|
|
934
1000
|
"Every visible custom-control element must justify its space by enabling selection, ordering, preview, removal, upload, editing, or status that affects the product.",
|
|
@@ -961,7 +1027,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
961
1027
|
"The runtime Canvas width and Canvas height block uses the technical Setup section and renders without a visible section heading; do not add a separate Canvas section label above these fields.",
|
|
962
1028
|
"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.",
|
|
963
1029
|
"Aspect ratio presets are the only interaction that may resize both canvas dimensions from a preset; manual size inputs are exact output dimensions.",
|
|
964
|
-
"For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1x to 2x without changing visible canvas size, and adding/enabling it requires
|
|
1030
|
+
"For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1x to 2x without changing visible canvas size, and adding/enabling it requires targeted browser evidence that the canvas stays responsive at the selected scale.",
|
|
965
1031
|
"After enabling canvas.renderScale, verify that canvas preview stays responsive while dragging sliders and other high-frequency controls at the selected scale.",
|
|
966
1032
|
"Performance fixes for canvas.renderScale must preserve the selected visual quality; do not silently downsample, stretch a lower-resolution backing canvas, blur output, or clamp canvas.renderScale below the user's chosen value to pass budgets.",
|
|
967
1033
|
"Do not enable canvas.renderScale for DOM/SVG/vector-native previews; preserve vector fidelity through native vector rendering instead of raster supersampling.",
|
|
@@ -1052,18 +1118,24 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1052
1118
|
performanceAcceptance: {
|
|
1053
1119
|
aiUsageRules: [
|
|
1054
1120
|
"Custom renderers must define performance budgets for media import, preview updates, control drags, and export/copy before implementation.",
|
|
1055
|
-
"Controls that change renderer workload
|
|
1056
|
-
"
|
|
1121
|
+
"Controls that change renderer workload by changing output dimensions, element count, density, sample count, iteration count, blur/filter radius, shader branch cost, media processing, text layout, or export quality must be tested at min, default, and max values.",
|
|
1122
|
+
"For workload control scenarios, stressFixture is the tested control value. If the app has an independent heavy baseline such as large media, long text, many items, high render scale, or dense source state, declare workloadFixture and apply it before the measured control interaction.",
|
|
1123
|
+
"Hash differs is not enough for workload controls; tests must assert semantic direction, such as density increasing item count or size changes reducing/increasing rendered cells.",
|
|
1057
1124
|
"Performance tests must use representative fixtures and the same renderer/export path as the running app, not only tiny 32px fixtures or isolated helper state.",
|
|
1125
|
+
"Media-import workload fixtures and media workload baselines must use fixture kind \"media\" with numeric width and height at least 1920x1080-equivalent; 640x480 preview fixtures cannot satisfy upload, effect-control, or image-processing performance coverage.",
|
|
1058
1126
|
"Expensive renderers must cache decoded media, source pixels, glyph atlases, gradients, and other reusable inputs by media id, canvas size, and stable control keys.",
|
|
1059
1127
|
"Slider drags and high-frequency controls must debounce or coalesce preview work, cancel stale async renders, and avoid re-decoding media on every control change.",
|
|
1060
1128
|
"Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
|
|
1061
|
-
"A full performance checkpoint must run with pnpm verify:perf when the first working app version exists
|
|
1129
|
+
"A full performance checkpoint must run with pnpm verify:perf 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.",
|
|
1130
|
+
"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.",
|
|
1062
1131
|
"Performance fixes must preserve selected output and preview quality; do not reduce image quality, selected renderScale, export resolution, source media fidelity, or canvas backing pixels as the hidden way to pass budgets.",
|
|
1063
1132
|
"When canvas or slider interactions lag, diagnose where the slowdown comes from before changing output quality: renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, async render cancellation, or animation scheduling.",
|
|
1064
1133
|
"Renderer specs must include a Renderer Technique Decision Matrix with sourceRepresentation, productRepresentation, previewRenderer, exportRenderer, rendererWorkload, rendererStrategy, whyNotAlternativeStrategies, fidelityRisks, and performanceRisks.",
|
|
1065
1134
|
"Custom renderer apps must mirror the Renderer Technique Decision Matrix in typed rendererTechnique config so validation can reject contradictory renderer choices.",
|
|
1066
1135
|
"Custom renderer specs must include a Renderer Layer Inventory and mirror it in typed rendererTechnique.layers so dense raster backgrounds cannot silently rasterize semantic foreground output.",
|
|
1136
|
+
"Custom renderer apps must declare rendererPipeline with render passes, cache keys, execution location, preview/export quality, and interaction invalidation before implementation.",
|
|
1137
|
+
"Render Pipeline Inventory must explain which runtime targets invalidate each expensive pass; high-frequency interactions such as animation frames, drag, zoom, pan, timeline playback, and mask movement must not invalidate upstream decode/preprocess/pixel-transform work unless that target truly changes the upstream result.",
|
|
1138
|
+
"Cache-sensitive render passes such as decode, preprocess, pixel-transform, text-layout, rasterize, and composite must declare cache keys so tests can reject full recomputation on every control change.",
|
|
1067
1139
|
"Semantic foreground output such as product lines, shapes, icons, text, object bounds, and meaningful markers should use DOM or SVG by default; dense raster backgrounds do not justify rasterizing low-count foreground geometry or text.",
|
|
1068
1140
|
"Editing handles must be DOM/SVG overlays, excluded from export, and written through runtime state instead of being drawn into the product raster layer.",
|
|
1069
1141
|
"Product foreground and editing handle renderer layers must declare uiSelector so browser tests can verify the visible layer exists.",
|
|
@@ -1072,17 +1144,18 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1072
1144
|
"Do not switch renderer technology just because it seems more modern or faster. Preview and export may use different renderers only when the decision matrix explains why and export/copy remains product-quality.",
|
|
1073
1145
|
"Choose renderer workload by product fidelity before choosing rendering technology: ASCII, glyph grids, code art, subtitles, typography, or monospace text products are text-output unless the product intentionally rasterizes them into per-pixel effects.",
|
|
1074
1146
|
"Text-output and vector-output visible previews must preserve native output fidelity. Do not render a low-resolution offscreen canvas or texture and upscale it to the product size.",
|
|
1075
|
-
"Pixel-output renderers must
|
|
1147
|
+
"Pixel-output renderers must treat WebGL/WebGPU as the default candidate even when the scene is static; Canvas 2D is allowed only when measured worst-case evidence shows the CPU path preserves quality and remains responsive.",
|
|
1076
1148
|
"Procedural pixel renderers, shader-like effects, animated mesh gradients, and large exportable previews should use WebGL or WebGPU for pixel work instead of main-thread ImageData loops.",
|
|
1149
|
+
"Detail-heavy Canvas 2D pixel/media renderers may stay on CPU only when rendererTechnique records measured stress evidence for rejecting WebGL/WebGPU; if the heavy media stress fails, move pixel work to GPU instead of lowering quality.",
|
|
1077
1150
|
"WebGL and WebGPU renderers must initialize contexts, programs, shaders, pipelines, textures, and large buffers once, then update uniforms or stable buffers when controls change.",
|
|
1078
1151
|
"For keyframe or playback renderers, texture upload and media decode must be keyed to source media/resource changes, not to timeline time or evaluated settings. Timeline-only updates must reuse decoded media and existing GPU resources.",
|
|
1079
1152
|
"Do not create WebGL/WebGPU contexts, shader programs, textures, or requestAnimationFrame loops directly in the React render path.",
|
|
1080
1153
|
"Animation loops must cancel scheduled frames during cleanup.",
|
|
1081
1154
|
"Animated preview renderers must suspend or coalesce non-essential animation work while the user drags, pans, pinches, zooms, or centers the canvas viewport, then resume from the correct timeline or autonomous time without changing the user's play/pause state.",
|
|
1082
|
-
"If a generated app uses ImageData, getImageData, or putImageData for procedural output, performance validation must fail unless the
|
|
1155
|
+
"If a generated app uses ImageData, getImageData, or putImageData for procedural output, performance validation must fail unless rendererTechnique records measured WebGL/WebGPU comparison evidence that the CPU path preserves quality and responsiveness, or the renderer is moved to GPU.",
|
|
1083
1156
|
"Performance matrices must declare rendererStrategy so tests can distinguish none, dom, svg, canvas-2d, webgl, and webgpu renderer paths.",
|
|
1084
1157
|
"Browser verification must interact with the actual UI, exercise worst-case control values, and fail if the app freezes, creates runaway render loops, drops canvas zoom/offset, or misses the performance budget.",
|
|
1085
|
-
"If a renderer cannot meet the budget,
|
|
1158
|
+
"If a renderer cannot meet the budget, first optimize renderer technique, caching, invalidation, scheduling, and critical-path work. Only change exposed product ranges, work units, or controls after recording measured evidence that the requested quality ceiling is impossible; do not silently reduce product quality to pass budgets.",
|
|
1086
1159
|
],
|
|
1087
1160
|
capabilities: ["performance-budgets", "workload-control-tests"],
|
|
1088
1161
|
commands: [],
|
|
@@ -1138,6 +1211,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1138
1211
|
"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.",
|
|
1139
1212
|
"Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
|
|
1140
1213
|
"Ordinary 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 technical Setup/settings sections and sticky footer Export sections are not collapsible.",
|
|
1214
|
+
"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.",
|
|
1141
1215
|
"Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical Setup/settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.",
|
|
1142
1216
|
"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.",
|
|
1143
1217
|
"Section titles in one controls panel must be unique.",
|
|
@@ -89,9 +89,10 @@ describe("Toolcraft template decision contract", () => {
|
|
|
89
89
|
expect(rule?.desiredBehavior).toMatch(/canvas drag, pan, pinch, zoom, and radar\/center/i);
|
|
90
90
|
expect(rule?.desiredBehavior).toMatch(/playback state/i);
|
|
91
91
|
expect(rule?.desiredBehavior).toMatch(/first working version/i);
|
|
92
|
-
expect(rule?.desiredBehavior).toMatch(/renderer\/canvas\/animation\/export\/timeline\/layers/i);
|
|
93
|
-
expect(rule?.desiredBehavior).toMatch(/bug that previously broke functionality/i);
|
|
94
92
|
expect(rule?.desiredBehavior).toMatch(/optimize performance/i);
|
|
93
|
+
expect(rule?.desiredBehavior).toMatch(/otherwise complains about performance/i);
|
|
94
|
+
expect(rule?.desiredBehavior).toMatch(/targeted functional\/browser checks/i);
|
|
95
|
+
expect(rule?.desiredBehavior).toMatch(/touched workload\/viewport\/export paths/i);
|
|
95
96
|
expect(rule?.desiredBehavior).toMatch(/full performance checkpoint/i);
|
|
96
97
|
});
|
|
97
98
|
|