@pixel-point/toolcraft 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +42 -5
- package/src/generate.test.mjs +40 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +251 -47
- package/templates/runtime/contracts/component-contracts.ts +130 -57
- package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
- package/templates/runtime/contracts/decision-contracts.ts +22 -9
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +916 -108
- package/templates/runtime/react/controls-panel.tsx +136 -30
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
- package/templates/runtime/schema/define-toolcraft.ts +140 -246
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +44 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +810 -21
- package/templates/runtime/testing/performance.ts +823 -53
- package/templates/starter/AGENTS.md +24 -18
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
- package/templates/starter/docs/toolcraft/component-rules.md +75 -42
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +37 -11
- package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
- package/templates/starter/scripts/toolcraft-port.mjs +280 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
- package/templates/starter/src/app/starter-acceptance.ts +1453 -97
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +8 -25
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
- package/templates/ui/components/panel/panel-section.tsx +29 -5
|
@@ -12,7 +12,7 @@ If a built-in owner is discovered after a custom workaround, replace the workaro
|
|
|
12
12
|
|
|
13
13
|
Common exact-owner choices:
|
|
14
14
|
|
|
15
|
-
- Use `gradient` for adjustable gradients, color transitions, gradient fills, stops, type, and angle. Do not replace it with two `color` controls. The built-in Gradient owns type/angle, the draggable stop track, and the Stops list; the full Gradient control uses content-width internal dividers only when it shares a section with sibling controls, with 18px between each divider and the control content. If Gradient is the first control in that section, only the bottom internal divider renders.
|
|
15
|
+
- Use `gradient` for adjustable gradients, color transitions, gradient fills, stops, type, and angle. Do not replace it with two `color` controls. The built-in Gradient owns type/angle, the draggable stop track, and the Stops list; the full Gradient control uses content-width internal dividers only when it shares a section with sibling controls, with 18px between each divider and the control content. If Gradient is the first control in that section, only the bottom internal divider renders; if it is last, only the top internal divider renders.
|
|
16
16
|
- Use `fontPicker` for typography that includes font family, weight, size, text case, text color/opacity, letter spacing, or line height.
|
|
17
17
|
- Use `colorOpacity` when one product entity owns both color and opacity.
|
|
18
18
|
- Use `rangeSlider` or `rangeInput` for lower/upper bounds or from/to ranges.
|
|
@@ -23,9 +23,14 @@ Common exact-owner choices:
|
|
|
23
23
|
- Use `palette` only for constrained design-token color choices with both family and shade: brand palette, Tailwind-like token color, style-guide color scale, semantic palette family, or theme accent token.
|
|
24
24
|
- Use `actions` for local section commands that affect only the nearby entity, such as randomize palette, normalize weights, sort glyphs, clear selection, duplicate item, or reset current stop.
|
|
25
25
|
- Use `collectionActions` for repeatable product entities whose actual item list can grow or shrink, such as colors, glyphs, symbols, points, rules, variants, object entries, or typography style entries. Use it instead of a count slider when the user edits the actual set. The item list must be runtime state that changes preview/export, not panel-only row chrome. The collection control shows the collection `label` on the left and remove/add icon buttons on the right. Homogeneous repeated items do not show visible per-item labels like `Color 1`, `Color 2`, `Item 1`, or `Item 2` when the collection label already names the group. Item controls should use built-ins such as `color`, `colorOpacity`, `text`, `select`, `segmented`, `slider`, `switch`, `checkbox`, `rangeInput`, or `fontPicker` before any custom renderer. Use `fontPicker` as the item control when each item is a text style or typography entity; do not split its owned fields into neighboring collection controls.
|
|
26
|
-
- `actions`
|
|
26
|
+
- For a single `actions` button, the control label and the button label must not be identical. Keep the button as the command verb and make the control label a concise one- or two-word context such as `Ink wash`, `Palette action`, or `Current layer`.
|
|
27
|
+
- If an `actions` control has a visible label, the label is always above the buttons. Do not use a side-label layout with buttons on the right.
|
|
28
|
+
- Actions render in 50% cells: one button uses the left half, two buttons fill one row, and larger groups continue in two columns.
|
|
29
|
+
- Do not stretch an odd trailing action full-width or center it; keep it in the left 50% cell.
|
|
27
30
|
- Use `panelActions` for sticky final product actions such as export, copy, generate, apply, or download.
|
|
28
31
|
|
|
32
|
+
When upload/import is part of the source-material flow, `fileDrop` owns the empty/upload state. Do not put a custom pre-upload design, CTA, helper copy, fake sample output, decorative placeholder, or agent-made source preset on the canvas. A default procedural/reference source is allowed only when the prompt or reference explicitly defines it and the worklog records that evidence. If the default source is a file, image, or background image, declare it in `media.defaultAssets` with `sourceTarget` matching the `fileDrop` control so it renders as an attached file rather than a hidden renderer constant.
|
|
33
|
+
|
|
29
34
|
Small action buttons inside custom controls are for item-level actions such as remove, reorder, add stop, or delete stop. Use schema `actions` for section-level local commands. Keep final product actions in `panelActions`, keep timeline transport in the top timeline, and keep global reset in the controls panel header.
|
|
30
35
|
|
|
31
36
|
For local reset-like `actions`, use product-specific values such as `reset-current-layer`, `reset-palette`, or `reset-current-stop` and handle them through `ToolcraftApp onPanelAction`. Do not use a bare `reset` value unless the action intentionally runs global `controls.reset`.
|
|
@@ -33,7 +38,7 @@ For local reset-like `actions`, use product-specific values such as `reset-curre
|
|
|
33
38
|
## Dividers
|
|
34
39
|
|
|
35
40
|
- Full-width dividers belong only to panel sections.
|
|
36
|
-
- Large built-in compound controls inside a section render content-width internal dividers only when their parent section contains more than one visible control item. Keep 18px between each rendered internal divider and the compound control content. If the compound control is the first item in that section, render only its bottom internal divider and remove the top internal padding. This applies to `gradient`, `fontPicker`, RGB `curves`, `channelMixer`, and `palette`. Single `curves` are one labeled control and do not render internal dividers.
|
|
41
|
+
- Large built-in compound controls inside a section render content-width internal dividers only when their parent section contains more than one visible control item. Keep 18px between each rendered internal divider and the compound control content. If the compound control is the first item in that section, render only its bottom internal divider and remove the top internal padding. If it is the last item, render only its top internal divider and remove the bottom internal padding. This applies to `gradient`, `fontPicker`, RGB `curves`, `channelMixer`, and `palette`. Single `curves` are one labeled control and do not render internal dividers.
|
|
37
42
|
- If a section contains exactly one control, whether simple or compound, render only the parent section dividers.
|
|
38
43
|
- Do not add full-width borders inside a compound control, and do not put dividers only around an internal subsection such as Gradient Stops.
|
|
39
44
|
- Small compound fields such as `colorOpacity` and `rangeInput` stay inline fields without section dividers.
|
|
@@ -53,7 +58,7 @@ Visual discrete sliders must declare `step`; the runtime derives one marker per
|
|
|
53
58
|
|
|
54
59
|
Schema sliders always render stacked at full width. Do not put `slider` or `rangeSlider` controls in two-column inline rows. The only built-in exception is `fontPicker`, whose letter-spacing and line-height footer sliders stay paired inside that component.
|
|
55
60
|
|
|
56
|
-
Use slider `unit` only for measurement
|
|
61
|
+
Use slider `unit` only for real measurement suffixes: `%`, `px`, `°`, `s`, `ms`, `fps`, `rows`, `cols`, or a similarly useful domain unit. Do not use `unit: "x"`; scale, multiplier, intensity, opacity, strength, depth, and shader amount sliders display plain numbers unless a real measurement unit applies. Do not use `unit` to repeat the entity already named by the section or label. Avoid `Letters` + `letters`, `Shape Density / Count` + `shapes`, `Words` + `words`, `Symbols` + `symbols`, `Items` + `items`, `Particles` + `particles`, and `Layers` + `layers`. If the numeric value needs an entity noun to make sense, rename the label or section instead of appending the noun to the value. Compact units render tight (`70%`, `24px`, `8s`); word or acronym units render with a space (`5 cols`, `17 fps`) only when they are truly needed.
|
|
57
62
|
|
|
58
63
|
Slider value labels are editable only when they contain a numeric value. Textual state labels such as `Normal` are display-only and must not expose hover or click editing affordances.
|
|
59
64
|
|
|
@@ -61,38 +66,28 @@ Range sliders are always full-width two-thumb controls. Do not put a `rangeSlide
|
|
|
61
66
|
|
|
62
67
|
Range slider value editing accepts common range separators such as `20/80`, `20-80`, `20 - 80`, `20 80`, and en-dash ranges. Use the built-in parser instead of adding custom label parsing.
|
|
63
68
|
|
|
64
|
-
Discrete sliders must still drag smoothly. Heavy preview work
|
|
65
|
-
|
|
66
|
-
When a slider or range slider is intentionally unavailable, use schema `disabled: true`. Do not draw custom disabled-looking slider rows or disable only the renderer response while leaving the control active.
|
|
69
|
+
Discrete sliders must still drag smoothly. Heavy preview work may be coalesced, cached, or split into lightweight live feedback plus heavier refinement, but the canvas/product output must not stay unchanged until pointer release.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Use `visibleWhen` when a slider or range slider is meaningful only in some mode, type, source, include, variant, or count state. Inactive branches disappear so the panel shows only controls that can be used in the current state.
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
fillAmount: {
|
|
72
|
-
type: "slider",
|
|
73
|
-
label: "Fill level",
|
|
74
|
-
target: "distribution.fillAmount",
|
|
75
|
-
disabledWhen: {
|
|
76
|
-
target: "distribution.fillMode",
|
|
77
|
-
equals: "full",
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
```
|
|
73
|
+
Do not use schema `disabled: true` or `disabledWhen` for product sliders and range sliders. Product panels should show only controls usable in the current state. Use `visibleWhen` for unavailable product states instead of rendering disabled controls.
|
|
81
74
|
|
|
82
|
-
|
|
75
|
+
For mode/type/source/include/count branches, hide with `visibleWhen` instead of disabling. Example: when Texture is `Off`, hide texture pattern, upload, blend, and opacity. When Texture is `Image`, show the image uploader and shared texture settings. When `Shades` is `2`, `Shade 3`, `Shade 4`, and `Shade 5` are not visible. Do not keep inactive controls visible while making the renderer ignore them.
|
|
83
76
|
|
|
84
|
-
|
|
77
|
+
If `visibleWhen` points to a selector for the same target entity or selected branch, keep the selector and the dependent controls in the same 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. Use one section with conditional controls; split only when the dependent branch is a separate product entity with its own workflow and acceptance evidence.
|
|
85
78
|
|
|
86
79
|
## Palette
|
|
87
80
|
|
|
88
81
|
Use `palette` only when the product needs a constrained token palette: family plus shade. It is for design-system color tokens, not for arbitrary color entry.
|
|
89
82
|
|
|
90
|
-
Use `color` for free hex colors, `colorOpacity` when opacity belongs to the same color entity, `gradient` for color transitions, and `fontPicker` when the color belongs to typography. Browser acceptance must change both `palette.family` and `palette.shade` and prove the rendered/exported output consumes both parts.
|
|
83
|
+
Use `color` for free hex colors, `colorOpacity` when opacity belongs to the same color entity, `gradient` for color transitions, and `fontPicker` when the color belongs to typography. Palette is a live control: family and shade changes update runtime state immediately, before delayed persistence/commit settles, so the next canvas interaction uses the selected token. Browser acceptance must change both `palette.family` and `palette.shade` and prove the rendered/exported output consumes both parts.
|
|
91
84
|
|
|
92
85
|
## Segmented Controls
|
|
93
86
|
|
|
94
87
|
Use segmented controls only for compact mode choices that preserve every cell's internal padding.
|
|
95
88
|
|
|
89
|
+
Segmented controls are full-width. Do not place `segmented` beside Switch, Color, Select, or another control in a two-column inline row; use `select` when a finite choice must occupy a half-width column.
|
|
90
|
+
|
|
96
91
|
Limits:
|
|
97
92
|
|
|
98
93
|
- at most four options;
|
|
@@ -101,21 +96,35 @@ Limits:
|
|
|
101
96
|
|
|
102
97
|
If cells clip, collide, lose padding, or force labels into adjacent cells, shorten labels first. If compact labels still fail, use `select`.
|
|
103
98
|
|
|
99
|
+
## Select Controls
|
|
100
|
+
|
|
101
|
+
Standalone `select` controls render stacked and full-width: label above, dropdown below. Do not use the old compact side-label form with label on the left and dropdown on the right.
|
|
102
|
+
|
|
103
|
+
Use a two-column inline row only for related short `select` pairs that tune one workflow or entity, such as export `Format` and `Resolution`. If either label or selected value clips, truncates, or loses internal padding, stack the pair and record the fit reason.
|
|
104
|
+
|
|
105
|
+
## Sliders
|
|
106
|
+
|
|
107
|
+
Slider and range slider controls are live canvas controls. Dragging a thumb must update runtime state and product output while the drag is in progress, not only on pointer release, blur, an Apply action, or a final commit. Browser acceptance should drag the real control and prove the canvas/product observable changes during the interaction.
|
|
108
|
+
|
|
109
|
+
If live slider updates are slow, fix the renderer path first: update uniforms or stable buffers, cache decoded media and expensive derived inputs, coalesce preview work to `requestAnimationFrame`, cancel stale async renders, move heavy work off React renders, or change renderer strategy. Only in an extreme measured performance ceiling may the app use a degraded live preview or delayed heavy refinement; even then, the user must see immediate canvas feedback during drag and the worklog must record the evidence.
|
|
110
|
+
|
|
104
111
|
## Sections
|
|
105
112
|
|
|
106
113
|
Build controls-panel sections from product entities and workflow stages, not component types. Keep sections discrete: two to seven product controls is the normal size. When a section grows past seven controls or mixes several meanings, split it into specific sections such as `Flow Motion`, `Flow Geometry`, `Letter Burst`, `Shape Colors`, `Logo Glow`, `Logo Plate`, or `Text Block`. Do not reuse the same section title for multiple sections.
|
|
107
114
|
|
|
108
|
-
|
|
115
|
+
Section splitting must preserve dependency cohesion. A selector that chooses a mode, type, source, variant, or include state stays with the controls it gates when they share the same product entity. Prefer internal compound-control dividers, tighter labels, or a more specific section title before moving a gated branch into a separate section.
|
|
116
|
+
|
|
117
|
+
Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created `Setup` renders as the first visible headerless controls block with no title, reset action, collapse button, or collapsed state; sticky footer action sections use the technical title `Export` but render without a visible heading. Do not omit a title on app-authored body sections to avoid naming decisions; choose the nearest honest product context instead.
|
|
109
118
|
|
|
110
119
|
Every visible section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon. Do not hand-build section headers in generated apps.
|
|
111
120
|
|
|
112
121
|
Section expand/collapse uses the standard runtime height/opacity animation. Do not replace it with instant custom section visibility.
|
|
113
122
|
|
|
114
|
-
|
|
123
|
+
Section collapsed/expanded state persists as a per-app runtime UI preference. It is not undo/redo state, not settings import/export state, and `Reset controls` must not clear it. Runtime `Setup` is not collapsible; sticky footer `Export` sections are not collapsible.
|
|
115
124
|
|
|
116
125
|
Ordinary 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`.
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
Runtime `Setup` and ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Sticky footer action sections keep their dedicated spacing.
|
|
119
128
|
|
|
120
129
|
## Colors
|
|
121
130
|
|
|
@@ -133,7 +142,7 @@ Use `colorOpacity` when one product entity owns both color and opacity, such as
|
|
|
133
142
|
|
|
134
143
|
When one short numeric/text field and one plain `color` field configure the same entity, they can share a two-column inline row. Example: `Mask size` and `Color` belong in the same `Mask` row instead of two stacked rows. Do not put `colorOpacity` in inline rows.
|
|
135
144
|
|
|
136
|
-
Mixed inline rows require label parity: every field in that row has a visible label.
|
|
145
|
+
Mixed inline rows usually require label parity: every field in that row has a visible label. Toggle-plus-parameter rows are the section-owned exception: keep the `switch`/`checkbox` label visible and set the non-toggle parameter to `label: 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; do not give toggle-plus-parameter rows a separate wider or narrower gap. The required `Background` section row uses the switch label `Include` beside the background color parameter with `label: false`. Palette variation color banks are the other exception when the group or section label already names the color bank.
|
|
137
146
|
|
|
138
147
|
Renderer-owned output background is a base product control. Use a schema `color` target such as `appearance.background` or `scene.background`, add an `export.includeBackground` control for PNG transparency, and make preview/export read those runtime values. Keep them in one required `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`. Use one equal-width inline row with `export.includeBackground` on the left and `appearance.background` on the right when no other fit rule is violated. The switch label is `Include`, not `Include background`; the background color control uses `label: false`. Each control occupies one half of the row; do not shrink the toggle column to intrinsic width. `export.includeBackground` controls PNG alpha and live preview product-background visibility through `shouldIncludeToolcraftPreviewBackground(state)`; it must not make the Toolcraft canvas shell/backing or video output transparent. Do not hardcode a configurable background in CSS, Canvas `fillStyle`, or WebGL clear color.
|
|
139
148
|
|
|
@@ -143,7 +152,11 @@ Use `fileDrop` for source material uploads in the controls panel. Do not place u
|
|
|
143
152
|
|
|
144
153
|
Use `assetKind: "image"` for image-only source uploads and `assetKind: "file"` for arbitrary uploaded files. Image mode accepts images only by default. File mode accepts any file by default unless `accept` narrows the allowed extensions or MIME types.
|
|
145
154
|
|
|
146
|
-
In single-layer apps, the runtime shows uploaded image preview and clear button in the file control. Clearing
|
|
155
|
+
In single-layer apps, the runtime shows uploaded image preview and clear button in the file control. Clearing removes the attached source from the renderer and canvas. Global Reset controls and section reset restore `media.defaultAssets` for that fileDrop target; when no default asset exists, reset removes uploaded source material and returns the fileDrop target to `defaultValue`. If users can delete/reorder/transform predefined attached files and that state should survive reload, include `"media"` in schema persistence.
|
|
156
|
+
|
|
157
|
+
In image mode, the runtime owns image transform actions directly below the uploader: `90° Right`, `Flip horizontal`, and `Flip vertical`. They render through the built-in actions-control in one three-column row with compact visible labels: `90°`, `Flip H`, `Flip V`; keep a 6px vertical gap between the uploader and action row. Do not create a custom image action button grid. With exactly one uploaded image, those actions are visible immediately. With multiple uploaded images, the user selects a thumbnail first; until then the actions are hidden, and once shown they apply only to the selected image. Product preview/export must consume `state.mediaAssets[].transform` rather than keeping separate image transform state.
|
|
158
|
+
|
|
159
|
+
The FileDrop panel preview is not product canvas rendering. It keeps a stable preview frame across rotate/flip actions and contains the transformed bitmap inside that frame, so horizontal or vertical uploads are never cropped by the control preview. Canvas/product renderers may still use cover/crop when the uploaded image is source material.
|
|
147
160
|
|
|
148
161
|
Use `multiple: true` when the app needs several uploaded images as one source set. The runtime appends media, switches to a sortable four-column thumbnail grid when more than one image is present, puts the add-more tile last, and keeps per-image removal inside the file control. Dragging thumbnails updates runtime media order; preview, export, and renderer mapping must consume that order instead of keeping a separate product-only order.
|
|
149
162
|
|
|
@@ -151,6 +164,8 @@ In file mode, uploaded files render as a sortable list with a paperclip icon, fi
|
|
|
151
164
|
|
|
152
165
|
When an app contains both image and file uploaders, canvas drops route by asset kind. Image files prefer visible image uploaders; non-image files prefer visible file uploaders; file uploaders may accept images only when no image uploader matches. Product renderers must consume `state.mediaAssets` filtered by `sourceTarget` and runtime media order.
|
|
153
166
|
|
|
167
|
+
When uploaded images are used as canvas/background source material, use `editable-output`, draw them with cover/crop behavior, scale proportionally until the current canvas bounds are fully covered, leave canvas dimensions and Setup controls unchanged, and crop overflow at the canvas bounds.
|
|
168
|
+
|
|
154
169
|
In multi-layer apps, deletion and visibility belong to the Layers panel; `fileDrop` stays an upload target.
|
|
155
170
|
|
|
156
171
|
## Image Picker
|
|
@@ -193,6 +208,14 @@ Use variants by product meaning:
|
|
|
193
208
|
- `chromaOffset`: RGB or chromatic offset;
|
|
194
209
|
- `toneBias`: split-tone, duotone, or color-grading bias.
|
|
195
210
|
|
|
211
|
+
Default/spatial vector pads use screen-coordinate movement. Dragging the pad left/up lowers `vector.x` and `vector.y`, so an object on the canvas moves left/up without renderer-side Y inversion. Use `coordinateMode: "cartesian"` only when the product intentionally exposes mathematical Y-up coordinates.
|
|
212
|
+
|
|
213
|
+
Vector pad value labels are compact UI labels, not raw state dumps. They show rounded normalized coordinates and must never expose floating-point tails such as `-0.07070312499999998`.
|
|
214
|
+
|
|
215
|
+
Double-clicking the vector pad resets both axes to the control default through the normal runtime value update, matching the reset button in the section header. If no default is defined, the fallback is `0,0`. Do not add a separate custom reset button for basic pad reset behavior.
|
|
216
|
+
|
|
217
|
+
Holding Shift while dragging a vector pad locks movement to the dominant axis and must not select text or page content. Use the built-in `vector` control for constrained two-axis movement instead of creating a custom pad.
|
|
218
|
+
|
|
196
219
|
Do not add custom vector sizing props. Choose the right number, variant, and section grouping, then let runtime sizing handle the pad.
|
|
197
220
|
|
|
198
221
|
## Curves
|
|
@@ -216,11 +239,11 @@ Acceptance for curves should include an off-center control point near an edge so
|
|
|
216
239
|
|
|
217
240
|
## Text And Code
|
|
218
241
|
|
|
219
|
-
Use `text` for short single-line strings: names, small values, compact prompts, titles, and tokens.
|
|
242
|
+
Use `text` for short single-line strings: button labels, canvas labels, names, small values, compact prompts, titles, captions, badges, and tokens.
|
|
220
243
|
|
|
221
244
|
For `text`, separate content from settings. `commitMode` defaults to `"content"`: content strings such as prompts, names, titles, tokens, and short text update while the user types. 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. Canvas width and Canvas height are runtime-owned editable-size fields and always commit on blur or Enter.
|
|
222
245
|
|
|
223
|
-
Use `code` / `CodeTextarea` as the base multiline content editor for any potentially long value: prompts, instructions, JSON, CSS, shader code, scripts, templates, or other structured text. It applies while typing, is capped at 12 visible lines, and long content scrolls inside the textarea instead of making the controls panel taller. Do not name a section `Code` unless the product value is actually code.
|
|
246
|
+
Use `code` / `CodeTextarea` as the base multiline content editor for any potentially long value: long prompts, multiline text, instructions, JSON, CSS, shader code, scripts, templates, or other structured text. It applies while typing, is capped at 12 visible lines, and long content scrolls inside the textarea instead of making the controls panel taller. Do not use it for short one-line button/canvas text such as `Glass`, `Submit`, `Title`, or `Badge`; use `text`. If the default value is short but the intended input is long or structured, make that reason explicit in `description`. Do not name a section `Code` unless the product value is actually code.
|
|
224
247
|
|
|
225
248
|
## Labels
|
|
226
249
|
|
|
@@ -240,7 +263,7 @@ Switch and checkbox labels name the setting context, not the action. Do not pref
|
|
|
240
263
|
|
|
241
264
|
Two adjacent `switch` or `checkbox` controls for the same product entity must share one inline row when every visible label fits without truncation. Use short one- or two-word labels such as `Snap X` and `Snap Y`, or `Glow` and `Loop`. The runtime auto-pairs safe adjacent toggles by target entity; use explicit layout groups only when pairing a toggle with a non-toggle parameter. If either label would truncate in half-width, remove the inline group and let the toggles stack.
|
|
242
265
|
|
|
243
|
-
A single `switch` or `checkbox` may share an inline row with one related parameter control when the toggle label fits and the controls edit the same entity. This row is always equal-width: each control occupies one half. Example: `Loop` plus
|
|
266
|
+
A single `switch` or `checkbox` may share an inline row with one related parameter control when the toggle label fits and the controls edit the same entity. This row is always equal-width: each control occupies one half, using the same horizontal column gap as a paired `select` row. The non-toggle parameter uses `label: false`; if that parameter label is needed for clarity, stack the controls instead. Example: `Loop` plus an unlabeled duration field, or `Include` plus unlabeled background color inside the required `Background` section. If the section title already names the toggle context, shorten the toggle label instead of repeating the title.
|
|
244
267
|
|
|
245
268
|
## Layers
|
|
246
269
|
|
|
@@ -254,17 +277,23 @@ When Layers are enabled, browser tests must use the real LayersPanel UI: select,
|
|
|
254
277
|
|
|
255
278
|
Before choosing timeline mode for an animated product, write an Animation Intent Inventory:
|
|
256
279
|
|
|
257
|
-
- `timeline-playback`: user-facing play, pause, scrub, duration, loop, restart, progress,
|
|
280
|
+
- `timeline-playback`: user-facing play, pause, scrub, duration, loop, restart, progress, export-at-time, or video export.
|
|
258
281
|
- `timeline-keyframes`: editable diamonds, rows, easing, or keyframe evaluation.
|
|
259
|
-
- `autonomous`: decorative or self-running output with no user-facing transport.
|
|
282
|
+
- `autonomous`: decorative or self-running output with no user-facing transport and no video export.
|
|
260
283
|
|
|
261
|
-
|
|
284
|
+
Product output animation uses the top Toolcraft timeline. Use no timeline only for non-product autonomous decorative/self-running motion without video export, and declare `starterTransferMode.animationIntent.mode = "autonomous"` with coverage proving no play/pause, scrub, duration, loop, export-at-time, product animation, or video export behavior.
|
|
262
285
|
|
|
263
|
-
Use playback timeline for play, pause, scrub, duration, loop, restart,
|
|
286
|
+
Use playback timeline for play, pause, scrub, duration, loop, restart, export-at-time, or video export.
|
|
287
|
+
|
|
288
|
+
When `panels.timeline` is enabled for a new Toolcraft app, `starterTransferMode.animationIntent` must match it: `mode: "timeline-playback"` for playback, or `mode: "timeline-keyframes"` for keyframes.
|
|
264
289
|
|
|
265
290
|
Playback renderers must read `state.timeline.currentTimeSeconds`, `state.timeline.durationSeconds`, `state.timeline.isPlaying`, and loop state from the runtime. The full animation cycle must span `state.timeline.durationSeconds`; do not hard-code a separate local animation duration such as 3s or 8s inside the renderer.
|
|
266
291
|
|
|
267
|
-
|
|
292
|
+
Product animation loop means a seamless forward-only cycle by default. Motion advances in one direction, the first and last frames stitch without a visible jump, and mirror, yoyo, ping-pong, or reverse loops are allowed only when the user explicitly requests that behavior as a product mode.
|
|
293
|
+
|
|
294
|
+
When the product has a known loop duration, declare it as `panels.timeline.defaultDurationSeconds`; the runtime timeline starts from that loop duration instead of an unrelated 8s default. Timeline animation intent must also declare `loopDuration` with `source`, `seconds`, and `evidence`. Valid sources are `reference`, `user-request`, and `product-derived`; runtime/template fallback 8s is not a valid source. `panels.timeline.defaultDurationSeconds` must match `animationIntent.loopDuration.seconds` so the initial timeline UI shows the declared product loop. Renderers may compute an initial loop duration default during app initialization or reset, but they must not watch `state.timeline.durationSeconds` and dispatch `timeline.setDuration` back to a computed local value. Once the user edits the timeline duration, that runtime value is the loop duration source of truth and renderer progress must map into it. Use `getToolcraftTimelineLoopTime` or `getToolcraftTimelineLoopProgress` to derive loop phase from `state.timeline.currentTimeSeconds` and `state.timeline.durationSeconds`; do not hand-roll wall-clock, fixed-duration, mirror, yoyo, ping-pong, or reverse phase math. Changing duration must preserve seamless forward-loop semantics: one complete cycle maps from `0` to `state.timeline.durationSeconds`, the first and last frames still stitch, direction does not reverse, and the renderer must not switch to wall-clock time or a fixed local duration.
|
|
295
|
+
|
|
296
|
+
For reference-runtime-clone apps that map reference transport to the Toolcraft timeline, the same duration proof lives on `starterTransferMode.referenceTimeline.loopDuration`. `referenceTimeline.mode: "toolcraft-playback"` or `"toolcraft-keyframes"` must declare `loopDuration` with source, seconds, and evidence, and `panels.timeline.defaultDurationSeconds` must match it. Do not let a reference clone inherit the runtime/template 8s default unless the reference or user request actually proves an 8s loop.
|
|
268
297
|
|
|
269
298
|
Use keyframes timeline for diamonds, editable rows, easing, or keyframe evaluation. In keyframes mode, Toolcraft infers capable controls; do not manually hide diamonds on controls that can be keyframed.
|
|
270
299
|
|
|
@@ -284,17 +313,19 @@ Do not replace `TimelinePanel` with an app-level playback, transport, or timelin
|
|
|
284
313
|
|
|
285
314
|
Use `panelActions` only for sticky footer product actions such as Generate, Apply, Export, Copy, or Download.
|
|
286
315
|
|
|
287
|
-
|
|
316
|
+
Generated apps keep a controls panel so runtime `Setup` is visible from the first run. Settings import/export is mandatory runtime `Setup` behavior there; do not add Import Settings or Export Settings to sticky footer `panelActions`; the runtime inserts them in the first visible headerless `Setup` controls block and imports/exports control values, canvas size, and timeline state.
|
|
288
317
|
|
|
289
|
-
|
|
318
|
+
Do not gate settings import/export by complexity thresholds, app size, or prompt wording. Use schema `settingsTransfer` only to customize exported JSON identity or file name.
|
|
290
319
|
|
|
291
|
-
When
|
|
320
|
+
When editable-output canvas sizing is enabled, the first `Setup` runtime section contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`, `Canvas height`, optional `Resolution scale`, and optional `Timeline` in that order. Do not split these into separate app-authored sections, rename the controls, rebuild the block by hand, or declare runtime Setup targets in product sections. App-authored controls targeting `runtime.settingsTransfer`, `canvas.aspectRatio`, `canvas.size.width`, `canvas.size.height`, `canvas.renderScale`, or `panels.timeline.extended` are invalid and never suppress the mandatory runtime controls.
|
|
292
321
|
|
|
293
|
-
If only `Export Settings` and `Import Settings` appear in that section, the schema is not using `editable-output` canvas sizing
|
|
322
|
+
If only `Export Settings` and `Import Settings` appear in that section, the schema is not using `editable-output` canvas sizing. For product-output apps, fix the canvas sizing decision instead of adding hand-built size fields. A reference, previous app, fixed-format baseline, or user-provided default size does not justify hiding size controls; keep those dimensions as editable `canvas.size` defaults.
|
|
294
323
|
|
|
295
324
|
Manual `Canvas width` or `Canvas height` edits are exact output-size edits. They keep the other dimension unchanged, switch `Aspect ratio` to `Custom`, and update the custom ratio inputs to the reduced current ratio. Do not recreate the old behavior where typing one size field stays locked to the previous aspect preset.
|
|
296
325
|
|
|
297
|
-
Enable `canvas.renderScale: true` for non-vector raster previews such as Canvas 2D, WebGL, or WebGPU output. Runtime adds a `Resolution scale` slider after canvas sizing; it defaults to `
|
|
326
|
+
Enable `canvas.renderScale: true` for non-vector raster previews such as Canvas 2D, WebGL, or WebGPU output. Runtime adds a `Resolution scale` slider after canvas sizing; it defaults to `2` and lets users trade preview quality/performance without changing output size. Adding or enabling this slider requires targeted browser evidence that the canvas stays responsive while dragging sliders or other high-frequency controls at the selected scale. A full browser performance checkpoint is required for the first working product version and explicit performance complaints; use the agent-controlled browser first and `pnpm verify:perf` only as fallback. Performance fixes must preserve the selected scale and keep canvas preview responsive. Diagnose the actual bottleneck before lowering quality; do not silently downsample, stretch a lower-resolution backing canvas, blur output, or clamp `canvas.renderScale` below the user's chosen value. Do not enable it for DOM/SVG/vector-native previews.
|
|
327
|
+
|
|
328
|
+
When `panels.timeline` is enabled, runtime adds a `Timeline` switch as the last Setup control. It is a runtime presentation preference only: off shows compact Play-only transport, on shows the extended TimelinePanel with scrubber, duration, loop, and keyframe UI. It does not change playback, keyframes, export, product values, settings transfer, or Reset controls. `persistence.include: ["panels"]` may restore it. If `panels.timeline` is omitted, the Timeline switch must not be shown.
|
|
298
329
|
|
|
299
330
|
Reset belongs to the controls panel header reset button. Do not add a footer action with `label`, `value`, or `command` containing reset; acceptance treats that as a duplicate Reset.
|
|
300
331
|
|
|
@@ -302,6 +333,8 @@ Still-output product apps include one primary `Export PNG` action.
|
|
|
302
333
|
|
|
303
334
|
Animated product apps include `Export Video` as the primary action and `Export PNG` as the secondary action.
|
|
304
335
|
|
|
336
|
+
Export-labeled footer actions use `icon: "upload-simple"`, matching the runtime `Export Settings` button. Do not use `download`, `download-simple`, or `export` icons for `Export PNG` or `Export Video`.
|
|
337
|
+
|
|
305
338
|
Every product app with `Export PNG` includes a separate `Image Export` section. That section must contain:
|
|
306
339
|
|
|
307
340
|
- `export.image.format` as a `select`, with default value `png` and baseline options `png` and `jpg`;
|
|
@@ -309,14 +342,14 @@ Every product app with `Export PNG` includes a separate `Image Export` section.
|
|
|
309
342
|
|
|
310
343
|
Place `Image Export` directly above sticky footer export buttons for still-output apps. For animated apps with both PNG and video export, place `Image Export` immediately before `Video Export`. `Format` and `Resolution` are one compact workflow pair: render them in a two-column inline row by default. Do not use `segmented` for this pair; it must visually match the Video Export dropdown structure.
|
|
311
344
|
|
|
312
|
-
Animated product apps with `Export Video` include a separate `Video Export` section. That section must contain:
|
|
345
|
+
Animated product apps with `Export Video` must enable the top Toolcraft timeline and include a separate `Video Export` section. That section must contain:
|
|
313
346
|
|
|
314
347
|
- `export.video.format` as a `select`, with default value `mp4` and baseline options `mp4` and `webm`;
|
|
315
348
|
- `export.video.resolution` as a `select`, with default value `current` and options such as `current` and `4k`.
|
|
316
349
|
|
|
317
350
|
Place `Video Export` as the final authored controls section directly above sticky footer export buttons. `Format` and `Resolution` are one compact workflow pair: render them in a two-column inline row by default. Use stacked rows only when a label or selected value would clip, truncate, or lose internal padding, and record that fallback reason in the spec or worklog. Do not use `segmented` for this pair unless the product has a deliberately tiny fixed output menu and browser tests prove every cell keeps padding.
|
|
318
351
|
|
|
319
|
-
Do not put video export format/resolution controls inside effect, renderer, animation, or output-background sections. `MOV` and `ProRes` are not baseline browser outputs; use them only with an explicit encoder/transcoder and dedicated acceptance plus performance coverage.
|
|
352
|
+
Do not put video export format/resolution controls inside effect, renderer, animation, or output-background sections. `MOV` and `ProRes` are not baseline browser outputs; use them only with an explicit encoder/transcoder and dedicated acceptance plus performance coverage. Video exporters use `getToolcraftVideoExportSize`; do not hand-roll `4096` long-edge sizing for video. The `current` video option uses the current canvas/output size with even encoder-safe rounding. The `4k` video option fits inside 3840x2160, preserves aspect ratio, and returns even encoder-safe dimensions. Recorder/encoder errors must reject the export Promise instead of producing a corrupt blob.
|
|
320
353
|
|
|
321
354
|
Add `Copy PNG` only when clipboard output is part of the product. Copy never replaces export. If two footer actions are needed, secondary/outline goes left and primary goes right. Footer actions must be one compact horizontal group, not stacked full-width rows. If an odd number of actions leaves one action alone in the final row, that final action spans the full row.
|
|
322
355
|
|
|
@@ -23,6 +23,7 @@ Use this before writing a schema, spec, or implementation plan. It separates har
|
|
|
23
23
|
| Timeline | Heuristic, then invariant | Choose from Animation Intent Inventory and transport behavior |
|
|
24
24
|
| Controls | Mixed | Bind every visible control and prove product output behavior |
|
|
25
25
|
| Renderer | Default with escape hatches | Choose technique from fidelity, reference behavior, and workload |
|
|
26
|
+
| Reference analysis | Invariant | Study video references as frame-to-frame behavior before implementation |
|
|
26
27
|
| Reference clone | Invariant | Preserve reference behavior unless redesign is explicit |
|
|
27
28
|
| Acceptance | Invariant | Prove product observables, not only runtime mutation |
|
|
28
29
|
| Performance | Mixed | Workload controls need workload budgets; ordinary controls need responsiveness coverage; animated previews yield to viewport interactions |
|
|
@@ -50,6 +51,7 @@ This catalog mirrors `TOOLCRAFT_DECISION_CONTRACT`. If runtime adds or renames a
|
|
|
50
51
|
| `output-export-required` | Invariant | Controls |
|
|
51
52
|
| `controls-layout-heuristics` | Heuristic | Controls |
|
|
52
53
|
| `renderer-technique-inventory` | Default | Renderer |
|
|
54
|
+
| `video-reference-analysis` | Invariant | Reference analysis |
|
|
53
55
|
| `reference-clone-source-of-truth` | Invariant | Reference clone |
|
|
54
56
|
| `acceptance-product-observable` | Invariant | Acceptance |
|
|
55
57
|
| `performance-coverage-levels` | Invariant | Performance |
|
|
@@ -7,6 +7,20 @@ Performance coverage has two levels:
|
|
|
7
7
|
- workload coverage for controls that change render cost;
|
|
8
8
|
- responsiveness coverage for ordinary controls that still must not freeze input or break the viewport.
|
|
9
9
|
|
|
10
|
+
The preferred browser performance runner is the current AI agent's controlled browser. Use `pnpm verify:perf` only as the Playwright fallback when no agent-controlled browser is available or in CI/non-agent automation.
|
|
11
|
+
|
|
12
|
+
`app-performance.ts` must keep this policy:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
browserCheckPolicy: {
|
|
16
|
+
preferredRunner: "agent-browser",
|
|
17
|
+
fallbackRunner: "playwright",
|
|
18
|
+
fallbackWhen: ["agent-browser-unavailable", "ci"],
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do not change the preferred runner to Playwright to make local automation easier; that reverses the contract. Playwright is portable fallback evidence, not the primary agent workflow.
|
|
23
|
+
|
|
10
24
|
## Workload Coverage
|
|
11
25
|
|
|
12
26
|
Use workload coverage for controls that change rendering cost:
|
|
@@ -33,13 +47,17 @@ Both fixture types must include:
|
|
|
33
47
|
|
|
34
48
|
- `kind`: `large-text`, `large-canvas`, `high-density`, `many-items`, `max-value`, `media`, or `custom`;
|
|
35
49
|
- `reason`: why this value is the heaviest useful product case;
|
|
36
|
-
- `value`: the actual value the browser performance
|
|
50
|
+
- `value`: the actual value the browser performance check will apply.
|
|
51
|
+
|
|
52
|
+
Fixtures that represent numeric maximums, density, item counts, canvas/media size, or custom combined heavy states also declare `loadProfile`. This separates the product `hardLimit` from the `smoothTarget` that the browser performance checkpoint guarantees. `hardLimit` is the largest product range or tier the app exposes; `smoothTarget` is the value the browser performance check applies and must match `stressFixture.value` or `workloadFixture.value`.
|
|
53
|
+
|
|
54
|
+
Default to `smoothTargetRatio: 1`, which means the full hard limit must pass budgets. If the hard limit cannot pass after reasonable renderer and pipeline optimizations, lower the smooth target only in 10 percent steps (`0.9`, then `0.8`, and so on). Every lower step must include `loadProfile.evidence` with the failed higher target, measured result, optimization attempted, and decision. When `smoothTargetRatio < 1`, set `userFacingRange: "experimental-above-smooth"` so the range above the smooth target is not silently presented as guaranteed.
|
|
37
55
|
|
|
38
56
|
For multiline text, prompt, code, JSON, CSS, shader, script, or template workload controls, use `kind: "large-text"`. The fixture must contain at least `50_000` characters and `1_000` lines unless the product has a stricter real-world maximum.
|
|
39
57
|
|
|
40
58
|
For uploaded images, videos, source media, and image-processing workloads, use `kind: "media"` with a `value` object containing numeric `width` and `height`. The fixture must be at least `1920x1080`-equivalent, and products that claim high-resolution media or export quality should test a 4K-class source when that is the realistic heavy case. Do not satisfy media import, preview, effect-slider, or image-processing performance with `640x480`, thumbnails, or toy fixtures.
|
|
41
59
|
|
|
42
|
-
Choose heavy fixtures from this app's real controls, not from generic examples.
|
|
60
|
+
Choose heavy fixtures from this app's real controls, not from generic examples. Derive the workload from four app-specific sources: schema controls and `performanceRole`, `rendererTechnique`, `rendererPipeline`, and real product stress values such as large text, source media dimensions, `canvas.renderScale`, canvas size, export resolution, animation speed, viewport interactions, and effect strength. Try the product hard limit first, then use a documented smooth target only when measured evidence shows the hard limit cannot be made smooth without changing product scope.
|
|
43
61
|
|
|
44
62
|
Browser performance tests for workload and stress scenarios must read the scenario value through:
|
|
45
63
|
|
|
@@ -47,13 +65,15 @@ Browser performance tests for workload and stress scenarios must read the scenar
|
|
|
47
65
|
getToolcraftPerformanceStressValue(appPerformance, "scenario-id")
|
|
48
66
|
```
|
|
49
67
|
|
|
50
|
-
For workload sliders,
|
|
68
|
+
For workload sliders, the fallback Playwright test uses `dragToolcraftSliderToPerformanceStressValue(page, label, appPerformance, "scenario-id")` so the test applies the exact numeric value through the real slider min/max range. Agent-browser checks must apply the same value from `app-performance.ts`. Do not divide a stress value by the slider max, type a separate short value, or hardcode a runner-local ratio. If a browser check uses a toy value while `app-performance.ts` claims a heavy fixture, the performance checkpoint must fail.
|
|
69
|
+
|
|
70
|
+
Slider and range slider performance scenarios must preserve live product feedback. The browser test should drag the real thumb and verify the canvas/product output changes during drag. If that drag misses budget, keep the live control semantics and optimize the renderer path first: cache expensive inputs, update uniforms or stable buffers, coalesce preview work to `requestAnimationFrame`, cancel stale async renders, move work off React renders, reuse GPU resources, or change renderer strategy. Do not pass performance by making the slider update only after pointer release or Apply.
|
|
51
71
|
|
|
52
72
|
When a scenario declares `workloadFixture`, apply it first with `getToolcraftPerformanceWorkloadValue` or `applyToolcraftPerformanceWorkloadFixture`, then apply `stressFixture`, then measure. A control-drag scenario that only sets its own slider value while leaving the source media, text, item count, render scale, or dense scene at defaults is invalid.
|
|
53
73
|
|
|
54
74
|
For combined worst cases, put every relevant independent baseline value in `workloadFixture.value` for control scenarios, such as `{ sourceMedia: { width: 3840, height: 2160 }, renderScale: 2 }`, and put the tested control value in `stressFixture.value`. For preview, zoom, drag, animation, or export scenarios that stress the entire state instead of one control, use `stressFixture.value`, such as `{ detail: 96, scale: 0.6, renderScale: 2 }`. Testing workload controls one-by-one is not enough when the product exposes combinations that multiply render cost.
|
|
55
75
|
|
|
56
|
-
Use `kind: "custom"` only for combined object fixtures. Single numeric/string heavy values should use a semantic kind such as `max-value`, `high-density`, `large-canvas`, `many-items`, or `large-text`. Browser tests for custom object fixtures must call:
|
|
76
|
+
Use `kind: "custom"` only for combined object fixtures. Single numeric/string heavy values should use a semantic kind such as `max-value`, `high-density`, `large-canvas`, `many-items`, or `large-text`. Custom object fixtures are still semantically validated: keys such as `content`, `text`, `prompt`, `code`, `script`, `shader`, `template`, `json`, and `css` must hold real `large-text` values; keys for media/image/video must hold realistic `width`/`height`; and `renderScale` / `canvas.renderScale` / `resolutionScale` must be numeric values greater than `1`. Do not hide a short text string, thumbnail media, or a non-applied render scale inside `kind: "custom"`. Browser tests for custom object fixtures must call:
|
|
57
77
|
|
|
58
78
|
```ts
|
|
59
79
|
await applyToolcraftPerformanceStressFixture(page, appPerformance, "scenario-id", {
|
|
@@ -82,7 +102,7 @@ Ordinary controls still need lightweight responsiveness checks. They should not
|
|
|
82
102
|
- panel scroll affecting canvas zoom;
|
|
83
103
|
- timeline or layer interactions destabilizing the viewport.
|
|
84
104
|
|
|
85
|
-
When `canvas.renderScale` / `Resolution scale` is enabled, responsiveness coverage must include slider or other high-frequency control drags at the selected scale. If the canvas lags, diagnose the source before changing quality: renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, stale async renders, or animation scheduling.
|
|
105
|
+
When `canvas.renderScale` / `Resolution scale` is enabled, responsiveness coverage must include slider or other high-frequency control drags at the selected scale. Browser performance tests for scenarios that include `renderScale`, `canvas.renderScale`, or the Resolution scale control must assert backing canvas pixels with `expectToolcraftCanvasBackingPixelsForRenderScale`; state-only checks, labels, or visual CSS stretching do not prove the selected scale was applied. If the canvas lags, diagnose the source before changing quality: renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, stale async renders, or animation scheduling.
|
|
86
106
|
|
|
87
107
|
## Renderer Performance
|
|
88
108
|
|
|
@@ -91,11 +111,13 @@ Custom renderers should:
|
|
|
91
111
|
- initialize contexts, programs, shaders, pipelines, textures, and large buffers once;
|
|
92
112
|
- update uniforms or stable buffers when controls change;
|
|
93
113
|
- cache decoded media;
|
|
94
|
-
-
|
|
114
|
+
- coalesce high-frequency preview work and split lightweight live feedback from heavier refinement when needed;
|
|
95
115
|
- cancel stale async renders;
|
|
96
116
|
- avoid re-decoding media on every control change;
|
|
97
117
|
- cancel scheduled frames during cleanup.
|
|
98
118
|
|
|
119
|
+
Coalescing may reduce redundant renders during high-frequency slider drags, but it must not make the slider feel deferred or leave the canvas unchanged until release.
|
|
120
|
+
|
|
99
121
|
Custom renderers must declare `rendererPipeline` in `src/app/app-performance.ts`. This is the machine-checkable Render Pipeline Inventory:
|
|
100
122
|
|
|
101
123
|
- every render pass has an `id`, `kind`, `runsOn`, `output`, `quality`, `inputs`, and `invalidatedBy`;
|
|
@@ -110,11 +132,11 @@ Pixel-output renderers may use a capped preview pixel budget, but export/copy mu
|
|
|
110
132
|
|
|
111
133
|
Text-output and vector-output previews must preserve native output fidelity. Do not render low-resolution text/vector output into an offscreen canvas and upscale it.
|
|
112
134
|
|
|
113
|
-
For heavy bitmap-media, shader-like, noise/texture, filters, halftone, mesh, and per-pixel image-processing paths, WebGL/WebGPU is a required candidate before choosing CPU Canvas 2D. Canvas 2D is allowed only when `rendererTechnique.
|
|
135
|
+
For heavy bitmap-media, shader-like, noise/texture, filters, halftone, mesh, and per-pixel image-processing paths, WebGL/WebGPU is a required candidate before choosing CPU Canvas 2D. Canvas 2D is allowed only when `rendererTechnique.measuredAlternativeEvidence` records a structured WebGL/WebGPU comparison with `alternativeStrategy`, `scenarioId`, `fixture`, `measuredResult`, and `decision`. Prose in `whyNotAlternativeStrategies` or `performanceRisks` is not enough. A worker can protect the UI thread, but it is not a substitute for GPU acceleration when the workload is fundamentally per-pixel or when the pipeline has main-thread rasterize/composite preview pressure.
|
|
114
136
|
|
|
115
137
|
Renderer strategy is not final until the heavy scenarios pass. When stress preview, animation, drag, zoom, or export tests exceed budget, first decide whether the chosen renderer is wrong for this workload. Move heavy work to WebGL/WebGPU, split semantic foreground from heavy backgrounds, cache atlases/buffers, coalesce high-frequency preview updates, or change the rendering layer model before reducing product quality or relaxing budgets.
|
|
116
138
|
|
|
117
|
-
Do not pass performance by lowering output quality, render scale, canvas backing pixels, export resolution, media fidelity, maximum detail, item count, or animation fidelity. If all reasonable optimizations are exhausted and the app still cannot meet the
|
|
139
|
+
Do not pass performance by lowering output quality, render scale, canvas backing pixels, export resolution, media fidelity, maximum detail, item count, or animation fidelity. If all reasonable optimizations are exhausted and the app still cannot meet the budget at the hard limit, document the measured ceiling with `loadProfile`: exact hard limit, smooth target, ratio, failed higher measurements, attempted optimizations, why further changes would harm product quality or require a different product scope, and whether the range above the smooth target remains experimental. Lowering the smooth target does not relax frame, interaction, preview, render, export, or long-task budgets.
|
|
118
140
|
|
|
119
141
|
## Required Browser Checks
|
|
120
142
|
|
|
@@ -125,12 +147,14 @@ Use real interactions for:
|
|
|
125
147
|
- `control-drag`;
|
|
126
148
|
- `mask-drag` when canvas handles, masks, pins, or on-canvas anchors affect output;
|
|
127
149
|
- `media-import` when upload exists;
|
|
128
|
-
- `export-copy` for product export actions and clipboard actions; measure
|
|
150
|
+
- `export-copy` for product export actions and clipboard actions; measure the selected output dimensions, not CSS preview size;
|
|
129
151
|
- `timeline-playback` or `timeline-scrub` when timeline exists;
|
|
130
152
|
- `layers-interactions` when layers exist;
|
|
131
153
|
- `viewport-zoom-stress` for detail-heavy or animated custom renderers;
|
|
132
154
|
- `viewport-stability`.
|
|
133
155
|
|
|
156
|
+
For `slider` and `rangeSlider`, the required performance scenario is `control-drag`. A `control-change` scenario can cover selects, inputs, toggles, and other non-drag controls, but it does not prove live canvas feedback or drag smoothness for sliders.
|
|
157
|
+
|
|
134
158
|
Animated custom renderers also need `animation-viewport-drag`. Animation-only frame sampling and viewport-only stability are not enough: the browser test must sample frames while physically dragging or panning the canvas viewport. If SVG/DOM cannot pass that combined budget, choose a different renderer strategy from evidence instead of loosening the budget.
|
|
135
159
|
|
|
136
160
|
Detail-heavy or animated custom renderers also need `viewport-zoom-stress`. This test must apply the combined worst-case stress fixture first, then use the real toolbar zoom controls while sampling frame gaps and long tasks. Do not satisfy it by calling `canvas.zoom`, mutating runtime state directly, checking only the final zoom value, or zooming a default/lightweight output.
|
|
@@ -143,13 +167,15 @@ Animation checks should sample enough frames to catch jank. Interaction budgets
|
|
|
143
167
|
|
|
144
168
|
Use `app-performance.ts` as the single budget and fixture source. Browser performance tests must call `getToolcraftPerformanceStressValue(appPerformance, scenarioId)` for workload values and `expectToolcraftScenarioPerformanceBudget(..., appPerformance, scenarioId)` for budgets.
|
|
145
169
|
|
|
146
|
-
Run targeted performance scenarios for Tier 3 performance-sensitive edits when they directly exercise the touched workload, viewport, or export path. `pnpm verify:perf` is the
|
|
170
|
+
Run targeted performance scenarios for Tier 3 performance-sensitive edits when they directly exercise the touched workload, viewport, or export path. The full performance checkpoint prefers the current AI agent's controlled browser. `pnpm verify:perf` is the Playwright fallback suite; it runs `e2e/app-performance.spec.ts` and every `browser perf:` scenario with one worker so budget failures are not hidden or created by unrelated parallel browser tests.
|
|
147
171
|
|
|
148
|
-
Run a full performance checkpoint
|
|
172
|
+
Run a full performance checkpoint only when:
|
|
149
173
|
|
|
150
174
|
- the first working version of the app exists;
|
|
151
175
|
- the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance.
|
|
152
176
|
|
|
177
|
+
For the first working product app, this checkpoint is mandatory: the app is not complete until `pnpm verify:final` and the browser performance checkpoint have both passed and the worklog records the runner as `agent-browser` or `playwright-fallback`. Use `pnpm verify:perf` only as the fallback when no agent-controlled browser is available or in CI/non-agent automation.
|
|
178
|
+
|
|
153
179
|
Performance fixes must preserve selected output and preview quality. Do not pass budgets by lowering image quality, selected `canvas.renderScale`, export resolution, source media fidelity, or canvas backing pixels unless the user explicitly chooses that lower-quality value through a visible control. Prefer coalescing slider updates, caching expensive inputs, moving work off the React render path, reusing GPU resources, or changing renderer strategy over reducing visual fidelity.
|
|
154
180
|
|
|
155
181
|
Do not use the full performance suite as the default loop for feature work after the first working version. Those edits still need the targeted checks named by the verification tier, but they should not pay for every renderer and viewport stress test unless one of the two full-performance triggers applies. If a feature loop skips full performance, record the reason in the worklog.
|