@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.
@@ -1,339 +1,136 @@
1
1
  # Schema Reference
2
2
 
3
- Edit `src/app/app-schema.ts` as the public product surface.
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
- - Use `defineToolcraft`.
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
- ```ts
37
- export: {
38
- png: {
39
- background: "include",
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
- `export.png.background` defaults to `"include"`. Product apps still expose runtime controls for the actual user choice:
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
- - `appearance.background` or `scene.background` as a `color` control;
47
- - `export.includeBackground` as a boolean/options control.
23
+ ## Canvas
48
24
 
49
- PNG exporters should call `createToolcraftPngExportCanvas({ background, includeBackground, resolution, state, render })`, where `background`, `includeBackground`, and `resolution` come from runtime state. Live preview renderers should call `shouldIncludeToolcraftPreviewBackground(state)` and hide only the product-rendered background when it returns false; do not hide or replace the Toolcraft canvas shell/backing. For every app with `Export PNG`, `resolution` comes from `export.image.resolution`: `2k`, `4k`, and `8k` render actual 2048/4096/8192px long-edge PNGs. `current` or omitted resolution falls back to retina sizing. Video export always includes the product background, uses `getToolcraftVideoExportSize({ resolution: state.values["export.video.resolution"], state })`, and must prove exported metadata duration matches the runtime timeline duration.
25
+ Canvas sizing modes:
50
26
 
51
- Every app with `Export PNG` exposes a separate `Image Export` controls section. For still-output apps it sits directly above sticky footer actions. For animated apps with both `Export PNG` and `Export Video`, it sits immediately before `Video Export`:
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
- ```ts
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
- Animated apps with `Export Video` must enable the top Toolcraft timeline and also expose a separate `Video Export` controls section. Do not mix video export settings into renderer/effect sections. Place this section after `Image Export` as the final authored controls section directly above sticky footer export buttons. `Format` and `Resolution` are a compact semantic pair, so use an inline two-column layout by default; stack them only when labels or selected values would clip.
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
- title: "Video Export",
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
- columns: 2,
119
- controls: ["videoFormat", "videoResolution"],
120
- layout: "inline",
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
- Use `MediaRecorder.isTypeSupported(...)` or an explicit encoder/transcoder capability check before choosing the actual MIME/container. `MOV` and `ProRes` are not baseline browser outputs; use them only with a custom encoder/transcoder and dedicated acceptance plus performance coverage. `4K` is an export resolution target, not a reason to lock `canvas.size` and not PNG-style 4096px long-edge sizing. Use `getToolcraftVideoExportSize`: `current` uses the current canvas/output size with even encoder-safe rounding, while `4k` fits inside an encoder-safe 3840x2160 box, preserves aspect ratio, and returns even pixel dimensions. Set recording canvas dimensions before `captureStream`, `MediaRecorder`, `VideoEncoder`, or equivalent setup, and reject recorder/encoder errors instead of returning corrupt blobs. Offline rendered-frame video export must write timeline-based timestamps; `canvas.captureStream()` plus `MediaRecorder` records wall-clock time and cannot be the only duration mechanism for heavy renderers.
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
- - Use `panels: {}` for the neutral starter or for products that have no user-facing panels yet.
159
- - Controls panel is the primary editing panel once the product has schema controls.
160
- - Layers are optional. Enable only for multiple editable objects, media objects, groups, visibility, selection, reorder, or selected-layer controls.
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
- For `curves`, choose the variant explicitly. Use `variant: "single"` for acceleration, bend, easing, response, depth, mask, opacity, threshold, or remap curves. Omit it only for RGB/color-correction or channel-specific curves that intentionally need RGB/R/G/B tabs.
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
- ## Video Reference Study
62
+ ## Toolbar
231
63
 
232
- When a video, GIF, screen recording, contact sheet, or extracted-frame sequence is supplied as a reference, declare `starterTransferMode.videoReferenceStudy` before implementation. This is independent of whether the app is a new Toolcraft app or a reference-runtime clone.
64
+ `toolbar` configures runtime-owned controls:
233
65
 
