@pixel-point/toolcraft 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate.test.mjs +49 -6
- package/templates/starter/AGENTS.md +4 -3
- package/templates/starter/docs/toolcraft/README.md +15 -0
- package/templates/starter/docs/toolcraft/acceptance-testing.md +2 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +35 -171
- package/templates/starter/docs/toolcraft/component-rules.md +12 -188
- package/templates/starter/docs/toolcraft/core/control-selection.md +93 -0
- package/templates/starter/docs/toolcraft/core/layout.md +104 -0
- package/templates/starter/docs/toolcraft/core/media-upload.md +85 -0
- package/templates/starter/docs/toolcraft/core/performance.md +83 -0
- package/templates/starter/docs/toolcraft/core/reference-study.md +115 -0
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +53 -0
- package/templates/starter/docs/toolcraft/core/setup-export.md +86 -0
- package/templates/starter/docs/toolcraft/core/timeline-animation.md +67 -0
- package/templates/starter/docs/toolcraft/custom-controls.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +2 -0
- package/templates/starter/docs/toolcraft/renderer-technique.md +2 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +117 -367
- package/templates/starter/docs/toolcraft/workflow.md +12 -10
- package/templates/starter/scripts/check-toolcraft-docs.mjs +28 -6
- package/templates/starter/src/app/starter-acceptance.test.ts +9 -8
|
@@ -1,339 +1,136 @@
|
|
|
1
1
|
# Schema Reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Reading route: start with `workflow.md`. Core generated-app rules live in `core/*`; this file is a field reference for `src/app/app-schema.ts`.
|
|
4
|
+
|
|
5
|
+
Edit `src/app/app-schema.ts` as the public product surface. Use `defineToolcraft` to configure runtime surfaces, product controls, defaults, persistence, and product actions.
|
|
4
6
|
|
|
5
7
|
## Runtime Shape
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
- Configure `canvas`, `panels`, `toolbar`, and `panelActions` through the schema instead of composing those surfaces by hand.
|
|
9
|
-
- New generated apps keep a controls panel so runtime `Setup` always provides `Export Settings` / `Import Settings` from the first run.
|
|
10
|
-
- Bind every control to a schema `target`.
|
|
11
|
-
- Use `defaultValue` for reset behavior.
|
|
12
|
-
- Use `description` for product-specific help beside a visible label. Keep `label` short. Omit `description` instead of writing label recaps like `Adjusts Opacity`; also omit it for obvious color clusters such as `Color 1` / `Color 2` inside a color section. Compound controls such as `fontPicker` must not use `description` to list their own fields.
|
|
13
|
-
- Use `visibleWhen` when a control or section exists only for a specific template, type, source, include state, mode, variant, or count. Hidden values are preserved. A section with no visible controls is hidden automatically.
|
|
14
|
-
- Do not use `disabled: true` or `disabledWhen` for generated product controls. Product panels should show only controls usable in the current state. Runtime primitives may still have disabled styling internally, but app schemas should model product availability with `visibleWhen`.
|
|
15
|
-
- Conditions support `equals`, `notEquals`, `oneOf`, `notOneOf`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, and `lessThanOrEqual`.
|
|
16
|
-
- Use `orderRole` to make control order testable.
|
|
17
|
-
- Use `performanceRole` and `performanceReason` on every visible non-action control so performance coverage can be derived from the schema.
|
|
18
|
-
- Use built-in controls before custom renderers.
|
|
19
|
-
- Use `panelActions` only for sticky footer product actions.
|
|
20
|
-
- Do not use `panelActions` for settings import/export; `settingsTransfer` owns that body section.
|
|
21
|
-
- Do not use `panelActions` for reset. The controls panel header owns reset, and footer actions with `label`, `value`, or `command` containing reset fail acceptance.
|
|
22
|
-
- Use `media.defaultAssets` for predefined file/image attachments; never hard-code those files inside `canvasContent` or the renderer.
|
|
23
|
-
- Still-output product apps expose `Export PNG`.
|
|
24
|
-
- Animated product apps expose `Export Video` and `Export PNG`.
|
|
25
|
-
- Export PNG and Export Video use `icon: "upload-simple"` to match the runtime `Export Settings` action.
|
|
26
|
-
- `Copy PNG` can be secondary, but it never replaces export.
|
|
27
|
-
- If an odd number of footer actions leaves one action alone in the final row, that final action spans the full row.
|
|
28
|
-
- Use `ToolcraftApp onPanelAction` for product-specific actions.
|
|
29
|
-
- Async export/download/copy/generate/apply handlers return the real Promise from `onPanelAction`; the runtime shows the sticky footer top accent indicator only while that Promise is pending and fills it from `reportProgress(0..1)` when determinate progress is available.
|
|
30
|
-
- Do not duplicate runtime-owned canvas, toolbar, panel, layer, or timeline internals.
|
|
31
|
-
|
|
32
|
-
## Export
|
|
33
|
-
|
|
34
|
-
Use the standard export helpers from `@/toolcraft/runtime`.
|
|
9
|
+
Top-level schema fields:
|
|
35
10
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
11
|
+
| Field | Purpose | Detailed rules |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `canvas` | Product workspace, output size, upload/drop support, render scale. | `core/runtime-boundary.md`, `core/setup-export.md`, `core/media-upload.md` |
|
|
14
|
+
| `media` | Predefined attached files/images that appear in `fileDrop`. | `core/media-upload.md` |
|
|
15
|
+
| `panels` | Controls, layers, timeline. | `core/runtime-boundary.md`, `core/timeline-animation.md` |
|
|
16
|
+
| `toolbar` | History, radar, theme, zoom. | `assembly-workflow.md` |
|
|
17
|
+
| `persistence` | Intentional reload persistence for runtime slices. | `performance.md`, `acceptance-testing.md` |
|
|
18
|
+
| `settingsTransfer` | Runtime-owned Export Settings / Import Settings identity. | `core/setup-export.md` |
|
|
19
|
+
| `panelActions` | Sticky product delivery actions such as export, copy, generate, apply. | `core/setup-export.md`, `core/control-selection.md` |
|
|
43
20
|
|
|
44
|
-
`
|
|
21
|
+
Schema controls always bind to a `target`, use `defaultValue` for reset behavior, and include `performanceRole` / `performanceReason` on visible non-action controls. Use built-in control `type` values before `controlRenderers`.
|
|
45
22
|
|
|
46
|
-
|
|
47
|
-
- `export.includeBackground` as a boolean/options control.
|
|
23
|
+
## Canvas
|
|
48
24
|
|
|
49
|
-
|
|
25
|
+
Canvas sizing modes:
|
|
50
26
|
|
|
51
|
-
|
|
27
|
+
- `editable-output`: product/export apps. Runtime `Setup` shows `Aspect ratio`, `Canvas width`, `Canvas height`, optional `Resolution scale`, and optional `Timeline`.
|
|
28
|
+
- `intrinsic-media`: explicit media-viewer/source-native products where imported media intentionally owns `canvas.size`.
|
|
29
|
+
- `fixed-output`: non-product/internal fixtures where users must not edit output size.
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
{
|
|
55
|
-
title: "Image Export",
|
|
56
|
-
controls: {
|
|
57
|
-
imageFormat: {
|
|
58
|
-
defaultValue: "png",
|
|
59
|
-
label: "Format",
|
|
60
|
-
options: [
|
|
61
|
-
{ label: "PNG", value: "png" },
|
|
62
|
-
{ label: "JPG", value: "jpg" },
|
|
63
|
-
],
|
|
64
|
-
target: "export.image.format",
|
|
65
|
-
type: "select",
|
|
66
|
-
},
|
|
67
|
-
imageResolution: {
|
|
68
|
-
defaultValue: "4k",
|
|
69
|
-
label: "Resolution",
|
|
70
|
-
options: [
|
|
71
|
-
{ label: "2K", value: "2k" },
|
|
72
|
-
{ label: "4K", value: "4k" },
|
|
73
|
-
{ label: "8K", value: "8k" },
|
|
74
|
-
],
|
|
75
|
-
target: "export.image.resolution",
|
|
76
|
-
type: "select",
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
layoutGroups: [
|
|
80
|
-
{
|
|
81
|
-
layout: "inline",
|
|
82
|
-
columns: 2,
|
|
83
|
-
controls: ["imageFormat", "imageResolution"],
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
}
|
|
87
|
-
```
|
|
31
|
+
Product-output, exportable, shader, procedural, and reference-clone apps use `editable-output`. Uploaded background/source images inside a product canvas also use `editable-output`: keep the current canvas size and render the image as cover/crop inside current canvas bounds.
|
|
88
32
|
|
|
89
|
-
|
|
33
|
+
Use `canvas.renderScale: true` only for non-vector raster previews such as Canvas 2D, WebGL, or WebGPU. Do not enable it for DOM/SVG/vector-native previews.
|
|
34
|
+
|
|
35
|
+
## Media Defaults
|
|
36
|
+
|
|
37
|
+
Use `media.defaultAssets` for predefined files, source images, masks, symbol sets, or background images:
|
|
90
38
|
|
|
91
39
|
```ts
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
controls: {
|
|
95
|
-
videoFormat: {
|
|
96
|
-
defaultValue: "mp4",
|
|
97
|
-
label: "Format",
|
|
98
|
-
options: [
|
|
99
|
-
{ label: "MP4", value: "mp4" },
|
|
100
|
-
{ label: "WebM", value: "webm" },
|
|
101
|
-
],
|
|
102
|
-
target: "export.video.format",
|
|
103
|
-
type: "select",
|
|
104
|
-
},
|
|
105
|
-
videoResolution: {
|
|
106
|
-
defaultValue: "current",
|
|
107
|
-
label: "Resolution",
|
|
108
|
-
options: [
|
|
109
|
-
{ label: "Current", value: "current" },
|
|
110
|
-
{ label: "4K", value: "4k" },
|
|
111
|
-
],
|
|
112
|
-
target: "export.video.resolution",
|
|
113
|
-
type: "select",
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
layoutGroups: [
|
|
40
|
+
media: {
|
|
41
|
+
defaultAssets: [
|
|
117
42
|
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
43
|
+
id: "default-source",
|
|
44
|
+
name: "source.png",
|
|
45
|
+
sourceTarget: "source.image",
|
|
46
|
+
url: "/assets/source.png",
|
|
121
47
|
},
|
|
122
48
|
],
|
|
123
49
|
}
|
|
124
50
|
```
|
|
125
51
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
## Canvas Sizing
|
|
129
|
-
|
|
130
|
-
Choose sizing from product context:
|
|
131
|
-
|
|
132
|
-
- `intrinsic-media`: an explicit media-viewer/source-native product where a single uploaded or generated source defines `canvas.size`.
|
|
133
|
-
- `editable-output`: product/export output where users always see aspect ratio, width, and height.
|
|
134
|
-
- `fixed-output`: non-product/internal output size that users must not edit.
|
|
135
|
-
|
|
136
|
-
For product output, export, copy, download, shader rendering, procedural rendering, reference clones, or no single intrinsic source image, use `editable-output`. Upload without explicit sizing also resolves to `editable-output`.
|
|
137
|
-
|
|
138
|
-
An uploaded background/source image inside a product canvas is not source-native sizing. Use `editable-output`, keep the current `canvas.size`, keep `Setup` canvas controls visible, and render the image as cover/crop inside the current canvas bounds without letterbox or aspect distortion. Use `intrinsic-media` only when the product is truly a media viewer/source-native tool where imported media natural dimensions intentionally own `canvas.size`, and prove that with `canvasSizingCoverage: "intrinsic-media-size"` acceptance.
|
|
139
|
-
|
|
140
|
-
A prompt-provided, reference, fixed-format, or base/default size is only the initial `canvas.size`. It must not remove the runtime Aspect ratio, Canvas width, and Canvas height controls. Aspect presets use canonical output sizes (`16:9` is `1920x1080`; the other presets are derived around a 1080px short edge or matching portrait long edge). When no explicit product size is provided, runtime defaults to `16:9` / `1920x1080`; choose another preset only when the product meaning calls for it. Generated product/export apps do not use `fixed-output` to preserve a reference baseline; fixed dimensions stay visible as editable defaults.
|
|
141
|
-
|
|
142
|
-
Resolved `canvas.size` exists for every canvas app, but visible `Aspect ratio`, `Canvas width`, and `Canvas height` controls are mandatory through `editable-output` sizing for product/export apps. They live in the first visible headerless `Setup` controls block after `Export Settings` and `Import Settings`. Do not hand-build a duplicate size selector.
|
|
143
|
-
|
|
144
|
-
When the user manually edits `Canvas width` or `Canvas height`, the runtime treats that as an exact custom output size. It keeps the typed dimension, keeps the other dimension unchanged, switches `Aspect ratio` to `Custom`, and shows the reduced current ratio in the custom ratio inputs. Only selecting an aspect preset may resize both dimensions from a canonical preset.
|
|
145
|
-
|
|
146
|
-
For non-vector raster, Canvas 2D, WebGL, or WebGPU previews, set `canvas.renderScale: true`. Runtime appends `Resolution scale` after canvas sizing in `Setup`. The slider ranges from `1` to `2`, defaults to `2`, and changes the renderer backing pixels without changing the visible CSS size or product output dimensions. 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. Diagnose whether lag comes from renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, stale async renders, or animation scheduling before reducing 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; use native vector rendering for those.
|
|
147
|
-
|
|
148
|
-
When `panels.timeline` is enabled, runtime appends a `Timeline` switch as the last control in `Setup`, after `Resolution scale` when present. The switch controls runtime presentation only: off shows compact Play-only transport, on shows the extended timeline with scrubber, duration, loop, and keyframe UI. It does not pause playback, change keyframes, alter export, write product `values`, or reset with `Reset controls`. If `persistence.include` contains `"panels"`, the extended/compact state can restore as a UI preference.
|
|
149
|
-
|
|
150
|
-
## Media Defaults
|
|
151
|
-
|
|
152
|
-
Use `media.defaultAssets` when the app starts with predefined files, source images, masks, symbol sets, or background images. Each item should set `sourceTarget` to the matching `fileDrop` control target. The runtime treats these as attached files: users see them in the uploader, can remove them to get an empty source/canvas state, and Reset restores them.
|
|
153
|
-
|
|
154
|
-
If removal, reorder, or transforms of predefined media should survive reload, add `"media"` to `persistence.include`; do not mirror the file list into product `values` and do not hard-code the file in `canvasContent`.
|
|
52
|
+
`sourceTarget` must match a `fileDrop` control target. Runtime shows the asset as an attached file, users can remove it, and Reset restores it.
|
|
155
53
|
|
|
156
54
|
## Panels
|
|
157
55
|
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
- Do not use `selectedLayer.*` targets when layers are disabled.
|
|
162
|
-
- Timeline is optional only for autonomous decorative animation with no video export. Use playback, keyframes, or custom reference timeline from product transport behavior.
|
|
163
|
-
- Timeline compact/extended presentation is runtime panel UI state controlled by the auto-injected `Setup` switch, not a product control target.
|
|
164
|
-
- Do not add right-panel Play, Pause, Animate, or Restart controls for app-wide transport. Use the top timeline.
|
|
165
|
-
|
|
166
|
-
## Built-In Control Types
|
|
167
|
-
|
|
168
|
-
Use built-ins before custom controls. Unknown `type` values render nothing unless the app passes a matching `controlRenderers` entry.
|
|
169
|
-
|
|
170
|
-
| `type` | Renders | Key fields |
|
|
171
|
-
| --- | --- | --- |
|
|
172
|
-
| `actions` | Local action buttons for the current section or nearby entity, rendered below the label in a two-column grid | `actions`, `target`, `label` |
|
|
173
|
-
| `anchorGrid` | Anchor picker | `defaultValue`, `target` |
|
|
174
|
-
| `channelMixer` | RGB-only channel matrix mixer with R/G/B tabs and Red/Green/Blue source sliders | `defaultValue`, `target`, `label` |
|
|
175
|
-
| `checkbox` | Checkbox field | `defaultValue`, `target`, `label` |
|
|
176
|
-
| `collectionActions` | Add/remove buttons for repeatable product entities and their runtime-backed item controls | `defaultValue: []`, `itemControl`, `itemDefaultValue`, `itemLabel`, `minItems`, `recommendedMaxItems`, `hardMaxItems`, `target` |
|
|
177
|
-
| `code` | Multiline textarea | `defaultValue`, `target`, `label` |
|
|
178
|
-
| `color` | Hex color picker | `defaultValue: { hex }`, `target`, `label` |
|
|
179
|
-
| `colorOpacity` | Hex color picker plus opacity percent input | `defaultValue: { hex, opacity }`, `target`, `label` |
|
|
180
|
-
| `curves` | RGB or single curve editor | `defaultValue`, `target`, `variant: "single"`, `interpolation: "smooth" \| "monotone"` |
|
|
181
|
-
| `fileDrop` | Upload/drop input; `assetKind: "image"` owns image previews, rotate/flip actions, selection for multi-image transforms, and cover/crop canvas source behavior; `assetKind: "file"` owns sortable arbitrary file lists | `assetKind`, `accept`, `multiple`, `defaultValue`, `target` |
|
|
182
|
-
| `fontPicker` | Font preview select with popup, category search, weight, size, text case, text color/opacity, letter spacing, and line height; product text must consume `fontId`, `fontWeight`, `fontSize`, `letterSpacing`, `lineHeight`, `textCase`, `color`, and `opacity`; default text color is `#FFFFFF` at `100` opacity | `defaultValue: { fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, opacity }`, `target` |
|
|
183
|
-
| `gradient` | Gradient editor | `defaultValue: { angle, gradientType, stops }`, `target` |
|
|
184
|
-
| `imagePicker` | Image choice grid | `items`, `defaultValue`, `target` |
|
|
185
|
-
| `palette` | Constrained design-token palette picker for family + shade | `defaultValue: { family, shade }`, `target` |
|
|
186
|
-
| `panelActions` | Sticky footer product actions | `actions`, `target`, `variant` |
|
|
187
|
-
| `rangeInput` | Two compact text values | `defaultValue: { start, end }`, `target` |
|
|
188
|
-
| `rangeSlider` | Two-thumb slider | `defaultValue`, `min`, `max`, `step`, `variant` |
|
|
189
|
-
| `segmented` | Segmented control | `options`, `defaultValue`, `variant` |
|
|
190
|
-
| `select` | Select dropdown | `options`, `defaultValue` |
|
|
191
|
-
| `slider` | Single-value slider | `defaultValue`, `min`, `max`, `step`, `unit`, `variant` |
|
|
192
|
-
| `switch` | Binary switch | `defaultValue`, `target`, `label` |
|
|
193
|
-
| `text` | Single-line input | `defaultValue`, `target`, `label`, `commitMode` |
|
|
194
|
-
| `vector` | X/Y vector pad and fields | `defaultValue: { x, y }`, `xLabel`, `yLabel`, `variant`, `coordinateMode` |
|
|
195
|
-
|
|
196
|
-
For `select`, standalone controls render stacked and full-width with the label above the dropdown. Do not use the old compact side-label row with label left and dropdown right. Use compact two-column inline layout only for related short select pairs that tune one workflow or entity, such as export `Format` and `Resolution`.
|
|
197
|
-
|
|
198
|
-
`text` defaults to `commitMode: "content"` and applies while typing for short real content such as button labels, canvas labels, names, titles, captions, tokens, compact prompts, and other one-line text. 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 always commit on blur or Enter. `code` / `CodeTextarea` is a content editor for long, multiline, or structured values, applies while typing, and is capped at 12 visible lines. Long content scrolls inside the textarea instead of making the controls panel taller. Do not use `code` for short single-line button/canvas text unless `description` explicitly proves the field is intended for long or structured input.
|
|
199
|
-
|
|
200
|
-
For `slider` and `rangeSlider`, `unit` is a real measurement suffix, not the entity being counted and not a generic multiplier. Use units such as `%`, `px`, `°`, `s`, `ms`, `fps`, `rows`, or `cols` only when they clarify the number. Do not use `unit: "x"`; scale, multiplier, intensity, opacity, strength, depth, and shader amount values display plain numbers unless a real measurement unit applies. Do not add repeated nouns such as `letters`, `shapes`, `words`, `symbols`, `items`, `particles`, or `layers` when the label or section already names that entity. If the value needs a noun, rename the label or section. Word or acronym units, when truly needed, render with a space (`5 cols`, `17 fps`); compact symbol/CSS units stay tight (`70%`, `24px`).
|
|
201
|
-
|
|
202
|
-
`slider` and `rangeSlider` are live controls. Dragging must update runtime state and product output while the drag is in progress, not only on pointer release, blur, Apply, or a final commit. Treat a non-live slider as a broken product mapping unless an extreme measured performance ceiling is documented; even then, keep immediate lightweight canvas feedback and refine the heavy output after coalescing/caching.
|
|
203
|
-
|
|
204
|
-
For `vector`, the default/spatial variant uses `coordinateMode: "screen"` by default: dragging left/up lowers `x` and `y`, so canvas objects move left/up without renderer-side Y inversion. Use it only for stable user-authored two-axis parameters. Do not expose Vector for current animation state, keyboard movement, pointer movement, physics state, timeline phase, velocity, target pose, current pose, or simulated position/direction just because the internal value has `x` and `y`. Use `coordinateMode: "cartesian"` only for intentional mathematical Y-up coordinates. Color variants keep their color-axis semantics by default. Vector pad value labels show compact rounded coordinates; raw floating-point tails must never appear in the controls panel. Double-clicking the pad resets both axes to the control default through the normal runtime value update, matching the section header reset; if no default is defined, the fallback is `0,0`. Holding Shift while dragging locks movement to the dominant axis and must not select text or page content.
|
|
205
|
-
|
|
206
|
-
Use `collectionActions` when the product owns a growable/shrinkable item list. `minItems` protects the smallest valid output, `recommendedMaxItems` is only a design recommendation, and `hardMaxItems` is valid only for a real product or technical limit. Adding/removing items must update the runtime array and the renderer/export must consume that same array. Do not pair a count slider with hidden fixed item controls when the user needs to add or remove actual entities. The collection label is on the left and remove/add buttons stay on the right. Homogeneous repeated items do not show visible per-item labels when the collection label already names the group. `itemControl.type` supports normal item built-ins such as `color`, `colorOpacity`, `text`, `select`, `segmented`, `slider`, `switch`, `checkbox`, `rangeInput`, and `fontPicker`; item controls still follow normal density rules, so plain colors use equal 50% columns when they fit. Use `fontPicker` as the item control when each repeated item is a text style or typography entity; do not split its font, weight, size, case, color/opacity, letter spacing, or line height into sibling collection fields.
|
|
207
|
-
|
|
208
|
-
Segmented controls are full-width compact choices. Do not place `segmented` in inline half-width rows beside Switch, Color, Select, or another control; use `select` when a finite choice must occupy a half-width column.
|
|
209
|
-
|
|
210
|
-
## Control Selection Inventory
|
|
211
|
-
|
|
212
|
-
Before writing schema controls, map product needs to built-ins by value model, not visual similarity.
|
|
213
|
-
|
|
214
|
-
For every user-visible product setting or action, write:
|
|
215
|
-
|
|
216
|
-
```txt
|
|
217
|
-
Product need:
|
|
218
|
-
Value model:
|
|
219
|
-
Candidate built-ins checked:
|
|
220
|
-
Best built-in:
|
|
221
|
-
Rejected alternatives:
|
|
222
|
-
Target:
|
|
223
|
-
Required acceptance:
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
If a built-in exact owner matches the value model, use that built-in. If several controls fit, choose the best fit and record why. If no built-in fits, use the custom escape hatch from `component-rules.md`.
|
|
56
|
+
- `panels.controls` contains product sections after mandatory runtime `Setup`.
|
|
57
|
+
- `panels.layers` is only for multiple editable objects, media objects, groups, visibility, selection, or reorder.
|
|
58
|
+
- `panels.timeline` is required for product animation, keyframes, playback, and video export.
|
|
227
59
|
|
|
228
|
-
|
|
60
|
+
Timeline compact/extended presentation is runtime UI state owned by the auto-injected `Setup` switch. Do not create product targets for `panels.timeline.extended`.
|
|
229
61
|
|
|
230
|
-
##
|
|
62
|
+
## Toolbar
|
|
231
63
|
|
|
232
|
-
|
|
64
|
+
`toolbar` configures runtime-owned controls:
|
|
233
65
|
|
|
234
66
|
```ts
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
behavior: "Body motion preserves planted contact points before retargeting.",
|
|
242
|
-
frameIds: ["f000", "f012", "f024"],
|
|
243
|
-
},
|
|
244
|
-
],
|
|
245
|
-
behaviorDecomposition:
|
|
246
|
-
"The reference decomposes into moving body state, persistent anchors, delayed release, and retargeting behavior.",
|
|
247
|
-
extractionEvidence:
|
|
248
|
-
"Extracted frames with ffmpeg and reviewed a contact sheet before implementation.",
|
|
249
|
-
referenceLocation: "/path/to/reference.mp4",
|
|
250
|
-
storyboard: [
|
|
251
|
-
{
|
|
252
|
-
behaviorObservation: "Several endpoints remain planted while the body moves.",
|
|
253
|
-
frameId: "f000",
|
|
254
|
-
frameSource: "frames/frame_000.png",
|
|
255
|
-
timeSeconds: 0,
|
|
256
|
-
visualObservation: "The body is left of center with legs spread outward.",
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
transitionAnalysis: [
|
|
260
|
-
{
|
|
261
|
-
behaviorDelta:
|
|
262
|
-
"Between f000 and f012, body position changes while endpoints stay near their previous canvas positions.",
|
|
263
|
-
fromFrameId: "f000",
|
|
264
|
-
id: "f000-f012",
|
|
265
|
-
toFrameId: "f012",
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
} satisfies ToolcraftTransferMode;
|
|
67
|
+
toolbar: {
|
|
68
|
+
history: true,
|
|
69
|
+
radar: true,
|
|
70
|
+
theme: true,
|
|
71
|
+
zoom: true,
|
|
72
|
+
}
|
|
270
73
|
```
|
|
271
74
|
|
|
272
|
-
|
|
75
|
+
History owns undo/redo and keyboard shortcuts. Do not add route-local undo/redo listeners.
|
|
273
76
|
|
|
274
|
-
##
|
|
275
|
-
|
|
276
|
-
Reference clones must declare `referenceFeatureInventory` beside `behaviorCoverage`. The inventory is the source checklist for the port and is validated against acceptance rows.
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
export const starterTransferMode = {
|
|
280
|
-
behaviorCoverage: ["canvas-sizing", "control-mapping", "renderer-state"],
|
|
281
|
-
mode: "reference-runtime-clone",
|
|
282
|
-
referenceFeatureInventory: [
|
|
283
|
-
{
|
|
284
|
-
acceptanceId: "reference.rendererState",
|
|
285
|
-
behaviorEvidence:
|
|
286
|
-
"Observed the reference renderer keep particle state across multiple frames.",
|
|
287
|
-
featureName: "Renderer state",
|
|
288
|
-
id: "renderer-state",
|
|
289
|
-
referenceBehavior:
|
|
290
|
-
"The reference renderer keeps mutable particle state across frames.",
|
|
291
|
-
sourceEvidence: "Inspected reference/src/renderer.ts frame loop.",
|
|
292
|
-
status: "ported",
|
|
293
|
-
toolcraftMapping:
|
|
294
|
-
"The Toolcraft renderer keeps equivalent state and invalidation keys.",
|
|
295
|
-
},
|
|
296
|
-
],
|
|
297
|
-
referenceName: "Original app",
|
|
298
|
-
referenceStudy: {
|
|
299
|
-
behaviorEvidence:
|
|
300
|
-
"Ran the original app in a local browser and verified controls, renderer state, export, and media behavior.",
|
|
301
|
-
referenceLocation: "/path/to/original-app",
|
|
302
|
-
reproductionSteps:
|
|
303
|
-
"Installed dependencies, started the reference app, opened it in the browser, and compared behavior against the Toolcraft port.",
|
|
304
|
-
sourceEvidence:
|
|
305
|
-
"Inspected routes, renderer, control state, timeline/export handlers, and media lifecycle files.",
|
|
306
|
-
status: "ran-original",
|
|
307
|
-
},
|
|
308
|
-
referenceTimeline: { behaviorCoverage: [], mode: "none" },
|
|
309
|
-
sourceOfTruth: "reference-runtime",
|
|
310
|
-
} satisfies ToolcraftTransferMode;
|
|
311
|
-
```
|
|
77
|
+
## Control Fields
|
|
312
78
|
|
|
313
|
-
|
|
79
|
+
Common control fields:
|
|
314
80
|
|
|
315
|
-
|
|
81
|
+
| Field | Purpose |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `type` | Built-in type or registered custom renderer type. |
|
|
84
|
+
| `target` | Runtime state target. |
|
|
85
|
+
| `defaultValue` | Initial value and reset value. |
|
|
86
|
+
| `label` | Short UI label, `false`, or omitted. |
|
|
87
|
+
| `description` | Product-specific help text only when it adds meaning beyond the label. |
|
|
88
|
+
| `visibleWhen` | Mode/type/source/include/variant/count visibility. Hidden values are preserved. |
|
|
89
|
+
| `orderRole` | Makes section order testable. |
|
|
90
|
+
| `performanceRole` | `"workload"` or `"responsiveness"` for coverage derivation. |
|
|
91
|
+
| `performanceReason` | Why the role fits this app. |
|
|
92
|
+
| `commitMode` | `text` controls: `"content"` applies while typing, `"setting"` commits on blur/Enter. |
|
|
93
|
+
| `keyframeable` | Timeline/keyframe capability override when structurally needed. |
|
|
94
|
+
| `variant` | Component-specific variant. |
|
|
316
95
|
|
|
317
|
-
|
|
96
|
+
Use `visibleWhen` for product availability. Do not use `disabled`, `disabledWhen`, or inert visible controls for generated product branches.
|
|
318
97
|
|
|
319
|
-
|
|
98
|
+
Conditions support `equals`, `notEquals`, `oneOf`, `notOneOf`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, and `lessThanOrEqual`.
|
|
320
99
|
|
|
321
|
-
|
|
322
|
-
- `timeline-keyframes`: property animation controlled by keyframe diamonds and rows.
|
|
323
|
-
- `autonomous`: decorative/self-running output with no user-facing transport and no video export.
|
|
100
|
+
Reserved runtime targets include `runtime.settingsTransfer`, `canvas.aspectRatio`, `canvas.size.width`, `canvas.size.height`, `canvas.renderScale`, and `panels.timeline.extended`. Product sections must not declare those controls.
|
|
324
101
|
|
|
325
|
-
In
|
|
102
|
+
## Built-In Control Types
|
|
326
103
|
|
|
327
|
-
|
|
104
|
+
| `type` | Renders |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `actions` | Local section commands in 50% button cells. |
|
|
107
|
+
| `anchorGrid` | Anchor picker. |
|
|
108
|
+
| `channelMixer` | RGB channel mixer. |
|
|
109
|
+
| `checkbox` | Checkbox field. |
|
|
110
|
+
| `collectionActions` | Add/remove controls for growable runtime-backed item lists. |
|
|
111
|
+
| `code` | Multiline editor for long or structured content. |
|
|
112
|
+
| `color` | Hex color picker. |
|
|
113
|
+
| `colorOpacity` | Hex color plus opacity. |
|
|
114
|
+
| `curves` | Single or RGB curve editor. |
|
|
115
|
+
| `fileDrop` | Image/file upload, multiple upload, sorting, and image transforms. |
|
|
116
|
+
| `fontPicker` | Atomic typography block: font, weight, size, case, color/opacity, letter spacing, line height. |
|
|
117
|
+
| `gradient` | Gradient type, angle, track, stops. |
|
|
118
|
+
| `imagePicker` | Visual option picker. |
|
|
119
|
+
| `palette` | Design-token palette family + shade. |
|
|
120
|
+
| `rangeInput` | Two compact text values. |
|
|
121
|
+
| `rangeSlider` | Two-thumb slider. |
|
|
122
|
+
| `segmented` | Full-width compact finite choice. |
|
|
123
|
+
| `select` | Dropdown. |
|
|
124
|
+
| `slider` | Single-value slider. |
|
|
125
|
+
| `switch` | Binary switch. |
|
|
126
|
+
| `text` | Single-line input. |
|
|
127
|
+
| `vector` | X/Y vector pad and fields. |
|
|
128
|
+
|
|
129
|
+
Use `component-rules.md` for component-specific fit, labels, variants, units, parser behavior, and exceptions. Use `core/control-selection.md` before deciding a custom control is needed.
|
|
328
130
|
|
|
329
131
|
## Control Section Inventory
|
|
330
132
|
|
|
331
|
-
Before
|
|
332
|
-
|
|
333
|
-
- section title;
|
|
334
|
-
- product entity or workflow stage;
|
|
335
|
-
- included schema targets;
|
|
336
|
-
- reason these controls belong together or reason for a real workflow split.
|
|
133
|
+
Before writing `panels.controls.sections`, export `starterControlSectionInventory` beside `starterAcceptance`:
|
|
337
134
|
|
|
338
135
|
```ts
|
|
339
136
|
export const starterControlSectionInventory = [
|
|
@@ -344,101 +141,54 @@ export const starterControlSectionInventory = [
|
|
|
344
141
|
targets: ["text.content", "text.font"],
|
|
345
142
|
title: "Text",
|
|
346
143
|
},
|
|
347
|
-
{
|
|
348
|
-
entity: "Object shape",
|
|
349
|
-
groupingReason: "Structure controls tune the physical footprint of the object.",
|
|
350
|
-
splitReason:
|
|
351
|
-
"Structure and density are separate workflow stages in this editor.",
|
|
352
|
-
targets: ["object.shape.size"],
|
|
353
|
-
title: "Shape Structure",
|
|
354
|
-
workflowStage: "structure",
|
|
355
|
-
},
|
|
356
144
|
] as const;
|
|
357
145
|
```
|
|
358
146
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
The inventory must match the rendered schema: every product control target in every product section appears exactly once, and every inventory target renders in the section named by `title`. Runtime `Setup` controls, sticky footer `Export` actions, `settingsTransfer`, and runtime canvas sizing controls do not need inventory entries. If one target entity is split across sections, every split section must declare `workflowStage` and a concrete `splitReason`; otherwise the validator treats the split as accidental section drift.
|
|
362
|
-
|
|
363
|
-
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.
|
|
364
|
-
|
|
365
|
-
Every visible app-authored 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.
|
|
147
|
+
Every product control target appears exactly once in the inventory. Runtime `Setup`, sticky footer `Export`, settings transfer, and runtime canvas sizing targets do not need entries.
|
|
366
148
|
|
|
367
|
-
|
|
149
|
+
## Transfer Metadata
|
|
368
150
|
|
|
369
|
-
|
|
151
|
+
Reference and motion metadata lives in `starterTransferMode`.
|
|
370
152
|
|
|
371
|
-
|
|
153
|
+
Use `transferMode: "reference-runtime-clone"` when porting an existing app unless the user explicitly asks for redesign. Reference clones declare `referenceStudy`, `referenceFeatureInventory`, and acceptance mapping; detailed evidence requirements live in `core/reference-study.md`.
|
|
372
154
|
|
|
373
|
-
|
|
155
|
+
Video references declare `videoReferenceStudy` before implementation. Animated products declare `animationIntent`, and playback/keyframe timeline apps declare a proven loop duration when known. Detailed animation rules live in `core/timeline-animation.md`.
|
|
374
156
|
|
|
375
|
-
|
|
157
|
+
## Export And Actions
|
|
376
158
|
|
|
377
|
-
|
|
159
|
+
Product apps expose delivery through sticky `panelActions`, not canvas UI or ordinary body controls. Still products expose `Export PNG`; animated products expose `Export Video` plus `Export PNG`.
|
|
378
160
|
|
|
379
|
-
|
|
161
|
+
Every app with `Export PNG` includes `Image Export` controls with `export.image.format` and `export.image.resolution`. Animated apps also include `Video Export` controls with `export.video.format` and `export.video.resolution`.
|
|
380
162
|
|
|
381
|
-
|
|
163
|
+
Use standard helpers:
|
|
382
164
|
|
|
383
|
-
|
|
165
|
+
- `createToolcraftPngExportCanvas({ resolution, includeBackground, state, render })`
|
|
166
|
+
- `shouldIncludeToolcraftPreviewBackground(state)`
|
|
167
|
+
- `getToolcraftVideoExportSize({ resolution, state })`
|
|
384
168
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
Inline two-column groups are preferred when controls tune one close product meaning and labels/values fit. Short numeric text pairs can be inline. Related short `select` pairs can be inline, especially workflow pairs such as `Format` + `Resolution`, `Codec` + `Profile`, or `Width unit` + `Height unit`. Use stacked one-control rows only as a fit fallback when a label, selected value, or option text would clip, truncate, or lose padding; record that fallback reason in the spec or worklog. A short numeric/text field may also pair with one related plain `color` field when both configure the same entity, such as `Mask size` and `Color` inside `Mask`. `colorOpacity` never renders in inline two-column groups; if either color control has opacity, keep the controls stacked. Color labels are semantic, not automatic: decide once for the whole color group, omit per-item labels such as `Color 1` for palette variation banks like `Accent Shades` or `Bead Colors`, and do not mix labeled and unlabeled items inside that bank. Sibling controls like `Spread` or `Randomness` do not force item labels; keep visible labels only when colors edit distinct roles such as `Fill`, `Stroke`, `Background`, `Connector`, or `Object`. Related plain color banks render two per row, and an odd trailing plain color remains half-width instead of stretching to a full row. Mixed inline rows usually require visible labels on every field, except toggle-plus-parameter rows, the required Background row, and palette variation color banks whose group/section label already names the bank. All 50/50 inline rows use the same horizontal column gap as paired `select` controls; do not create a wider or narrower gap for toggle-plus-parameter rows. Two adjacent `switch` or `checkbox` controls for the same product entity must share one inline row when both visible labels fit without truncation; the runtime auto-pairs safe adjacent toggles by target entity, and schemas should stack them only when either label is too long. A single `switch` or `checkbox` may share an inline row with one related parameter control when the toggle label fits and both controls edit the same entity; the non-toggle parameter uses `label: false`, and if that label is needed, stack the controls instead. Toggle plus parameter rows are equal-width two-column rows: each control occupies one half, never intrinsic toggle width plus remaining space. The required Background row uses `Include` plus unlabeled background color. Schema `slider` and `rangeSlider` controls always stay stacked at full width; the only built-in exception is the paired letter-spacing and line-height footer sliders inside `fontPicker`. Do not place sibling controls for case, color, opacity, size, weight, letter spacing, or line height when the same text entity already uses `fontPicker`.
|
|
388
|
-
|
|
389
|
-
`rangeSlider` is always a full-width two-thumb control. Do not include it in `layoutGroups`. Its `defaultValue` must start with different lower and upper values, such as `[20, 80]`, so the two handles do not collapse into one apparent slider. Manual range labels accept built-in separators such as slash, hyphen, spaces, and dashes.
|
|
390
|
-
|
|
391
|
-
## Control Order
|
|
392
|
-
|
|
393
|
-
Order controls by decision flow inside each section:
|
|
394
|
-
|
|
395
|
-
- `input`: upload, source, and canvas-size controls;
|
|
396
|
-
- `mode`: mode, type, filter, blend, style, and preset selectors;
|
|
397
|
-
- `primary`, `spatial`, `color`: core product parameters;
|
|
398
|
-
- `strength`: intensity, opacity, scale, depth;
|
|
399
|
-
- `detail`: noise, texture, blur, density, radius, quality;
|
|
400
|
-
- `advanced`: secondary tuning;
|
|
401
|
-
- `action`: footer actions.
|
|
402
|
-
|
|
403
|
-
A selector that changes how later controls are interpreted must use `orderRole: "mode"` and sit above dependent parameters.
|
|
404
|
-
|
|
405
|
-
Use `visibleWhen` for mode-, type-, source-, include-, variant-, or count-exclusive controls and sections. Example: `Partner` is visible when `coBrand.identityMode` is `text`; `Partner logo` is visible when it is `logo`. Count-controlled banks use numeric conditions: `Shade 4` is visible when `shapes.shadeCount` is `greaterThanOrEqual: 4`. When every control in a section is hidden by `visibleWhen`, the whole section is hidden automatically. If a switch/select/segmented/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use `visibleWhen`, not `disabledWhen`.
|
|
406
|
-
|
|
407
|
-
If a selector says “use the first N”, “number of colors”, “number of stops”, “active slots”, or similar, dependent sibling controls must be hidden with `visibleWhen` when they are outside the current count. Do not leave all possible controls visible while making the renderer ignore the inactive ones.
|
|
408
|
-
|
|
409
|
-
App schema tests must assert visible control order with `getToolcraftControlOrderTargets(appSchema)` or an equivalent exact target-order check.
|
|
169
|
+
Detailed Setup, Background, Image Export, Video Export, sticky action, icon, and progress rules live in `core/setup-export.md`.
|
|
410
170
|
|
|
411
171
|
## Persistence
|
|
412
172
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
Use `persistence: { storage: "localStorage", key, version, include }` when user-edited app settings should survive reload. Typical product editors persist `values`, `canvas`, and `panels`. If localStorage persistence is enabled and any runtime panel is visible, `include` must contain `"panels"` so dragged panel positions survive reload in that specific app. Add `timeline` when playback position, duration, loop, expansion, or keyframes should survive reload. Add `layers` only when the app has a real layer model. Add `media` only when runtime media state must survive reload, such as predefined attached files that users can delete, reorder, or transform.
|
|
173
|
+
Use persistence only for intentional user-edited state:
|
|
416
174
|
|
|
417
|
-
|
|
175
|
+
```ts
|
|
176
|
+
persistence: {
|
|
177
|
+
storage: "localStorage",
|
|
178
|
+
key: "my-toolcraft-app",
|
|
179
|
+
version: 1,
|
|
180
|
+
include: ["values", "canvas", "panels"],
|
|
181
|
+
}
|
|
182
|
+
```
|
|
418
183
|
|
|
419
|
-
|
|
184
|
+
Add `"timeline"`, `"layers"`, or `"media"` only when those runtime slices should survive reload. If localStorage is enabled, add reload acceptance coverage.
|
|
420
185
|
|
|
421
186
|
## Settings Transfer
|
|
422
187
|
|
|
423
|
-
|
|
188
|
+
`settingsTransfer` customizes runtime-owned settings import/export identity:
|
|
424
189
|
|
|
425
190
|
```ts
|
|
426
191
|
settingsTransfer: "auto"
|
|
427
192
|
```
|
|
428
193
|
|
|
429
|
-
Allowed values
|
|
430
|
-
|
|
431
|
-
- `"auto"`: default. Runtime still shows the mandatory first Setup section.
|
|
432
|
-
- `true`: keep the mandatory section and mark settings transfer explicitly enabled.
|
|
433
|
-
- `false`: keep the mandatory section; this value is retained as metadata only and does not hide `Export Settings` / `Import Settings`.
|
|
434
|
-
- `{ enabled, appId, fileName }`: customize the exported JSON identity and file name.
|
|
435
|
-
|
|
436
|
-
Runtime inserts a visible headerless `Setup` controls block as the first controls-panel block. It exports and imports control values, `canvas.size`, and timeline state, ignores unknown targets on import, and pauses playback after importing.
|
|
437
|
-
|
|
438
|
-
Do not add settings transfer buttons manually and do not use complexity thresholds to decide whether they appear.
|
|
439
|
-
|
|
440
|
-
When the canvas uses `editable-output` sizing, `Setup` 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 sections or recreate them manually. Product sections must not declare `runtime.settingsTransfer`, `canvas.aspectRatio`, `canvas.size.width`, `canvas.size.height`, `canvas.renderScale`, or `panels.timeline.extended`; runtime Setup owns those targets and always renders its own controls.
|
|
441
|
-
|
|
442
|
-
A settings-transfer section with only `Export Settings` and `Import Settings` means the canvas is not `editable-output`. For product-output apps, treat that as a schema error to fix, not as a layout variant.
|
|
443
|
-
|
|
444
|
-
Do not hand-write `settings-transfer.ts`, hidden file inputs, route handlers, or `panelActions` for settings import/export. Sticky footer `panelActions` remain product delivery only.
|
|
194
|
+
Allowed values are `"auto"`, `true`, `false`, or `{ enabled, appId, fileName }`. None of these hide mandatory runtime `Setup`; they only affect metadata. Do not implement settings import/export through `panelActions`, hidden file inputs, or route-local handlers.
|