@pixel-point/toolcraft 0.0.9 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/package.json +2 -1
- package/src/cli.mjs +4 -2
- package/src/cli.test.mjs +5 -2
- package/src/generate.mjs +14 -6
- package/src/generate.test.mjs +33 -2
- package/templates/runtime/contracts/component-contracts.test.ts +175 -36
- package/templates/runtime/contracts/component-contracts.ts +90 -45
- package/templates/runtime/contracts/decision-contracts.test.ts +33 -1
- package/templates/runtime/contracts/decision-contracts.ts +19 -6
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +478 -27
- package/templates/runtime/react/controls-panel.tsx +71 -26
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +225 -173
- package/templates/runtime/schema/define-toolcraft.ts +117 -247
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +41 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +657 -2
- package/templates/runtime/testing/performance.ts +789 -49
- package/templates/starter/AGENTS.md +22 -16
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +41 -8
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +51 -20
- package/templates/starter/docs/toolcraft/component-rules.md +58 -37
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +30 -10
- package/templates/starter/docs/toolcraft/schema-reference.md +134 -33
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +57 -4
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -2
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +82 -16
- package/templates/starter/scripts/toolcraft-port.mjs +178 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +147 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +2573 -313
- package/templates/starter/src/app/starter-acceptance.ts +978 -81
- package/templates/starter/src/app/starter-performance.test.ts +130 -8
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/controls/actions/actions-control.tsx +46 -3
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +2 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +19 -5
- package/templates/ui/components/controls/select/select-control.tsx +4 -26
- package/templates/ui/components/controls/vector/vector-control.tsx +25 -4
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
|
@@ -29,7 +29,7 @@ Common exact-owner choices:
|
|
|
29
29
|
- Do not stretch an odd trailing action full-width or center it; keep it in the left 50% cell.
|
|
30
30
|
- Use `panelActions` for sticky final product actions such as export, copy, generate, apply, or download.
|
|
31
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.
|
|
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
33
|
|
|
34
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.
|
|
35
35
|
|
|
@@ -68,33 +68,19 @@ Range slider value editing accepts common range separators such as `20/80`, `20-
|
|
|
68
68
|
|
|
69
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.
|
|
70
70
|
|
|
71
|
-
|
|
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.
|
|
72
72
|
|
|
73
|
-
|
|
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.
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
fillAmount: {
|
|
77
|
-
type: "slider",
|
|
78
|
-
label: "Fill level",
|
|
79
|
-
target: "distribution.fillAmount",
|
|
80
|
-
disabledWhen: {
|
|
81
|
-
target: "distribution.fillMode",
|
|
82
|
-
equals: "full",
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
```
|
|
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.
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Use `visibleWhen` instead of `disabledWhen` when a control or section belongs only to another template, type, mode, variant, or count. Example: in a co-brand lockup, `Partner` belongs to text identity mode and `Partner logo` belongs to logo identity mode. For count-controlled banks, hide inactive siblings: if `Shades` is `2`, `Shade 3`, `Shade 4`, and `Shade 5` are not visible. Do not keep inactive controls visible and enabled while making the renderer ignore them.
|
|
90
|
-
|
|
91
|
-
If `visibleWhen` or `disabledWhen` 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.
|
|
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.
|
|
92
78
|
|
|
93
79
|
## Palette
|
|
94
80
|
|
|
95
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.
|
|
96
82
|
|
|
97
|
-
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.
|
|
98
84
|
|
|
99
85
|
## Segmented Controls
|
|
100
86
|
|
|
@@ -110,6 +96,12 @@ Limits:
|
|
|
110
96
|
|
|
111
97
|
If cells clip, collide, lose padding, or force labels into adjacent cells, shorten labels first. If compact labels still fail, use `select`.
|
|
112
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
|
+
|
|
113
105
|
## Sliders
|
|
114
106
|
|
|
115
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.
|
|
@@ -122,17 +114,17 @@ Build controls-panel sections from product entities and workflow stages, not com
|
|
|
122
114
|
|
|
123
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.
|
|
124
116
|
|
|
125
|
-
Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created
|
|
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.
|
|
126
118
|
|
|
127
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.
|
|
128
120
|
|
|
129
121
|
Section expand/collapse uses the standard runtime height/opacity animation. Do not replace it with instant custom section visibility.
|
|
130
122
|
|
|
131
|
-
|
|
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.
|
|
132
124
|
|
|
133
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`.
|
|
134
126
|
|
|
135
|
-
|
|
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.
|
|
136
128
|
|
|
137
129
|
## Colors
|
|
138
130
|
|
|
@@ -160,7 +152,11 @@ Use `fileDrop` for source material uploads in the controls panel. Do not place u
|
|
|
160
152
|
|
|
161
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.
|
|
162
154
|
|
|
163
|
-
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.
|
|
164
160
|
|
|
165
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.
|
|
166
162
|
|
|
@@ -168,6 +164,8 @@ In file mode, uploaded files render as a sortable list with a paperclip icon, fi
|
|
|
168
164
|
|
|
169
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.
|
|
170
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
|
+
|
|
171
169
|
In multi-layer apps, deletion and visibility belong to the Layers panel; `fileDrop` stays an upload target.
|
|
172
170
|
|
|
173
171
|
## Image Picker
|
|
@@ -210,9 +208,22 @@ Use variants by product meaning:
|
|
|
210
208
|
- `chromaOffset`: RGB or chromatic offset;
|
|
211
209
|
- `toneBias`: split-tone, duotone, or color-grading bias.
|
|
212
210
|
|
|
211
|
+
Use Vector only when the user is meant to manually author a stable two-axis product parameter. Do not expose a pad for current animation state, keyboard movement, pointer movement, physics state, timeline phase, velocity, target pose, current pose, or simulated position/direction just because the internal value has `x` and `y`.
|
|
212
|
+
|
|
213
|
+
Before adding a Vector control to an animated or interactive product, classify movement ownership:
|
|
214
|
+
|
|
215
|
+
- `direct-authored`: a stable parameter the user manually edits, such as light direction, focus, anchor, or object offset. This can be Vector.
|
|
216
|
+
- `timeline-driven`: movement comes from playback/keyframes. Use timeline, speed, duration, path, step, or amplitude controls instead.
|
|
217
|
+
- `keyboard/pointer-driven`: movement comes from user input on the canvas/app. Keep position/direction in interaction state and expose only useful tuning controls.
|
|
218
|
+
- `simulation-owned`: movement comes from physics/procedural state. Keep current pose/velocity internal and expose high-level tuning controls.
|
|
219
|
+
|
|
213
220
|
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.
|
|
214
221
|
|
|
215
|
-
|
|
222
|
+
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`.
|
|
223
|
+
|
|
224
|
+
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.
|
|
225
|
+
|
|
226
|
+
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 direct-authored parameters instead of creating a custom pad.
|
|
216
227
|
|
|
217
228
|
Do not add custom vector sizing props. Choose the right number, variant, and section grouping, then let runtime sizing handle the pad.
|
|
218
229
|
|
|
@@ -275,17 +286,23 @@ When Layers are enabled, browser tests must use the real LayersPanel UI: select,
|
|
|
275
286
|
|
|
276
287
|
Before choosing timeline mode for an animated product, write an Animation Intent Inventory:
|
|
277
288
|
|
|
278
|
-
- `timeline-playback`: user-facing play, pause, scrub, duration, loop, restart, progress,
|
|
289
|
+
- `timeline-playback`: user-facing play, pause, scrub, duration, loop, restart, progress, export-at-time, or video export.
|
|
279
290
|
- `timeline-keyframes`: editable diamonds, rows, easing, or keyframe evaluation.
|
|
280
|
-
- `autonomous`: decorative or self-running output with no user-facing transport.
|
|
291
|
+
- `autonomous`: decorative or self-running output with no user-facing transport and no video export.
|
|
292
|
+
|
|
293
|
+
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.
|
|
281
294
|
|
|
282
|
-
|
|
295
|
+
Use playback timeline for play, pause, scrub, duration, loop, restart, export-at-time, or video export.
|
|
283
296
|
|
|
284
|
-
|
|
297
|
+
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.
|
|
285
298
|
|
|
286
299
|
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.
|
|
287
300
|
|
|
288
|
-
|
|
301
|
+
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.
|
|
302
|
+
|
|
303
|
+
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.
|
|
304
|
+
|
|
305
|
+
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.
|
|
289
306
|
|
|
290
307
|
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.
|
|
291
308
|
|
|
@@ -305,17 +322,19 @@ Do not replace `TimelinePanel` with an app-level playback, transport, or timelin
|
|
|
305
322
|
|
|
306
323
|
Use `panelActions` only for sticky footer product actions such as Generate, Apply, Export, Copy, or Download.
|
|
307
324
|
|
|
308
|
-
|
|
325
|
+
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.
|
|
309
326
|
|
|
310
|
-
|
|
327
|
+
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.
|
|
311
328
|
|
|
312
|
-
When
|
|
329
|
+
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.
|
|
313
330
|
|
|
314
|
-
If only `Export Settings` and `Import Settings` appear in that section, the schema is not using `editable-output` canvas sizing
|
|
331
|
+
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.
|
|
315
332
|
|
|
316
333
|
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.
|
|
317
334
|
|
|
318
|
-
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.
|
|
335
|
+
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.
|
|
336
|
+
|
|
337
|
+
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.
|
|
319
338
|
|
|
320
339
|
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.
|
|
321
340
|
|
|
@@ -323,6 +342,8 @@ Still-output product apps include one primary `Export PNG` action.
|
|
|
323
342
|
|
|
324
343
|
Animated product apps include `Export Video` as the primary action and `Export PNG` as the secondary action.
|
|
325
344
|
|
|
345
|
+
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`.
|
|
346
|
+
|
|
326
347
|
Every product app with `Export PNG` includes a separate `Image Export` section. That section must contain:
|
|
327
348
|
|
|
328
349
|
- `export.image.format` as a `select`, with default value `png` and baseline options `png` and `jpg`;
|
|
@@ -330,14 +351,14 @@ Every product app with `Export PNG` includes a separate `Image Export` section.
|
|
|
330
351
|
|
|
331
352
|
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.
|
|
332
353
|
|
|
333
|
-
Animated product apps with `Export Video` include a separate `Video Export` section. That section must contain:
|
|
354
|
+
Animated product apps with `Export Video` must enable the top Toolcraft timeline and include a separate `Video Export` section. That section must contain:
|
|
334
355
|
|
|
335
356
|
- `export.video.format` as a `select`, with default value `mp4` and baseline options `mp4` and `webm`;
|
|
336
357
|
- `export.video.resolution` as a `select`, with default value `current` and options such as `current` and `4k`.
|
|
337
358
|
|
|
338
359
|
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.
|
|
339
360
|
|
|
340
|
-
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.
|
|
361
|
+
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.
|
|
341
362
|
|
|
342
363
|
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.
|
|
343
364
|
|
|
@@ -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,7 +65,7 @@ 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.
|
|
51
69
|
|
|
52
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.
|
|
53
71
|
|
|
@@ -55,7 +73,7 @@ When a scenario declares `workloadFixture`, apply it first with `getToolcraftPer
|
|
|
55
73
|
|
|
56
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.
|
|
57
75
|
|
|
58
|
-
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:
|
|
59
77
|
|
|
60
78
|
```ts
|
|
61
79
|
await applyToolcraftPerformanceStressFixture(page, appPerformance, "scenario-id", {
|
|
@@ -84,7 +102,7 @@ Ordinary controls still need lightweight responsiveness checks. They should not
|
|
|
84
102
|
- panel scroll affecting canvas zoom;
|
|
85
103
|
- timeline or layer interactions destabilizing the viewport.
|
|
86
104
|
|
|
87
|
-
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.
|
|
88
106
|
|
|
89
107
|
## Renderer Performance
|
|
90
108
|
|
|
@@ -114,11 +132,11 @@ Pixel-output renderers may use a capped preview pixel budget, but export/copy mu
|
|
|
114
132
|
|
|
115
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.
|
|
116
134
|
|
|
117
|
-
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.
|
|
118
136
|
|
|
119
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.
|
|
120
138
|
|
|
121
|
-
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.
|
|
122
140
|
|
|
123
141
|
## Required Browser Checks
|
|
124
142
|
|
|
@@ -129,7 +147,7 @@ Use real interactions for:
|
|
|
129
147
|
- `control-drag`;
|
|
130
148
|
- `mask-drag` when canvas handles, masks, pins, or on-canvas anchors affect output;
|
|
131
149
|
- `media-import` when upload exists;
|
|
132
|
-
- `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;
|
|
133
151
|
- `timeline-playback` or `timeline-scrub` when timeline exists;
|
|
134
152
|
- `layers-interactions` when layers exist;
|
|
135
153
|
- `viewport-zoom-stress` for detail-heavy or animated custom renderers;
|
|
@@ -149,13 +167,15 @@ Animation checks should sample enough frames to catch jank. Interaction budgets
|
|
|
149
167
|
|
|
150
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.
|
|
151
169
|
|
|
152
|
-
Run targeted performance scenarios for Tier 3 performance-sensitive edits when they directly exercise the touched workload, viewport, or export path. `pnpm verify:
|
|
170
|
+
Run targeted performance scenarios for Tier 3 performance-sensitive edits when they directly exercise the touched workload, viewport, or export path. The default `pnpm test:browser` / `pnpm verify:ui` gate excludes every Playwright test whose name contains `browser perf:`, including the performance audit and budget scenarios. The full performance checkpoint prefers the current AI agent's controlled browser. `pnpm verify:perf` is the Playwright fallback suite; it runs every `browser perf:` scenario with one worker so budget failures are not hidden or created by unrelated parallel browser tests.
|
|
153
171
|
|
|
154
|
-
Run a full performance checkpoint
|
|
172
|
+
Run a full performance checkpoint only when:
|
|
155
173
|
|
|
156
174
|
- the first working version of the app exists;
|
|
157
175
|
- the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance.
|
|
158
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
|
+
|
|
159
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.
|
|
160
180
|
|
|
161
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.
|