234
66
  ```ts
235
- export const starterTransferMode = {
236
- mode: "new-toolcraft-app",
237
- videoReferenceStudy: {
238
- acceptanceMapping: [
239
- {
240
- acceptanceId: "reference.video.motion",
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
- The real study must include at least four storyboard frames, at least three frame-to-frame transition rows, behavior decomposition, and acceptance mapping to automated browser-backed tests. Do not implement from a single screenshot or static summary when the reference is temporal.
75
+ History owns undo/redo and keyboard shortcuts. Do not add route-local undo/redo listeners.
273
76
 
274
- ## Reference Transfer Mode
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
- If the original cannot run as-is but behavior can be reconstructed, use `status: "restored-local"` and describe the restoration steps. Use `status: "source-inspection-only"` only with `sourceOnlyReason` explaining the concrete blocker that made running or restoring unavailable.
79
+ Common control fields:
314
80
 
315
- Use `status: "ported"` when the behavior is carried over directly and `status: "toolcraft-native"` when Toolcraft owns the same behavior, such as canvas sizing or export shell. Use `status: "intentionally-changed"` only with `userApprovedChangeReason` that cites explicit user approval or redesign/change-request evidence.
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
- ## Animation Intent
96
+ Use `visibleWhen` for product availability. Do not use `disabled`, `disabledWhen`, or inert visible controls for generated product branches.
318
97
 
319
- Before adding animation controls, decide the animation owner:
98
+ Conditions support `equals`, `notEquals`, `oneOf`, `notOneOf`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, and `lessThanOrEqual`.
320
99
 
321
- - `timeline-playback`: product time controlled by the top timeline.
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 keyframes mode, renderer code reads evaluated values from the runtime keyframe evaluator. Do not parse timeline labels and do not use raw `state.values` for targets with keyframes.
102
+ ## Built-In Control Types
326
103
 
327
- If the product output is animated, use the top Toolcraft timeline. Use `panels.timeline: { mode: "playback", defaultDurationSeconds }` for playback animation and set `defaultDurationSeconds` to the product loop duration when it is known. 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. `starterTransferMode.animationIntent` must declare `loopDuration: { source, seconds, evidence }` for playback/keyframe animation; valid sources are `reference`, `user-request`, and `product-derived`, never runtime/template fallback 8s. Reference clones that choose `referenceTimeline.mode: "toolcraft-playback"` or `"toolcraft-keyframes"` declare the same shape on `starterTransferMode.referenceTimeline.loopDuration`. `defaultDurationSeconds` must equal the declared `loopDuration.seconds`. Any app with `Export Video` must enable the top Toolcraft timeline and use runtime timeline time for preview/export duration and seamless forward-loop behavior. Product loops must advance in one direction and stitch first/last frames at any timeline duration; mirror, yoyo, ping-pong, or reverse loops require explicit user request. Playback renderers should use `getToolcraftTimelineLoopTime` or `getToolcraftTimelineLoopProgress` so `state.timeline.durationSeconds` remains the active loop period after the user edits duration. If no timeline is used while animation controls remain visible, `starterTransferMode.animationIntent` must declare `mode: "autonomous"`, include a concrete reason, include behavior coverage for no transport, no play/pause, no scrub, no duration control, no loop control, and no export-at-time, and prove there is no product animation and no video export.
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 editing `panels.controls.sections`, define and export `starterControlSectionInventory` beside `starterAcceptance` in `src/app/starter-acceptance.ts`. This is the machine-checkable version of the section plan.
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
- Group controls by product meaning, not by component type. Do not create sections named `Controls`, `Settings`, `Options`, `Sliders`, `Inputs`, `Buttons`, `Color`, or `Colors`.
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
- Section expand/collapse uses the standard runtime height/opacity animation. Do not replace it with instant custom section visibility.
149
+ ## Transfer Metadata
368
150
 
369
- 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.
151
+ Reference and motion metadata lives in `starterTransferMode`.
370
152
 
371
- 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`.
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
- 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.
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
- Large built-in compound controls inside mixed sections render content-width internal dividers with 18px between each rendered divider and the control content. If a compound control is the first item in that section, render only its bottom internal divider and remove the top internal padding. If it is the last item in that section, render only its top internal divider and remove the bottom internal padding. If a section contains exactly one control, whether simple or compound, only the parent section dividers render. Single `curves` are not compound for dividers; RGB `curves` are compound.
157
+ ## Export And Actions
376
158
 
377
- Controls for the same product entity stay in the same section. For example, `squares.right.connections`, `squares.right.hoverRadius`, and `squares.right.color` belong in `Square 1 (Right)` with `Color` as the field label. A standalone color section is only valid when the color is the whole product entity, such as `Background`, `Accent`, `Connector`, or `Brand`.
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
- Keep sections discrete. A section usually has two to seven product controls. If a section grows past seven controls, split it by the next product sub-entity or workflow stage instead of keeping a broad bucket. Broad titles such as `Flow`, `Icon`, `Shapes`, `Scene`, `Text`, `Typography`, or `Motion` are valid only for small cohesive groups; larger groups need specific titles such as `Flow Motion`, `Flow Geometry`, `Letter Burst`, `Shape Colors`, `Logo Glow`, `Logo Plate`, or `Text Block`. Section titles must be unique in the same panel.
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
- Control labels are judged with their nearest visible context. Short property labels such as `Speed`, `Color`, `Size`, or `Opacity` are allowed when the section or group clearly names the edited product entity. If the section is generic, mixed, missing, or otherwise weak context, include the affected entity or role in the label, such as `Pattern color`, `Background opacity`, `Wave speed`, or `Stroke width`. Acceptance suggests a semantic replacement label; fix the schema label instead of relying on runtime fallback rewriting.
163
+ Use standard helpers:
382
164
 
383
- If a target prefix has to be split across sections, the spec must name the workflow reason. Otherwise the acceptance validator treats the split as a sectioning error.
165
+ - `createToolcraftPngExportCanvas({ resolution, includeBackground, state, render })`
166
+ - `shouldIncludeToolcraftPreviewBackground(state)`
167
+ - `getToolcraftVideoExportSize({ resolution, state })`
384
168
 
385
- Switch and checkbox labels name the setting context only. Do not prefix them with `Enable` or `Disable`; use `CRT`, `Glow`, `Loop`, or `Guides` instead. If the nearest section title already names the context, do not duplicate it as the visible toggle label. Use a short contextual label such as `Include` or, only for icon-only visual toggles, `label: false` with the product meaning in `target` and `description`.
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
- State persistence is a product policy, not a hidden side effect.
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
- Do not write media state directly to storage and do not use product values to mirror the attached file list. Use `media.defaultAssets` for predefined source files/background images; they render as ordinary `fileDrop` attachments, can be removed to produce an empty source/canvas state, and Reset restores them. Theme preference is runtime-owned separately. Do not write runtime state to `localStorage` directly from app code.
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
- If `storage` is `"localStorage"`, add a runtime acceptance row with `persistenceCoverage: "reload"` and a browser test that changes a user-facing setting, reloads the page, and verifies the restored value or product output. Settings import/export is preset transfer, not proof that persistence works.
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
- Generated apps keep a controls panel so runtime `Setup` is visible from the first run. Product controls are added after that mandatory runtime section. Use `settingsTransfer` only to customize the exported JSON identity or file name.
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.