@pixel-point/toolcraft 0.0.7 → 0.0.9
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.mjs +34 -5
- package/src/generate.test.mjs +12 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +185 -23
- package/templates/runtime/contracts/component-contracts.ts +91 -36
- package/templates/runtime/contracts/decision-contracts.test.ts +5 -0
- package/templates/runtime/contracts/decision-contracts.ts +4 -4
- 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 +774 -17
- package/templates/runtime/react/controls-panel.tsx +155 -8
- package/templates/runtime/react/media-file.ts +19 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +46 -1
- package/templates/runtime/schema/define-toolcraft.ts +29 -3
- package/templates/runtime/schema/types.ts +7 -0
- package/templates/runtime/state/reducer.test.ts +304 -0
- package/templates/runtime/state/reducer.ts +148 -9
- package/templates/runtime/state/types.ts +10 -2
- package/templates/runtime/testing/performance.test.ts +1424 -56
- package/templates/runtime/testing/performance.ts +710 -43
- package/templates/starter/AGENTS.md +10 -9
- package/templates/starter/docs/toolcraft/README.md +1 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +16 -8
- package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -7
- package/templates/starter/docs/toolcraft/component-rules.md +46 -16
- package/templates/starter/docs/toolcraft/custom-controls.md +8 -4
- package/templates/starter/docs/toolcraft/performance.md +54 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +48 -19
- package/templates/starter/docs/toolcraft/workflow.md +2 -2
- package/templates/starter/e2e/app-performance.spec.ts +136 -3
- package/templates/starter/e2e/performance-helpers.ts +197 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +5 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +39 -4
- package/templates/starter/scripts/toolcraft-port.mjs +102 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +60 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +1288 -105
- package/templates/starter/src/app/starter-acceptance.ts +740 -24
- package/templates/starter/src/app/starter-performance.test.ts +66 -5
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +10 -4
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +340 -44
- package/templates/ui/components/controls/file-drop/index.ts +1 -1
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/select/select-control.tsx +9 -4
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +84 -8
- package/templates/ui/components/panel/panel-section.tsx +82 -6
|
@@ -39,20 +39,26 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
39
39
|
"Only FontPicker may pair its internal letter-spacing and line-height sliders.",
|
|
40
40
|
],
|
|
41
41
|
requiredAcceptance: [
|
|
42
|
-
"Prove
|
|
42
|
+
"Prove dragging the slider changes product output or the intended runtime side effect while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.",
|
|
43
43
|
"For discrete sliders, prove the discrete variant renders markers and dragging remains smooth.",
|
|
44
44
|
],
|
|
45
45
|
}),
|
|
46
46
|
aiUsageRules: [
|
|
47
|
+
"Sliders are live canvas controls: dragging must update runtime state and product output in real time by default.",
|
|
48
|
+
"Do not implement slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.",
|
|
49
|
+
"Slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.",
|
|
50
|
+
"If a live slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.",
|
|
51
|
+
"Only in an extreme documented performance ceiling may a slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.",
|
|
47
52
|
"Slider step means numeric snapping only; it does not make the slider visually discrete by itself.",
|
|
48
53
|
"Classify every stepped slider as stepped continuous or visual discrete before writing the schema.",
|
|
49
54
|
'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".',
|
|
50
55
|
'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.',
|
|
51
56
|
"Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.",
|
|
52
|
-
"Use slider unit only for measurement
|
|
57
|
+
"Use slider unit only for real measurement suffixes such as %, px, °, s, ms, fps, rows/cols, or similar domain units.",
|
|
53
58
|
"Do not use unit for repeated entity nouns already named by the section or label, such as Letters + letters, Shape Density / Count + shapes, Words + words, Symbols + symbols, Items + items, Particles + particles, or Layers + layers.",
|
|
59
|
+
'Do not use unit: "x"; scale, multiplier, intensity, opacity, strength, depth, and shader amount sliders display plain numbers unless a real measurement unit applies.',
|
|
54
60
|
"When the value needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.",
|
|
55
|
-
"Compact symbol/CSS units render tight, such as 70%, 24px,
|
|
61
|
+
"Compact symbol/CSS units render tight, such as 70%, 24px, and 8s; word units render with a space, such as 5 cols, when they are truly needed.",
|
|
56
62
|
"Slider valueLabel is editable only when it contains a numeric value; textual state labels such as Normal are display-only and must not expose hover or click editing affordances.",
|
|
57
63
|
"Schema sliders render stacked at full width; do not put sliders in two-column inline layout groups.",
|
|
58
64
|
"The fontPicker component is the only built-in exception with two internal footer sliders for letter spacing and line height.",
|
|
@@ -90,16 +96,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
90
96
|
"Default lower and upper values must be different.",
|
|
91
97
|
],
|
|
92
98
|
requiredAcceptance: [
|
|
93
|
-
"Prove rangeSlider.lower and rangeSlider.upper both affect product output.",
|
|
99
|
+
"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.",
|
|
94
100
|
],
|
|
95
101
|
}),
|
|
96
102
|
aiUsageRules: [
|
|
103
|
+
"Range sliders are live canvas controls: dragging either thumb must update runtime state and product output in real time by default.",
|
|
104
|
+
"Do not implement range slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.",
|
|
105
|
+
"Range slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.",
|
|
106
|
+
"If a live range slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.",
|
|
107
|
+
"Only in an extreme documented performance ceiling may a range slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.",
|
|
97
108
|
"Range slider step means numeric snapping only; it does not make the range slider visually discrete by itself.",
|
|
98
109
|
"Classify every stepped range slider as stepped continuous or visual discrete before writing the schema.",
|
|
99
110
|
'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".',
|
|
100
111
|
'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.',
|
|
101
112
|
"Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.",
|
|
102
|
-
"Use rangeSlider unit only for measurement
|
|
113
|
+
"Use rangeSlider unit only for real measurement suffixes; do not use it for repeated entity nouns already named by the section or label, and do not use x as a unit.",
|
|
103
114
|
"When a range label needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.",
|
|
104
115
|
"Compact symbol/CSS units render tight, such as 20% – 80% or 12px – 48px; word units render with a space when truly needed.",
|
|
105
116
|
"RangeSlider is always a full-width two-thumb control; never place it in an inline two-column layout group with another slider or range slider.",
|
|
@@ -165,6 +176,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
165
176
|
],
|
|
166
177
|
layoutConstraints: [
|
|
167
178
|
"Keep text segmented controls to at most four options and compact labels.",
|
|
179
|
+
"Segmented controls are full-width controls and must not be placed in two-column inline or half-width layout groups.",
|
|
168
180
|
"Fallback to Select when cells collide, clip, or lose padding.",
|
|
169
181
|
],
|
|
170
182
|
requiredAcceptance: [
|
|
@@ -174,6 +186,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
174
186
|
}),
|
|
175
187
|
aiUsageRules: [
|
|
176
188
|
"Use Segmented only for compact mode choices where every cell keeps its internal padding.",
|
|
189
|
+
"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.",
|
|
177
190
|
"If a segmented control is too wide, first shorten option labels; if the compact labels still exceed the width budget, use Select because it has the same selection mechanics without broken cells.",
|
|
178
191
|
"Generated schemas should keep text segmented controls to at most four options, no option label longer than nine characters, and no more than twenty-four total option-label characters.",
|
|
179
192
|
"Browser verification must treat collided, clipped, or paddingless segmented cells as a broken component and switch to shorter labels or Select.",
|
|
@@ -212,7 +225,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
212
225
|
'Use labels such as "CRT", "Background", "Glow", or "Loop" instead of "Enable CRT" or "Disable background".',
|
|
213
226
|
"Two adjacent Switch controls for the same product entity must share one inline row when every visible label fits without truncation. Keep paired labels to short one- or two-word names; the runtime auto-pairs safe adjacent switches by target entity, and generated schemas should stack switches only when any label would truncate.",
|
|
214
227
|
"When the nearest section title already names the switch context, do not duplicate that title as the visible switch label. Use label false for a visual-only toggle and keep the meaning in target/description.",
|
|
215
|
-
'A Switch may share an inline row with one related parameter control when the visible switch label is short enough to fit. That row uses equal-width columns; never shrink the switch column to intrinsic width. In section-owned rows, use a short visible label such as "Include" instead of repeating the section title, such as "Include background" inside Background.',
|
|
228
|
+
'A Switch may share an inline row with one related parameter control when the visible switch label is short enough to fit. That row uses equal-width columns and the same horizontal column gap as paired Select controls; never shrink the switch column to intrinsic width. The non-switch parameter uses label false in that row; if its label is needed, stack the controls instead. In section-owned rows, use a short visible switch label such as "Include" instead of repeating the section title, such as "Include background" inside Background.',
|
|
216
229
|
],
|
|
217
230
|
},
|
|
218
231
|
checkbox: {
|
|
@@ -247,7 +260,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
247
260
|
'Use labels such as "Transparent background", "Guides", or "Loop" instead of "Enable transparent background".',
|
|
248
261
|
"When the nearest section title already names the checkbox context, do not duplicate that title as the visible checkbox label. Use label false for a visual-only checkbox and keep the meaning in target/description.",
|
|
249
262
|
"Two adjacent Checkbox controls for the same product entity must share one inline row when every visible label fits without truncation. Keep paired labels to short one- or two-word names; the runtime auto-pairs safe adjacent checkboxes by target entity, and generated schemas should stack checkboxes only when any label would truncate.",
|
|
250
|
-
"A Checkbox may share an inline row with one related parameter control when the visible checkbox label is short enough to fit. That row uses equal-width columns; never shrink the checkbox column to intrinsic width. Hide the checkbox label when the section title provides the visible context.",
|
|
263
|
+
"A Checkbox may share an inline row with one related parameter control when the visible checkbox label is short enough to fit. That row uses equal-width columns and the same horizontal column gap as paired Select controls; never shrink the checkbox column to intrinsic width. The non-checkbox parameter uses label false in that row; if its label is needed, stack the controls instead. Hide the checkbox label when the section title provides the visible context.",
|
|
251
264
|
],
|
|
252
265
|
},
|
|
253
266
|
colorOpacity: {
|
|
@@ -295,7 +308,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
295
308
|
"single-line setting value",
|
|
296
309
|
],
|
|
297
310
|
useWhen: [
|
|
298
|
-
"Use TextInput for short names, titles, tokens, compact prompts, and small setting strings.",
|
|
311
|
+
"Use TextInput for short names, button labels, titles, captions, tokens, compact prompts, and small setting strings.",
|
|
299
312
|
"Use commitMode content for real product content and commitMode setting for configuration-like values.",
|
|
300
313
|
],
|
|
301
314
|
doNotReplaceWith: [
|
|
@@ -313,6 +326,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
313
326
|
],
|
|
314
327
|
}),
|
|
315
328
|
aiUsageRules: [
|
|
329
|
+
"TextInput owns short single-line product text: button labels, labels on the canvas, names, titles, captions, badges, short tokens, and compact prompts.",
|
|
316
330
|
'TextInput commitMode defaults to "content": text content, prompts, names, tokens, titles, and instructions apply while typing.',
|
|
317
331
|
'Use commitMode: "setting" for text inputs that edit settings such as font size, numeric-like style values, dimensions, ids, or configuration fields; setting text commits on blur or Enter.',
|
|
318
332
|
"Canvas width and Canvas height are runtime editable-size fields and always commit on blur or Enter like editor size fields.",
|
|
@@ -375,6 +389,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
375
389
|
layoutConstraints: [
|
|
376
390
|
"Keep local actions close to the entity they affect.",
|
|
377
391
|
"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.",
|
|
392
|
+
"Do not set an Actions control label to the exact same visible text as its only button; use a short one- or two-word context label such as Ink wash, Palette action, or Current layer while the button keeps the command verb.",
|
|
393
|
+
"Actions never use a side-label layout. If a visible label exists, it sits above the buttons.",
|
|
394
|
+
"Actions buttons render as a two-column grid. One visible button occupies the left half of the section; two buttons occupy one half each; more than two buttons wrap into additional 50% cells.",
|
|
395
|
+
"Do not center or right-align a partial final Actions row; an odd trailing button stays in the left 50% cell.",
|
|
378
396
|
],
|
|
379
397
|
requiredAcceptance: [
|
|
380
398
|
"Prove each action dispatches the intended command or product side effect for the nearby entity only.",
|
|
@@ -388,6 +406,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
388
406
|
"Do not use Actions for final product delivery actions; use sticky panelActions for Export, Copy, Download, Generate, or Apply.",
|
|
389
407
|
"Do not use Actions for global reset; the controls panel header owns global reset.",
|
|
390
408
|
"Do not use Actions for animation transport; Play, Pause, Resume, Restart, and Scrub belong to the top timeline when timeline behavior exists.",
|
|
409
|
+
"For a single visible Actions button, the control label and button label must not be identical; make the control label a concise context and the button label the command.",
|
|
410
|
+
"Render the Actions label above the buttons; do not put the label on the left with buttons on the right.",
|
|
411
|
+
"Render Actions buttons in 50% cells: one button uses the left half, two buttons fill one row, and larger groups continue in two columns.",
|
|
412
|
+
"Do not stretch an odd trailing Actions button full-width.",
|
|
391
413
|
'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
414
|
"Acceptance and browser tests must click each Actions button and prove the product output or runtime state for the nearby entity changed.",
|
|
393
415
|
],
|
|
@@ -435,7 +457,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
435
457
|
"Adding or removing collection items must update the runtime target array consumed by the renderer and export; do not add panel-only items.",
|
|
436
458
|
"Do not model add/remove item behavior with a Slider count when users need to edit the actual items.",
|
|
437
459
|
"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.",
|
|
438
|
-
"CollectionActions item controls use built-in controls whenever possible, such as Color, ColorOpacity, TextInput, Select, Segmented, Slider, Switch, Checkbox, or
|
|
460
|
+
"CollectionActions item controls use built-in controls whenever possible, such as Color, ColorOpacity, TextInput, Select, Segmented, Slider, Switch, Checkbox, RangeInput, or FontPicker.",
|
|
461
|
+
"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.",
|
|
439
462
|
"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.",
|
|
440
463
|
"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.",
|
|
441
464
|
"Acceptance must add and remove items through the browser UI and prove canvas/export output follows the changed collection.",
|
|
@@ -501,8 +524,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
501
524
|
"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.",
|
|
502
525
|
"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.",
|
|
503
526
|
'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.',
|
|
504
|
-
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas
|
|
505
|
-
"PNG export must use createToolcraftPngExportCanvas so background transparency and selected image dimensions or retina fallback are applied consistently
|
|
527
|
+
"Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas for PNG alpha and call shouldIncludeToolcraftPreviewBackground(state) for live preview product background.",
|
|
528
|
+
"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.",
|
|
506
529
|
"Video export must keep product background and use getToolcraftRetinaExportSize for retina dimensions.",
|
|
507
530
|
"Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
|
|
508
531
|
"Add Copy PNG as a secondary action only when the prompt/reference includes clipboard output or the product clearly benefits from paste/share workflows.",
|
|
@@ -539,7 +562,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
539
562
|
],
|
|
540
563
|
layoutConstraints: [
|
|
541
564
|
"Palette is a standalone compound control.",
|
|
542
|
-
"Palette renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
|
|
565
|
+
"Palette renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
|
|
543
566
|
],
|
|
544
567
|
requiredAcceptance: [
|
|
545
568
|
"Prove palette.family and palette.shade both affect product output.",
|
|
@@ -594,6 +617,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
594
617
|
'Use variant: "chromaOffset" for RGB/chromatic offset vectors where the X/Y movement controls channel separation.',
|
|
595
618
|
'Use variant: "toneBias" for split-tone, duotone, or color-grading vectors where both axes describe tone or hue bias.',
|
|
596
619
|
'Use the default vector variant for spatial values such as position, offset, direction, focus, anchor, and light direction.',
|
|
620
|
+
'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.',
|
|
621
|
+
"Holding Shift while dragging a vector pad locks movement to the dominant axis; do not build a custom pad just to support axis-constrained movement.",
|
|
622
|
+
'Use coordinateMode: "cartesian" only when the product intentionally exposes mathematical Y-up coordinates instead of canvas/screen movement.',
|
|
597
623
|
"Do not add custom vector sizing props in generated schemas; choose the number, variant, and section grouping from product need and let the runtime size the pads.",
|
|
598
624
|
"Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output.",
|
|
599
625
|
],
|
|
@@ -621,7 +647,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
621
647
|
],
|
|
622
648
|
layoutConstraints: [
|
|
623
649
|
"Plain Color controls may render two per row only when no opacity is present.",
|
|
624
|
-
"Color
|
|
650
|
+
"Odd trailing plain Color controls in a multi-color bank keep the same half-width footprint as paired colors.",
|
|
651
|
+
"Color label visibility depends on user usefulness: palette variation banks omit per-item labels; distinct color roles keep labels.",
|
|
625
652
|
],
|
|
626
653
|
requiredAcceptance: [
|
|
627
654
|
"Prove the selected color affects product output, preview, or export.",
|
|
@@ -633,18 +660,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
633
660
|
"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.",
|
|
634
661
|
"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.",
|
|
635
662
|
"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.",
|
|
636
|
-
"
|
|
637
|
-
"
|
|
663
|
+
"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.",
|
|
664
|
+
"Make color label visibility a group-level decision: do not mix labeled and unlabeled items inside one semantic color bank.",
|
|
665
|
+
"Keep visible labels when colors edit distinct user-facing entities or roles, such as Fill, Stroke, Background, Connector, Object, or Highlight.",
|
|
666
|
+
"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.",
|
|
638
667
|
"The standalone default applies only to color-only sections; mixed semantic sections keep color grouped with nearby controls.",
|
|
639
668
|
"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.",
|
|
640
669
|
"Do not split a grouped object section into a separate generated Color section; if the color role is unclear, ask the user before implementation.",
|
|
641
670
|
"When one short numeric/text field and one Color field configure the same entity, keep them in one two-column inline layout group.",
|
|
642
|
-
'Mixed inline rows require visible labels on both controls.
|
|
671
|
+
'Mixed inline rows usually require visible labels on both controls. Toggle-plus-parameter rows are the section-owned exception: keep the Switch/Checkbox label visible and set the non-toggle parameter label to false; if the parameter label is needed, stack the controls instead. All 50/50 inline rows use the same horizontal column gap as paired Select controls. The required Background row uses the Switch label "Include" and sets 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.',
|
|
643
672
|
"Plain Color popovers must not show opacity controls. If opacity is editable, use ColorOpacity instead.",
|
|
644
673
|
"Product-output apps always expose renderer-owned output background color as a schema color target such as appearance.background or scene.background.",
|
|
645
674
|
'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.',
|
|
646
|
-
"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
|
|
675
|
+
"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.",
|
|
647
676
|
"Render multiple related color fields in one section with at most two colors per row.",
|
|
677
|
+
"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.",
|
|
648
678
|
],
|
|
649
679
|
},
|
|
650
680
|
gradient: {
|
|
@@ -674,7 +704,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
674
704
|
],
|
|
675
705
|
layoutConstraints: [
|
|
676
706
|
"Gradient is a full standalone compound control.",
|
|
677
|
-
"Gradient renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
|
|
707
|
+
"Gradient renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
|
|
678
708
|
],
|
|
679
709
|
requiredAcceptance: [
|
|
680
710
|
"Prove gradientType, angle, stop position, stop color, and stop opacity affect product output or export output.",
|
|
@@ -715,7 +745,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
715
745
|
],
|
|
716
746
|
layoutConstraints: [
|
|
717
747
|
"FontPicker owns its popup and internal footer controls.",
|
|
718
|
-
"FontPicker renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
|
|
748
|
+
"FontPicker renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
|
|
719
749
|
],
|
|
720
750
|
requiredAcceptance: [
|
|
721
751
|
"Prove fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity affect actual product text output.",
|
|
@@ -725,6 +755,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
725
755
|
"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.",
|
|
726
756
|
"Do not recreate FontPicker with a plain Select plus separate sliders; use type: \"fontPicker\" so the popup mechanics and footer controls stay intact.",
|
|
727
757
|
"Use one object value with fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity. Keep typography renderers wired to all eight parts.",
|
|
758
|
+
"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.",
|
|
728
759
|
"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.",
|
|
729
760
|
"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.",
|
|
730
761
|
"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.",
|
|
@@ -758,7 +789,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
758
789
|
layoutConstraints: [
|
|
759
790
|
"Use variant single for one standalone curve and RGB variant only for color-correction or channel-specific curves.",
|
|
760
791
|
"Single Curves is one labeled control and does not render internal dividers, even inside mixed sections.",
|
|
761
|
-
"RGB Curves is a compound channel control and renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each rendered divider and the control content; if it is the first control in that section, only the bottom internal divider renders and the top internal padding is removed, and if it is the only control in the section, only the parent section dividers render.",
|
|
792
|
+
"RGB Curves is a compound channel control and renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each rendered divider and the control content; if it is the first control in that section, only the bottom internal divider renders and the top internal padding is removed; if it is the last control in that section, only the top internal divider renders and the bottom internal padding is removed; and if it is the only control in the section, only the parent section dividers render.",
|
|
762
793
|
],
|
|
763
794
|
requiredAcceptance: [
|
|
764
795
|
"Prove curves.points affect product output; RGB curves also prove activeChannel affects output.",
|
|
@@ -828,7 +859,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
828
859
|
],
|
|
829
860
|
layoutConstraints: [
|
|
830
861
|
"ChannelMixer is a standalone compound control.",
|
|
831
|
-
"ChannelMixer renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
|
|
862
|
+
"ChannelMixer renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
|
|
832
863
|
],
|
|
833
864
|
requiredAcceptance: [
|
|
834
865
|
"Prove channelMixer.activeChannel and channelMixer.values both affect product output.",
|
|
@@ -856,6 +887,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
856
887
|
],
|
|
857
888
|
doNotReplaceWith: [
|
|
858
889
|
"Do not place upload UI on the canvas.",
|
|
890
|
+
"Do not draw a custom canvas empty-state design, CTA, fake sample output, decorative placeholder, or agent-invented preset source while waiting for uploaded content.",
|
|
859
891
|
"Do not build custom file buttons for source media import.",
|
|
860
892
|
],
|
|
861
893
|
acceptableAlternatives: [
|
|
@@ -863,20 +895,29 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
863
895
|
],
|
|
864
896
|
layoutConstraints: [
|
|
865
897
|
"FileDrop lives in the controls panel; single-layer apps use its preview and clear behavior.",
|
|
866
|
-
"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.",
|
|
898
|
+
"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.",
|
|
899
|
+
"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.",
|
|
867
900
|
],
|
|
868
901
|
requiredAcceptance: [
|
|
869
|
-
"Prove file import changes media state and product output; prove clear
|
|
902
|
+
"Prove file import changes media state and product output; prove clear, reorder, and section/global reset update source material.",
|
|
870
903
|
],
|
|
871
904
|
}),
|
|
872
905
|
aiUsageRules: [
|
|
873
906
|
"Use fileDrop for source material uploads in the controls panel, not on the canvas.",
|
|
907
|
+
'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
|
|
908
|
+
"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.",
|
|
909
|
+
"Do not add procedural Source Preset modes only to avoid an empty canvas. A default procedural or reference source is allowed only when the prompt/reference explicitly defines it and the worklog records that evidence.",
|
|
874
910
|
"In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
|
|
911
|
+
"In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
|
|
912
|
+
"In single-layer apps, global Reset controls and section reset must remove uploaded fileDrop source media and return the fileDrop target to defaultValue.",
|
|
875
913
|
"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.",
|
|
876
|
-
"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.",
|
|
914
|
+
"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.",
|
|
915
|
+
"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.",
|
|
916
|
+
"Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
|
|
917
|
+
"Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
|
|
877
918
|
"In multi-layer apps, deletion and visibility belong to the Layers panel; fileDrop remains an upload target.",
|
|
878
919
|
],
|
|
879
|
-
commands: ["media.delete", "media.import"],
|
|
920
|
+
commands: ["media.delete", "media.import", "media.reorder"],
|
|
880
921
|
},
|
|
881
922
|
imagePicker: {
|
|
882
923
|
...control("imagePicker", "ImagePicker", "standalone", "component-owned"),
|
|
@@ -946,8 +987,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
946
987
|
}),
|
|
947
988
|
aiUsageRules: [
|
|
948
989
|
"CodeTextarea is the multiline text input for any potentially long value, not only source code.",
|
|
949
|
-
"
|
|
950
|
-
"Use
|
|
990
|
+
"Do not use CodeTextarea for short single-line canvas text, button labels, names, titles, captions, badges, or short tokens; use TextInput.",
|
|
991
|
+
"Use text for short single-line strings such as names, button labels, small numeric values, compact prompts, titles, captions, and short tokens.",
|
|
992
|
+
"Use code only when the user may enter long prompts, multiline text, instructions, JSON, CSS, shader code, scripts, templates, or other long structured data.",
|
|
993
|
+
"If CodeTextarea has a short single-line default value, the schema description must make the long, multiline, or structured-content reason explicit.",
|
|
951
994
|
"CodeTextarea is a content editor and applies values while typing; do not wait for blur, Enter, or Cmd/Ctrl+Enter to update runtime state.",
|
|
952
995
|
"CodeTextarea height is capped at 12 visible text lines; long content scrolls inside the textarea instead of making the controls panel taller.",
|
|
953
996
|
"Do not name a section Code unless the product value is actually code; use the product role such as Prompt, Instructions, Template, JSON, Shader, or CSS.",
|
|
@@ -978,6 +1021,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
978
1021
|
aiUsageRules: [
|
|
979
1022
|
"Use custom controls only for product interactions that built-in controls cannot express.",
|
|
980
1023
|
"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.",
|
|
1024
|
+
"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.",
|
|
1025
|
+
"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.",
|
|
981
1026
|
"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.",
|
|
982
1027
|
"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.",
|
|
983
1028
|
"Every visible custom-control element must justify its space by enabling selection, ordering, preview, removal, upload, editing, or status that affects the product.",
|
|
@@ -1001,16 +1046,17 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1001
1046
|
aiUsageRules: [
|
|
1002
1047
|
"Choose canvas.sizing.mode from product context instead of copying a universal 1024px artboard.",
|
|
1003
1048
|
"Use intrinsic-media for single-layer upload/generation apps so imported media natural size becomes canvas.size.",
|
|
1004
|
-
"Use editable-output
|
|
1049
|
+
"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.",
|
|
1005
1050
|
"When no explicit product size is provided, the runtime default canvas is 16:9 at 1920x1080; do not reset a new product-output app to 1:1 unless the product meaning requires it.",
|
|
1006
|
-
"A user-provided base/default size is not a reason to remove size controls; model it as canvas.size plus editable-output
|
|
1007
|
-
"
|
|
1051
|
+
"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.",
|
|
1052
|
+
"Do not use fixed-output for generated product/output apps with export actions. Reserve fixed-output for non-product internal fixtures where width and height truly must never be user-editable, and prove that lock with canvasSizingCoverage fixed-output-size acceptance.",
|
|
1053
|
+
"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.",
|
|
1008
1054
|
"Resolved canvas.size exists for every canvas app, but visible Canvas width and Canvas height controls are mandatory only for editable-output sizing and do not depend on settingsTransfer.",
|
|
1009
1055
|
"If canvas.size is provided without an explicit sizing mode, defineToolcraft treats it as editable-output and adds Canvas width and Canvas height controls.",
|
|
1010
1056
|
"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.",
|
|
1011
1057
|
"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.",
|
|
1012
1058
|
"Aspect ratio presets are the only interaction that may resize both canvas dimensions from a preset; manual size inputs are exact output dimensions.",
|
|
1013
|
-
"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
|
|
1059
|
+
"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 1 to 2 without changing visible canvas size, and adding/enabling it requires targeted browser evidence that the canvas stays responsive at the selected scale.",
|
|
1014
1060
|
"After enabling canvas.renderScale, verify that canvas preview stays responsive while dragging sliders and other high-frequency controls at the selected scale.",
|
|
1015
1061
|
"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.",
|
|
1016
1062
|
"Do not enable canvas.renderScale for DOM/SVG/vector-native previews; preserve vector fidelity through native vector rendering instead of raster supersampling.",
|
|
@@ -1084,6 +1130,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1084
1130
|
"Component variants are accepted entities too; tests should fail if a non-default Toolcraft control variant falls back to the default variant or custom markup.",
|
|
1085
1131
|
"Conditional entities require fixtures that make the condition observable.",
|
|
1086
1132
|
"Use visibleWhen for mode-, type-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
|
|
1133
|
+
"When visibleWhen or disabledWhen references a selector for the same product entity or selected branch, keep the selector and its dependent controls in one semantic section. A section that exists only because one selector option is active is not a separate product section just because the branch uses a standalone control.",
|
|
1087
1134
|
"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.",
|
|
1088
1135
|
"Use disabledWhen for controls that belong to the current entity but are temporarily unavailable for the selected mode; the disabled value must be preserved.",
|
|
1089
1136
|
"Do not leave inactive conditional controls visible and enabled while making the renderer ignore them.",
|
|
@@ -1101,9 +1148,11 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1101
1148
|
performanceAcceptance: {
|
|
1102
1149
|
aiUsageRules: [
|
|
1103
1150
|
"Custom renderers must define performance budgets for media import, preview updates, control drags, and export/copy before implementation.",
|
|
1104
|
-
"Controls that change renderer workload
|
|
1105
|
-
"
|
|
1151
|
+
"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.",
|
|
1152
|
+
"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.",
|
|
1153
|
+
"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.",
|
|
1106
1154
|
"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.",
|
|
1155
|
+
"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.",
|
|
1107
1156
|
"Expensive renderers must cache decoded media, source pixels, glyph atlases, gradients, and other reusable inputs by media id, canvas size, and stable control keys.",
|
|
1108
1157
|
"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.",
|
|
1109
1158
|
"Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
|
|
@@ -1114,6 +1163,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1114
1163
|
"Renderer specs must include a Renderer Technique Decision Matrix with sourceRepresentation, productRepresentation, previewRenderer, exportRenderer, rendererWorkload, rendererStrategy, whyNotAlternativeStrategies, fidelityRisks, and performanceRisks.",
|
|
1115
1164
|
"Custom renderer apps must mirror the Renderer Technique Decision Matrix in typed rendererTechnique config so validation can reject contradictory renderer choices.",
|
|
1116
1165
|
"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.",
|
|
1166
|
+
"Custom renderer apps must declare rendererPipeline with render passes, cache keys, execution location, preview/export quality, and interaction invalidation before implementation.",
|
|
1167
|
+
"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.",
|
|
1168
|
+
"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.",
|
|
1117
1169
|
"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.",
|
|
1118
1170
|
"Editing handles must be DOM/SVG overlays, excluded from export, and written through runtime state instead of being drawn into the product raster layer.",
|
|
1119
1171
|
"Product foreground and editing handle renderer layers must declare uiSelector so browser tests can verify the visible layer exists.",
|
|
@@ -1122,17 +1174,18 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1122
1174
|
"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.",
|
|
1123
1175
|
"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.",
|
|
1124
1176
|
"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.",
|
|
1125
|
-
"Pixel-output renderers must
|
|
1177
|
+
"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.",
|
|
1126
1178
|
"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.",
|
|
1179
|
+
"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.",
|
|
1127
1180
|
"WebGL and WebGPU renderers must initialize contexts, programs, shaders, pipelines, textures, and large buffers once, then update uniforms or stable buffers when controls change.",
|
|
1128
1181
|
"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.",
|
|
1129
1182
|
"Do not create WebGL/WebGPU contexts, shader programs, textures, or requestAnimationFrame loops directly in the React render path.",
|
|
1130
1183
|
"Animation loops must cancel scheduled frames during cleanup.",
|
|
1131
1184
|
"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.",
|
|
1132
|
-
"If a generated app uses ImageData, getImageData, or putImageData for procedural output, performance validation must fail unless the
|
|
1185
|
+
"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.",
|
|
1133
1186
|
"Performance matrices must declare rendererStrategy so tests can distinguish none, dom, svg, canvas-2d, webgl, and webgpu renderer paths.",
|
|
1134
1187
|
"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.",
|
|
1135
|
-
"If a renderer cannot meet the budget,
|
|
1188
|
+
"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.",
|
|
1136
1189
|
],
|
|
1137
1190
|
capabilities: ["performance-budgets", "workload-control-tests"],
|
|
1138
1191
|
commands: [],
|
|
@@ -1184,6 +1237,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
|
|
|
1184
1237
|
"When the section is generic, mixed, missing, or otherwise weak context, include the affected entity or role in the label: Pattern color, Background opacity, Wave speed, Stroke width.",
|
|
1185
1238
|
"Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
|
|
1186
1239
|
"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.",
|
|
1240
|
+
"Generated product apps must export starterControlSectionInventory beside acceptance. Every product controls section declares title, exact targets, groupingReason, and entity or workflowStage; intentional splits of one target entity require workflowStage and splitReason on every split section.",
|
|
1241
|
+
"Section splitting must preserve dependency cohesion: a selector stays with the visibleWhen/disabledWhen controls it gates when they share the same target entity or selected branch. Use internal spacing/dividers or a more specific section title before splitting dependent branch controls away.",
|
|
1187
1242
|
"Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created setup/settings sections use the technical title Setup but render without a visible heading; sticky footer action sections use the technical title Export but render without a visible heading.",
|
|
1188
1243
|
"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.",
|
|
1189
1244
|
"Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
|
|
@@ -40,6 +40,11 @@ describe("Toolcraft template decision contract", () => {
|
|
|
40
40
|
expect(rule?.verdict).toBe("move-to-validator");
|
|
41
41
|
expect(rule?.enforcement).toContain("browser-helper");
|
|
42
42
|
expect(rule?.desiredBehavior).toMatch(/product result/i);
|
|
43
|
+
expect(rule?.desiredBehavior).toMatch(/uploaded or imported content is part/i);
|
|
44
|
+
expect(rule?.desiredBehavior).toMatch(/fake sample output/i);
|
|
45
|
+
expect(rule?.desiredBehavior).toMatch(/agent-invented preset modes/i);
|
|
46
|
+
expect(rule?.desiredBehavior).toMatch(/worklog records that evidence/i);
|
|
47
|
+
expect(rule?.currentConstraint).toMatch(/uploaded\/imported source-material flows/i);
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
it("keeps runtime shell bypass prevention in validators, not only docs", () => {
|
|
@@ -68,9 +68,9 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
68
68
|
{
|
|
69
69
|
area: "canvas",
|
|
70
70
|
currentConstraint:
|
|
71
|
-
"canvasContent must not contain buttons, forms, CTAs, helper text, upload prompts, menus, or
|
|
71
|
+
"canvasContent must not contain buttons, forms, CTAs, helper text, upload prompts, menus, settings UI, or invented placeholder/demo artwork for apps with uploaded/imported source-material flows.",
|
|
72
72
|
desiredBehavior:
|
|
73
|
-
"Canvas renders only product result, source material, renderer output, and valid product editing handles.",
|
|
73
|
+
"Canvas renders only real product result, source material, renderer output derived from current state, and valid product editing handles. If uploaded or imported content is part of the product source flow, the pre-content canvas stays neutral and runtime-backed until real content exists. Do not add agent-invented preset modes, demo backgrounds, CTA copy, fake sample output, or decorative placeholders to fill the canvas; a default procedural/reference source is allowed only when the prompt or reference explicitly defines it and the worklog records that evidence.",
|
|
74
74
|
enforcement: ["browser-helper", "starter-agents", "spec-checklist"],
|
|
75
75
|
id: "canvas-no-app-ui",
|
|
76
76
|
level: "invariant",
|
|
@@ -177,7 +177,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
177
177
|
currentConstraint:
|
|
178
178
|
"Product-output apps expose final output delivery through sticky footer panelActions.",
|
|
179
179
|
desiredBehavior:
|
|
180
|
-
'Static products include Export PNG plus an "Image Export" section for format and 2K/4K/8K resolution; animated products include Export Video and Export PNG plus "Video Export" settings. Copy can be secondary, but it does not replace export. Product apps expose a required "Background" section directly before export settings, with a Switch labeled "Include" and a background color control with label false in one equal-width row. Standard
|
|
180
|
+
'Static products include Export PNG plus an "Image Export" section for format and 2K/4K/8K resolution; animated products include Export Video and Export PNG plus "Video Export" settings. Copy can be secondary, but it does not replace export. Product apps expose a required "Background" section directly before export settings, with a Switch labeled "Include" and a background color control with label false in one equal-width row. Standard helpers own runtime PNG transparency, live preview product-background visibility, and selected image dimensions or retina fallback, while Toolcraft canvas backing and video keep the background.',
|
|
181
181
|
enforcement: ["acceptance-validator", "performance-validator", "browser-helper", "starter-agents"],
|
|
182
182
|
id: "output-export-required",
|
|
183
183
|
level: "invariant",
|
|
@@ -189,7 +189,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
189
189
|
currentConstraint:
|
|
190
190
|
"Labels, color placement, section grouping, selector order, and inline density need product-aware decisions.",
|
|
191
191
|
desiredBehavior:
|
|
192
|
-
"Apps
|
|
192
|
+
"Apps export a Control Section Inventory before schema authoring: every product controls section has a product entity or workflow stage, exact targets, a grouping reason, and split evidence when one target entity is intentionally divided.",
|
|
193
193
|
enforcement: ["acceptance-validator", "schema-normalization", "docs", "starter-agents"],
|
|
194
194
|
id: "controls-layout-heuristics",
|
|
195
195
|
level: "heuristic",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getToolcraftRetinaExportPixelRatio,
|
|
9
9
|
getToolcraftRetinaExportSize,
|
|
10
10
|
shouldIncludeToolcraftExportBackground,
|
|
11
|
+
shouldIncludeToolcraftPreviewBackground,
|
|
11
12
|
} from "./export";
|
|
12
13
|
|
|
13
14
|
function createState(schema = defineToolcraft({ canvas: { enabled: true }, panels: {} })): ToolcraftState {
|
|
@@ -89,6 +90,36 @@ describe("Toolcraft export helpers", () => {
|
|
|
89
90
|
expect(shouldIncludeToolcraftExportBackground({ format: "video", schema })).toBe(true);
|
|
90
91
|
});
|
|
91
92
|
|
|
93
|
+
it("uses include-background runtime state for live preview background", () => {
|
|
94
|
+
const state = createState();
|
|
95
|
+
|
|
96
|
+
expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true);
|
|
97
|
+
|
|
98
|
+
state.values["export.includeBackground"] = false;
|
|
99
|
+
expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false);
|
|
100
|
+
|
|
101
|
+
state.values["export.includeBackground"] = true;
|
|
102
|
+
expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("keeps video export background independent from preview include state", () => {
|
|
106
|
+
const schema = defineToolcraft({
|
|
107
|
+
canvas: { enabled: true },
|
|
108
|
+
export: {
|
|
109
|
+
png: {
|
|
110
|
+
background: "transparent",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
panels: {},
|
|
114
|
+
});
|
|
115
|
+
const state = createState(schema);
|
|
116
|
+
|
|
117
|
+
state.values["export.includeBackground"] = false;
|
|
118
|
+
|
|
119
|
+
expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false);
|
|
120
|
+
expect(shouldIncludeToolcraftExportBackground({ format: "video", schema })).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
92
123
|
it("uses at least 2x pixel ratio for retina export", () => {
|
|
93
124
|
expect(getToolcraftRetinaExportPixelRatio(1)).toBe(2);
|
|
94
125
|
expect(getToolcraftRetinaExportPixelRatio(1.5)).toBe(2);
|
|
@@ -16,6 +16,11 @@ export type ToolcraftExportBackgroundOptions = {
|
|
|
16
16
|
schema: ResolvedToolcraftAppSchema;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
export type ToolcraftPreviewBackgroundOptions = {
|
|
20
|
+
includeBackgroundTarget?: string;
|
|
21
|
+
state: ToolcraftState;
|
|
22
|
+
};
|
|
23
|
+
|
|
19
24
|
export type ToolcraftExportSizeOptions = {
|
|
20
25
|
devicePixelRatio?: number;
|
|
21
26
|
state: ToolcraftState;
|
|
@@ -129,6 +134,42 @@ export function shouldIncludeToolcraftExportBackground({
|
|
|
129
134
|
return schema.export.png.background !== "transparent";
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
export function shouldIncludeToolcraftPreviewBackground({
|
|
138
|
+
includeBackgroundTarget = "export.includeBackground",
|
|
139
|
+
state,
|
|
140
|
+
}: ToolcraftPreviewBackgroundOptions): boolean {
|
|
141
|
+
const includeBackgroundValue = state.values[includeBackgroundTarget];
|
|
142
|
+
|
|
143
|
+
if (typeof includeBackgroundValue === "boolean") {
|
|
144
|
+
return includeBackgroundValue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (typeof includeBackgroundValue === "string") {
|
|
148
|
+
const normalizedValue = includeBackgroundValue.trim().toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
normalizedValue === "false" ||
|
|
152
|
+
normalizedValue === "off" ||
|
|
153
|
+
normalizedValue === "no" ||
|
|
154
|
+
normalizedValue === "transparent" ||
|
|
155
|
+
normalizedValue === "exclude"
|
|
156
|
+
) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
normalizedValue === "true" ||
|
|
162
|
+
normalizedValue === "on" ||
|
|
163
|
+
normalizedValue === "yes" ||
|
|
164
|
+
normalizedValue === "include"
|
|
165
|
+
) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
132
173
|
export function createToolcraftPngExportCanvas({
|
|
133
174
|
background = "#000000",
|
|
134
175
|
canvasFactory = () => document.createElement("canvas"),
|