@pixel-point/toolcraft 0.0.4 → 0.0.7
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/scripts/prepare-pack.mjs +5 -0
- package/src/generate.mjs +13 -0
- package/src/generate.test.mjs +7 -1
- package/templates/runtime/contracts/component-contracts.test.ts +96 -3
- package/templates/runtime/contracts/component-contracts.ts +75 -3
- package/templates/runtime/contracts/decision-contracts.test.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.ts +1 -1
- package/templates/runtime/react/canvas-shell.test.tsx +7 -7
- package/templates/runtime/react/controls-panel.test.tsx +455 -1
- package/templates/runtime/react/controls-panel.tsx +515 -30
- package/templates/runtime/react/settings-transfer.test.ts +3 -3
- package/templates/runtime/react/timeline-panel.test.tsx +69 -0
- package/templates/runtime/react/timeline-panel.tsx +98 -10
- package/templates/runtime/react/toolbar-panel.test.tsx +6 -6
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +2 -2
- package/templates/runtime/schema/define-toolcraft.test.ts +78 -1
- package/templates/runtime/schema/define-toolcraft.ts +145 -6
- package/templates/runtime/schema/runtime-targets.ts +1 -0
- package/templates/runtime/schema/types.ts +46 -1
- package/templates/runtime/state/canvas-zoom.ts +1 -1
- package/templates/runtime/state/create-template-state.test.ts +6 -6
- package/templates/runtime/state/reducer.test.ts +139 -8
- package/templates/runtime/state/reducer.ts +88 -22
- package/templates/runtime/state/types.ts +3 -0
- package/templates/starter/AGENTS.md +8 -8
- package/templates/starter/docs/toolcraft/README.md +4 -3
- package/templates/starter/docs/toolcraft/acceptance-testing.md +6 -2
- package/templates/starter/docs/toolcraft/assembly-workflow.md +8 -6
- package/templates/starter/docs/toolcraft/component-rules.md +17 -1
- package/templates/starter/docs/toolcraft/custom-controls.md +2 -2
- package/templates/starter/docs/toolcraft/performance.md +8 -7
- package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +15 -4
- package/templates/starter/docs/toolcraft/workflow.md +5 -8
- package/templates/starter/gitignore +36 -0
- package/templates/starter/package.json +1 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +55 -0
- package/templates/starter/src/app/starter-acceptance.ts +67 -1
- package/templates/starter/src/app/starter-performance.test.ts +1 -1
- package/templates/ui/components/controls/collection-actions/collection-actions-control.tsx +60 -0
- package/templates/ui/components/controls/collection-actions/index.ts +4 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +101 -18
- package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1 -6
- package/templates/ui/components/controls/index.ts +8 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +4 -1
- package/templates/ui/components/controls/slider/slider-value.ts +48 -5
- package/templates/ui/components/primitives/editable-slider-value-label.tsx +6 -1
- package/templates/ui/index.ts +1 -0
|
@@ -146,7 +146,9 @@ Async product actions such as Export, Download, Copy, Generate, or Apply must re
|
|
|
146
146
|
|
|
147
147
|
For complex apps, use schema `settingsTransfer: "auto"` or `true` for settings import/export. Recalculate settings-transfer eligibility after adding, removing, or reorganizing controls, sections, timeline, or layers. The runtime threshold is 12 product controls, 5 product sections, or weighted score 18. Do not put Import Settings or Export Settings in sticky footer `panelActions`; runtime inserts the technical `Setup` settings-transfer section first without a visible section heading.
|
|
148
148
|
|
|
149
|
-
If the app also uses `editable-output` canvas sizing, that first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`,
|
|
149
|
+
If the app also uses `editable-output` canvas sizing, that first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`, `Canvas height`, and optional `Resolution scale` in that order. Do not split the canvas size fields and settings-transfer actions into app-authored sections.
|
|
150
|
+
|
|
151
|
+
For non-vector raster, Canvas 2D, WebGL, or WebGPU previews, set `canvas.renderScale: true`. Runtime appends `Resolution scale` to the same first technical section. The slider changes backing resolution from `1x` to `2x` without changing visible canvas size; DOM/SVG/vector-native previews should not use it.
|
|
150
152
|
|
|
151
153
|
If a controls panel shows only `Export Settings` and `Import Settings` in the first runtime section, check the canvas sizing decision. Product-output apps usually need `editable-output`; intrinsic media and explicitly fixed output are the cases where visible canvas size inputs are absent.
|
|
152
154
|
|
|
@@ -188,8 +190,8 @@ Use these tiers:
|
|
|
188
190
|
| Tier 0 — docs/copy | Documentation, comments, copy, labels, or titles change without schema targets, values, runtime behavior, renderer output, or layout mechanics. | Targeted docs/typecheck or targeted app test. Browser is not required unless visual text fitting is the risk. |
|
|
189
191
|
| Tier 1 — local control presentation | One control or panel visual state changes: spacing, hover, focus, disabled, marker visibility, label fit, or component variant display. Runtime state shape and product renderer are unchanged. | Targeted unit/component test plus one focused browser check for the affected control or panel. |
|
|
190
192
|
| Tier 2 — schema/product behavior | Controls, sections, defaults, persistence, panel actions, export actions, acceptance rows, or product behavior mapping changes. | `pnpm verify:quick` plus relevant browser acceptance. Run perf only when the changed control affects renderer workload or responsiveness. |
|
|
191
|
-
| Tier 3 — renderer/canvas/runtime feature | Custom renderer, animation loop, canvas sizing, upload/media, timeline, layers, toolbar, export bytes, WebGL/Canvas/SVG output, zoom, radar, history, heavy control behavior changes, or a post-generation iteration that touches renderer workload or viewport stability. | `pnpm verify:quick`, targeted browser acceptance, and
|
|
192
|
-
| Tier 4 — final delivery/template architecture | Fresh generated app completion, folder export, commit-ready delivery, dependency changes, runtime/template/contract/CLI changes, broad refactors, or major post-generation iterations that rewrite renderer, canvas, animation, timeline/keyframes, layers, media, export, or control mapping. | Fresh folders run `pnpm install` once, then `pnpm verify:final
|
|
193
|
+
| Tier 3 — renderer/canvas/runtime feature | Custom renderer, animation loop, canvas sizing, upload/media, timeline, layers, toolbar, export bytes, WebGL/Canvas/SVG output, zoom, radar, history, heavy control behavior changes, or a post-generation iteration that touches renderer workload or viewport stability. | `pnpm verify:quick`, targeted browser acceptance, and targeted performance scenarios only for touched workload/viewport/export paths. |
|
|
194
|
+
| Tier 4 — final delivery/template architecture | Fresh generated app completion, folder export, commit-ready delivery, dependency changes, runtime/template/contract/CLI changes, broad refactors, or major post-generation iterations that rewrite renderer, canvas, animation, timeline/keyframes, layers, media, export, or control mapping. | Fresh folders run `pnpm install` once, then `pnpm verify:final`; add `pnpm verify:perf` only for the first working app version or explicit performance complaints, then start `pnpm dev` to provide the local URL. |
|
|
193
195
|
|
|
194
196
|
Choose the tier by blast radius, not by line count. If uncertain, move one tier higher, not automatically to Tier 4.
|
|
195
197
|
|
|
@@ -197,9 +199,9 @@ Do not rerun `pnpm install` after every edit. Run it after fresh export, depende
|
|
|
197
199
|
|
|
198
200
|
Use `pnpm verify:ui` when a tier calls for the browser acceptance suite without the performance suite. Use a focused named Playwright test instead when only one entity changed and the relevant test is already known.
|
|
199
201
|
|
|
200
|
-
Run a full performance checkpoint with `pnpm verify:perf` when the first working version of the app exists,
|
|
202
|
+
Run a full performance checkpoint with `pnpm verify:perf` only when the first working version of the app exists, or when the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance.
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
Feature loops after the first working version do not run the full performance suite by default. Renderer, canvas, animation, export, timeline, layers, `canvas.renderScale`, bug fixes, and performance-sensitive controls still need targeted functional/browser checks first, plus targeted performance scenarios only when they directly exercise the touched path. Record any skipped full performance run and reason in the worklog.
|
|
203
205
|
|
|
204
206
|
For final delivery, run:
|
|
205
207
|
|
|
@@ -208,6 +210,6 @@ pnpm verify:final
|
|
|
208
210
|
pnpm dev
|
|
209
211
|
```
|
|
210
212
|
|
|
211
|
-
Browser verification must use the real Toolcraft shell plus renderer output. `pnpm verify:final` runs the full static, build, browser
|
|
213
|
+
Browser verification must use the real Toolcraft shell plus renderer output. `pnpm verify:final` runs the full static, build, and browser functional gate. `pnpm verify:perf` is intentionally separate and only runs for the two full-performance triggers. `pnpm dev` is intentionally separate because it keeps the local server running.
|
|
212
214
|
|
|
213
215
|
Do not stop existing local servers to free `3002`. `pnpm dev`, `pnpm preview`, and browser verification prefer `3002`, then automatically use the next free port when it is occupied.
|
|
@@ -22,6 +22,7 @@ Common exact-owner choices:
|
|
|
22
22
|
- Use `imagePicker` for choosing one visual option from a set.
|
|
23
23
|
- Use `palette` only for constrained design-token color choices with both family and shade: brand palette, Tailwind-like token color, style-guide color scale, semantic palette family, or theme accent token.
|
|
24
24
|
- Use `actions` for local section commands that affect only the nearby entity, such as randomize palette, normalize weights, sort glyphs, clear selection, duplicate item, or reset current stop.
|
|
25
|
+
- Use `collectionActions` for repeatable product entities whose actual item list can grow or shrink, such as colors, glyphs, symbols, points, rules, variants, or object entries. Use it instead of a count slider when the user edits the actual set. The item list must be runtime state that changes preview/export, not panel-only row chrome. The collection control shows the collection `label` on the left and remove/add icon buttons on the right. Homogeneous repeated items do not show visible per-item labels like `Color 1`, `Color 2`, `Item 1`, or `Item 2` when the collection label already names the group. Item controls should use built-ins such as `color`, `colorOpacity`, `text`, `select`, `segmented`, `slider`, `switch`, `checkbox`, or `rangeInput` before any custom renderer.
|
|
25
26
|
- Use `panelActions` for sticky final product actions such as export, copy, generate, apply, or download.
|
|
26
27
|
|
|
27
28
|
Small action buttons inside custom controls are for item-level actions such as remove, reorder, add stop, or delete stop. Use schema `actions` for section-level local commands. Keep final product actions in `panelActions`, keep timeline transport in the top timeline, and keep global reset in the controls panel header.
|
|
@@ -35,6 +36,7 @@ For local reset-like `actions`, use product-specific values such as `reset-curre
|
|
|
35
36
|
- If a section contains exactly one control, whether simple or compound, render only the parent section dividers.
|
|
36
37
|
- Do not add full-width borders inside a compound control, and do not put dividers only around an internal subsection such as Gradient Stops.
|
|
37
38
|
- Small compound fields such as `colorOpacity` and `rangeInput` stay inline fields without section dividers.
|
|
39
|
+
- `collectionActions` is a compound control when it shares a section with generated item controls, so it follows the same content-width divider rules. Place it at the start of the controlled section. Generated item controls still follow normal density rules: plain colors use equal 50% columns when they fit, while color+opacity items stay stacked.
|
|
38
40
|
|
|
39
41
|
## Sliders
|
|
40
42
|
|
|
@@ -50,6 +52,10 @@ Visual discrete sliders must declare `step`; the runtime derives one marker per
|
|
|
50
52
|
|
|
51
53
|
Schema sliders always render stacked at full width. Do not put `slider` or `rangeSlider` controls in two-column inline rows. The only built-in exception is `fontPicker`, whose letter-spacing and line-height footer sliders stay paired inside that component.
|
|
52
54
|
|
|
55
|
+
Use slider `unit` only for measurement or scale suffixes: `%`, `px`, `°`, `x`, `s`, `ms`, `fps`, `rows`, `cols`, or a similarly useful domain unit. Do not use `unit` to repeat the entity already named by the section or label. Avoid `Letters` + `letters`, `Shape Density / Count` + `shapes`, `Words` + `words`, `Symbols` + `symbols`, `Items` + `items`, `Particles` + `particles`, and `Layers` + `layers`. If the numeric value needs an entity noun to make sense, rename the label or section instead of appending the noun to the value. Compact units render tight (`70%`, `24px`, `1.2x`, `8s`); word or acronym units render with a space (`5 cols`, `17 fps`) only when they are truly needed.
|
|
56
|
+
|
|
57
|
+
Slider value labels are editable only when they contain a numeric value. Textual state labels such as `Normal` are display-only and must not expose hover or click editing affordances.
|
|
58
|
+
|
|
53
59
|
Range sliders are always full-width two-thumb controls. Do not put a `rangeSlider` in an inline row. Its `defaultValue` must start with different lower and upper values, such as `[20, 80]`, so the control does not collapse into a single-value slider.
|
|
54
60
|
|
|
55
61
|
Range slider value editing accepts common range separators such as `20/80`, `20-80`, `20 - 80`, `20 80`, and en-dash ranges. Use the built-in parser instead of adding custom label parsing.
|
|
@@ -106,6 +112,8 @@ Section expand/collapse uses the standard runtime height/opacity animation. Do n
|
|
|
106
112
|
|
|
107
113
|
Ordinary 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 technical `Setup` / settings sections and sticky footer `Export` sections are not collapsible.
|
|
108
114
|
|
|
115
|
+
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`.
|
|
116
|
+
|
|
109
117
|
Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical `Setup` / settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.
|
|
110
118
|
|
|
111
119
|
## Colors
|
|
@@ -134,6 +142,8 @@ Use `fileDrop` for source material uploads in the controls panel. Do not place u
|
|
|
134
142
|
|
|
135
143
|
In single-layer apps, the runtime shows uploaded image preview and clear button in the file control. Clearing removes source material from the renderer and canvas.
|
|
136
144
|
|
|
145
|
+
Use `multiple: true` when the app needs several uploaded images as one source set. The runtime appends media, switches to a four-column thumbnail grid when more than one image is present, puts the add-more tile last, and keeps per-image removal inside the file control.
|
|
146
|
+
|
|
137
147
|
In multi-layer apps, deletion and visibility belong to the Layers panel; `fileDrop` stays an upload target.
|
|
138
148
|
|
|
139
149
|
## Image Picker
|
|
@@ -211,6 +221,8 @@ Short labels must still be semantically sufficient with nearby context. `Animati
|
|
|
211
221
|
|
|
212
222
|
Visible control labels can get a runtime-owned filled Phosphor question tooltip icon. Put a concise product-specific explanation in `description` only when it adds meaning beyond the label. Do not write recaps like `Adjusts Opacity`, and do not build custom help icons beside built-in labels.
|
|
213
223
|
|
|
224
|
+
Do not add `description` to obvious color clusters. If a section title already names the palette/color context, sequential labels such as `Color 1`, `Color 2`, or simple palette controls such as `Spread` do not need help icons. Keep the whole obvious group clean unless the tooltip explains a non-obvious product behavior.
|
|
225
|
+
|
|
214
226
|
For compound controls such as `fontPicker`, `description` must not enumerate owned fields like font, weight, size, case, color, opacity, letter spacing, or line height. The component already labels those fields.
|
|
215
227
|
|
|
216
228
|
If a source label is unavoidably long, keep the visible label concise and rely on native `title` for the full text.
|
|
@@ -267,10 +279,14 @@ Use schema `settingsTransfer` for settings import/export. Do not add Import Sett
|
|
|
267
279
|
|
|
268
280
|
Recalculate settings-transfer eligibility after adding, removing, or reorganizing controls, sections, timeline, or layers. The runtime threshold is 12 product controls, 5 product sections, or weighted score 18. If the threshold is reached, use `settingsTransfer: "auto"` / `true` or document a product-specific opt-out through `runtime.settingsTransfer` acceptance evidence.
|
|
269
281
|
|
|
270
|
-
When settings transfer and editable-output canvas sizing are both enabled, the first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`,
|
|
282
|
+
When settings transfer and editable-output canvas sizing are both enabled, the first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`, `Canvas height`, and optional `Resolution scale` in that order. Do not split these into separate app-authored sections, rename the controls, or rebuild the block by hand.
|
|
271
283
|
|
|
272
284
|
If only `Export Settings` and `Import Settings` appear in that section, the schema is not using `editable-output` canvas sizing or already owns `canvas.size.width` / `canvas.size.height` controls. For product-output apps, prefer fixing the canvas sizing decision over adding hand-built size fields.
|
|
273
285
|
|
|
286
|
+
Manual `Canvas width` or `Canvas height` edits are exact output-size edits. They keep the other dimension unchanged, switch `Aspect ratio` to `Custom`, and update the custom ratio inputs to the reduced current ratio. Do not recreate the old behavior where typing one size field stays locked to the previous aspect preset.
|
|
287
|
+
|
|
288
|
+
Enable `canvas.renderScale: true` for non-vector raster previews such as Canvas 2D, WebGL, or WebGPU output. Runtime adds a `Resolution scale` slider after canvas sizing; it defaults to `2x` and lets users trade preview quality/performance without changing output size. Adding or enabling this slider requires targeted browser evidence that the canvas stays responsive while dragging sliders or other high-frequency controls at the selected scale. Full `pnpm verify:perf` is required only for the first working app version or explicit performance complaints. Performance fixes must preserve the selected scale and keep canvas preview responsive. Diagnose the actual bottleneck before lowering quality; do not silently downsample, stretch a lower-resolution backing canvas, blur output, or clamp `canvas.renderScale` below the user's chosen value. Do not enable it for DOM/SVG/vector-native previews.
|
|
289
|
+
|
|
274
290
|
Reset belongs to the controls panel header reset button. Do not add a footer action with `label`, `value`, or `command` containing reset; acceptance treats that as a duplicate Reset.
|
|
275
291
|
|
|
276
292
|
Still-output product apps include one primary `Export PNG` action.
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Use a custom control only when no built-in Toolcraft control represents the product interaction.
|
|
4
4
|
|
|
5
|
-
Built-ins come first: `slider`, `rangeSlider`, `select`, `segmented`, `switch`, `checkbox`, `color`, `colorOpacity`, `vector`, `gradient`, `curves`, `fontPicker`, `imagePicker`, `fileDrop`, `text`, `code`, `rangeInput`, `palette`, `actions`, and `panelActions`.
|
|
5
|
+
Built-ins come first: `slider`, `rangeSlider`, `select`, `segmented`, `switch`, `checkbox`, `color`, `colorOpacity`, `vector`, `gradient`, `curves`, `fontPicker`, `imagePicker`, `fileDrop`, `text`, `code`, `rangeInput`, `palette`, `actions`, `collectionActions`, and `panelActions`.
|
|
6
6
|
|
|
7
7
|
Register custom renderers through `ToolcraftApp controlRenderers`.
|
|
8
8
|
|
|
9
|
-
Do not use `controlRenderers` to recreate a built-in control. If the product needs a slider, select, segmented mode picker, color input, gradient editor, font picker, upload, textarea, local action group, or footer action, declare the matching schema control instead of rendering the component manually.
|
|
9
|
+
Do not use `controlRenderers` to recreate a built-in control. If the product needs a slider, select, segmented mode picker, color input, gradient editor, font picker, upload, textarea, local action group, repeatable item add/remove, or footer action, declare the matching schema control instead of rendering the component manually.
|
|
10
10
|
|
|
11
11
|
Do not edit `ControlsPanel`, copied `src/toolcraft`, or Toolcraft internals inside a generated app.
|
|
12
12
|
|
|
@@ -55,6 +55,8 @@ Ordinary controls still need lightweight responsiveness checks. They should not
|
|
|
55
55
|
- panel scroll affecting canvas zoom;
|
|
56
56
|
- timeline or layer interactions destabilizing the viewport.
|
|
57
57
|
|
|
58
|
+
When `canvas.renderScale` / `Resolution scale` is enabled, responsiveness coverage must include slider or other high-frequency control drags at the selected scale. If the canvas lags, diagnose the source before changing quality: renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, stale async renders, or animation scheduling.
|
|
59
|
+
|
|
58
60
|
## Renderer Performance
|
|
59
61
|
|
|
60
62
|
Custom renderers should:
|
|
@@ -99,14 +101,13 @@ Animation checks should sample enough frames to catch jank. Interaction budgets
|
|
|
99
101
|
|
|
100
102
|
Use `app-performance.ts` as the single budget and fixture source. Browser performance tests must call `getToolcraftPerformanceStressValue(appPerformance, scenarioId)` for workload values and `expectToolcraftScenarioPerformanceBudget(..., appPerformance, scenarioId)` for budgets.
|
|
101
103
|
|
|
102
|
-
Run
|
|
104
|
+
Run targeted performance scenarios for Tier 3 performance-sensitive edits when they directly exercise the touched workload, viewport, or export path. `pnpm verify:perf` is the full performance suite; it runs `e2e/app-performance.spec.ts` and every `browser perf:` scenario with one worker so budget failures are not hidden or created by unrelated parallel browser tests.
|
|
103
105
|
|
|
104
|
-
Run a full performance checkpoint with `pnpm verify:perf` when:
|
|
106
|
+
Run a full performance checkpoint with `pnpm verify:perf` only when:
|
|
105
107
|
|
|
106
108
|
- the first working version of the app exists;
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
- a
|
|
110
|
-
- the user asks to optimize performance, fix lag, remove jank, speed up animation, or stabilize drag/zoom.
|
|
109
|
+
- the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance.
|
|
110
|
+
|
|
111
|
+
Performance fixes must preserve selected output and preview quality. Do not pass budgets by lowering image quality, selected `canvas.renderScale`, export resolution, source media fidelity, or canvas backing pixels unless the user explicitly chooses that lower-quality value through a visible control. Prefer coalescing slider updates, caching expensive inputs, moving work off the React render path, reusing GPU resources, or changing renderer strategy over reducing visual fidelity.
|
|
111
112
|
|
|
112
|
-
Do not use the full performance suite as the default loop for
|
|
113
|
+
Do not use the full performance suite as the default loop for feature work after the first working version. Those edits still need the targeted checks named by the verification tier, but they should not pay for every renderer and viewport stress test unless one of the two full-performance triggers applies. If a feature loop skips full performance, record the reason in the worklog.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Choose render technology per product layer. Do not choose a renderer because it is convenient; choose it from product output semantics, reference behavior, fidelity, and workload.
|
|
4
4
|
|
|
5
|
-
The initial renderer choice is provisional. It becomes accepted only after the app passes performance checks with the largest useful product canvas and the heaviest useful values for its own controls. If those checks show frame gaps, long tasks, viewport shaking, slow export, or interaction jank, revise the renderer strategy from that evidence before delivery.
|
|
5
|
+
The initial renderer choice is provisional. It becomes accepted only after the app passes performance checks with the largest useful product canvas and the heaviest useful values for its own controls. If those checks show frame gaps, long tasks, viewport shaking, slow export, or interaction jank, revise the renderer strategy from that evidence before delivery. Do not make a renderer look fast by silently reducing the selected preview scale, backing pixels, source media quality, or export fidelity.
|
|
6
6
|
|
|
7
7
|
## Strategy Guide
|
|
8
8
|
|
|
@@ -9,7 +9,7 @@ Edit `src/app/app-schema.ts` as the public product surface.
|
|
|
9
9
|
- Use `settingsTransfer: "auto"` for complex apps that should let users import/export control settings.
|
|
10
10
|
- Bind every control to a schema `target`.
|
|
11
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`;
|
|
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
13
|
- Use `disabled: true` only when the control is intentionally unavailable; the runtime renders the disabled visual and interaction state.
|
|
14
14
|
- Use `visibleWhen` when a control or section exists only for a specific template, type, mode, variant, or count. Hidden values are preserved. A section with no visible controls is hidden automatically.
|
|
15
15
|
- Use `disabledWhen` when a control belongs to the current entity but is temporarily unavailable in the selected state. The value is preserved while disabled.
|
|
@@ -134,10 +134,14 @@ Choose sizing from product context:
|
|
|
134
134
|
|
|
135
135
|
For product output, export, copy, download, shader rendering, procedural rendering, or no single intrinsic source image, use `editable-output` unless the product explicitly needs `fixed-output`.
|
|
136
136
|
|
|
137
|
-
A prompt-provided 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). Use `fixed-output` only when the reference or product explicitly locks dimensions, and add runtime acceptance with `canvasSizingCoverage: "fixed-output-size"`.
|
|
137
|
+
A prompt-provided 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. Use `fixed-output` only when the reference or product explicitly locks dimensions, and add runtime acceptance with `canvasSizingCoverage: "fixed-output-size"`.
|
|
138
138
|
|
|
139
139
|
Resolved `canvas.size` exists for every canvas app, but visible `Aspect ratio`, `Canvas width`, and `Canvas height` controls are mandatory only for `editable-output` sizing. They do not depend on `settingsTransfer`: when settings transfer is off, the runtime prepends a technical `Setup` canvas size section without a visible heading; when settings transfer is on, the controls merge into the first technical `Setup` runtime settings section without a visible heading. Do not hand-build a duplicate size selector.
|
|
140
140
|
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
For non-vector raster, Canvas 2D, WebGL, or WebGPU previews, set `canvas.renderScale: true`. Runtime appends `Resolution scale` after canvas sizing in the first technical `Setup` section. The slider ranges from `1x` to `2x`, defaults to `2x`, 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. Full `pnpm verify:perf` is required only for the first working app version or explicit performance complaints. 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.
|
|
144
|
+
|
|
141
145
|
## Panels
|
|
142
146
|
|
|
143
147
|
- Use `panels: {}` for the neutral starter or for products that have no user-facing panels yet.
|
|
@@ -157,11 +161,12 @@ Use built-ins before custom controls. Unknown `type` values render nothing unles
|
|
|
157
161
|
| `anchorGrid` | Anchor picker | `defaultValue`, `target` |
|
|
158
162
|
| `channelMixer` | RGB-only channel matrix mixer with R/G/B tabs and Red/Green/Blue source sliders | `defaultValue`, `target`, `label` |
|
|
159
163
|
| `checkbox` | Checkbox field | `defaultValue`, `target`, `label` |
|
|
164
|
+
| `collectionActions` | Add/remove buttons for repeatable product entities and their runtime-backed item controls | `defaultValue: []`, `itemControl`, `itemDefaultValue`, `itemLabel`, `minItems`, `recommendedMaxItems`, `hardMaxItems`, `target` |
|
|
160
165
|
| `code` | Multiline textarea | `defaultValue`, `target`, `label` |
|
|
161
166
|
| `color` | Hex color picker | `defaultValue: { hex }`, `target`, `label` |
|
|
162
167
|
| `colorOpacity` | Hex color picker plus opacity percent input | `defaultValue: { hex, opacity }`, `target`, `label` |
|
|
163
168
|
| `curves` | RGB or single curve editor | `defaultValue`, `target`, `variant: "single"`, `interpolation: "smooth" \| "monotone"` |
|
|
164
|
-
| `fileDrop` | Upload/drop input | `accept`, `target` |
|
|
169
|
+
| `fileDrop` | Upload/drop input | `accept`, `multiple`, `target` |
|
|
165
170
|
| `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` | `defaultValue: { fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, opacity }`, `target` |
|
|
166
171
|
| `gradient` | Gradient editor | `defaultValue: { angle, gradientType, stops }`, `target` |
|
|
167
172
|
| `imagePicker` | Image choice grid | `items`, `defaultValue`, `target` |
|
|
@@ -178,6 +183,10 @@ Use built-ins before custom controls. Unknown `type` values render nothing unles
|
|
|
178
183
|
|
|
179
184
|
`text` defaults to `commitMode: "content"` and applies while typing for real content such as prompts, names, titles, tokens, and short 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, applies while typing, and is capped at 12 visible lines. Long content scrolls inside the textarea instead of making the controls panel taller.
|
|
180
185
|
|
|
186
|
+
For `slider` and `rangeSlider`, `unit` is a measurement or scale suffix, not the entity being counted. Use units such as `%`, `px`, `°`, `x`, `s`, `ms`, `fps`, `rows`, or `cols` only when they clarify the number. 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`).
|
|
187
|
+
|
|
188
|
+
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`, and `rangeInput`; item controls still follow normal density rules, so plain colors use equal 50% columns when they fit.
|
|
189
|
+
|
|
181
190
|
## Control Selection Inventory
|
|
182
191
|
|
|
183
192
|
Before writing schema controls, map product needs to built-ins by value model, not visual similarity.
|
|
@@ -229,6 +238,8 @@ Section expand/collapse uses the standard runtime height/opacity animation. Do n
|
|
|
229
238
|
|
|
230
239
|
Ordinary 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 technical `Setup` / settings sections and sticky footer `Export` sections are not collapsible.
|
|
231
240
|
|
|
241
|
+
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`.
|
|
242
|
+
|
|
232
243
|
Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical `Setup` / settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.
|
|
233
244
|
|
|
234
245
|
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 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.
|
|
@@ -296,7 +307,7 @@ When enabled, the runtime inserts a technical `Setup` settings-transfer section
|
|
|
296
307
|
|
|
297
308
|
After adding, removing, or reorganizing controls, sections, timeline, or layers, recalculate settings-transfer eligibility. If the threshold is reached, use `"auto"` / `true` or add an explicit `runtime.settingsTransfer` opt-out acceptance row with product evidence.
|
|
298
309
|
|
|
299
|
-
When settings transfer is enabled and the canvas uses `editable-output` sizing, the first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`,
|
|
310
|
+
When settings transfer is enabled and the canvas uses `editable-output` sizing, the first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Aspect ratio`, `Canvas width`, `Canvas height`, and optional `Resolution scale` in that order. Do not split these into separate sections or recreate them manually.
|
|
300
311
|
|
|
301
312
|
A settings-transfer section with only `Export Settings` and `Import Settings` means the canvas is not `editable-output` or the app already declares its own `canvas.size.width` / `canvas.size.height` controls. For product-output apps, treat that as a schema decision to review.
|
|
302
313
|
|
|
@@ -73,15 +73,12 @@ Choose the tier from `AGENTS.md` before editing. Use the tier to decide checks.
|
|
|
73
73
|
|
|
74
74
|
- Tier 0-1: targeted docs/typecheck/unit plus focused browser when visual.
|
|
75
75
|
- Tier 2: `pnpm verify:quick` plus relevant browser acceptance.
|
|
76
|
-
- Tier 3: `pnpm verify:quick`, targeted browser acceptance, and
|
|
77
|
-
- Tier 4: `pnpm verify:final
|
|
76
|
+
- Tier 3: `pnpm verify:quick`, targeted browser acceptance, and targeted performance scenarios only for touched workload/viewport/export paths.
|
|
77
|
+
- Tier 4: `pnpm verify:final`; add `pnpm verify:perf` only for the first working app version or explicit performance complaints, then start `pnpm dev` for the local URL.
|
|
78
78
|
|
|
79
|
-
Run a full performance checkpoint with `pnpm verify:perf` when:
|
|
79
|
+
Run a full performance checkpoint with `pnpm verify:perf` only when:
|
|
80
80
|
|
|
81
81
|
- the first working version of the app exists;
|
|
82
|
-
-
|
|
83
|
-
- a bug that previously broke functionality is fixed;
|
|
84
|
-
- a performance optimization lands;
|
|
85
|
-
- the user asks to optimize performance, fix lag, remove jank, speed up animation, or stabilize drag/zoom.
|
|
82
|
+
- the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance.
|
|
86
83
|
|
|
87
|
-
Fast feature loops
|
|
84
|
+
Fast feature loops after the first working version do not run the full performance suite by default. Renderer, canvas, animation, export, timeline, layers, `canvas.renderScale`, bug fixes, and performance-sensitive controls still need targeted functional/browser checks first, plus targeted performance scenarios only when they directly exercise the touched path. Record any skipped full performance run and reason in the worklog.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist
|
|
6
|
+
dist-ssr
|
|
7
|
+
.vite
|
|
8
|
+
.turbo
|
|
9
|
+
.output
|
|
10
|
+
.next
|
|
11
|
+
*.tsbuildinfo
|
|
12
|
+
|
|
13
|
+
# Test output
|
|
14
|
+
coverage
|
|
15
|
+
test-results
|
|
16
|
+
playwright-report
|
|
17
|
+
|
|
18
|
+
# Local environment
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
!.env.example
|
|
22
|
+
|
|
23
|
+
# Logs
|
|
24
|
+
logs
|
|
25
|
+
*.log
|
|
26
|
+
npm-debug.log*
|
|
27
|
+
pnpm-debug.log*
|
|
28
|
+
yarn-debug.log*
|
|
29
|
+
|
|
30
|
+
# OS and editor files
|
|
31
|
+
.DS_Store
|
|
32
|
+
Thumbs.db
|
|
33
|
+
.idea
|
|
34
|
+
.vscode/*
|
|
35
|
+
!.vscode/extensions.json
|
|
36
|
+
!.vscode/settings.json
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"verify:quick": "pnpm ai:check && pnpm test",
|
|
17
17
|
"verify:ui": "pnpm test:browser",
|
|
18
18
|
"verify:perf": "pnpm test:browser:perf",
|
|
19
|
-
"verify:final": "pnpm ai:check && pnpm test && pnpm build && pnpm test:browser
|
|
19
|
+
"verify:final": "pnpm ai:check && pnpm test && pnpm build && pnpm test:browser"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@base-ui/react": "^1.4.1",
|
|
@@ -5997,6 +5997,61 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
5997
5997
|
);
|
|
5998
5998
|
});
|
|
5999
5999
|
|
|
6000
|
+
it("rejects redundant descriptions in obvious color sections", () => {
|
|
6001
|
+
const schemaWithObviousColorHelp = defineToolcraft({
|
|
6002
|
+
canvas: { enabled: true },
|
|
6003
|
+
panels: {
|
|
6004
|
+
controls: {
|
|
6005
|
+
sections: [
|
|
6006
|
+
{
|
|
6007
|
+
controls: {
|
|
6008
|
+
color1: {
|
|
6009
|
+
defaultValue: { hex: "#DFFF1A" },
|
|
6010
|
+
description: "Sets the first bead color.",
|
|
6011
|
+
label: "Color 1",
|
|
6012
|
+
target: "beads.color1",
|
|
6013
|
+
type: "color",
|
|
6014
|
+
},
|
|
6015
|
+
color2: {
|
|
6016
|
+
defaultValue: { hex: "#8CFF3A" },
|
|
6017
|
+
description: "Sets the second bead color.",
|
|
6018
|
+
label: "Color 2",
|
|
6019
|
+
target: "beads.color2",
|
|
6020
|
+
type: "color",
|
|
6021
|
+
},
|
|
6022
|
+
colorSpread: {
|
|
6023
|
+
defaultValue: 34,
|
|
6024
|
+
description:
|
|
6025
|
+
"Controls how often beads use colors 2-5 instead of Color 1.",
|
|
6026
|
+
label: "Spread",
|
|
6027
|
+
max: 100,
|
|
6028
|
+
min: 0,
|
|
6029
|
+
target: "beads.colorSpread",
|
|
6030
|
+
type: "slider",
|
|
6031
|
+
unit: "%",
|
|
6032
|
+
},
|
|
6033
|
+
},
|
|
6034
|
+
title: "Bead Colors",
|
|
6035
|
+
},
|
|
6036
|
+
],
|
|
6037
|
+
title: "Controls",
|
|
6038
|
+
},
|
|
6039
|
+
},
|
|
6040
|
+
});
|
|
6041
|
+
|
|
6042
|
+
expect(validateToolcraftAcceptanceCoverage(schemaWithObviousColorHelp, [
|
|
6043
|
+
makeControlAcceptance("beads.color1", "color"),
|
|
6044
|
+
makeControlAcceptance("beads.color2", "color"),
|
|
6045
|
+
makeControlAcceptance("beads.colorSpread", "slider"),
|
|
6046
|
+
])).toEqual(
|
|
6047
|
+
expect.arrayContaining([
|
|
6048
|
+
"Bead Colors / color1 description adds a help icon to an obvious color-section control. Omit control.description when the section title and visible label already explain the setting.",
|
|
6049
|
+
"Bead Colors / color2 description adds a help icon to an obvious color-section control. Omit control.description when the section title and visible label already explain the setting.",
|
|
6050
|
+
"Bead Colors / colorSpread description adds a help icon to an obvious color-section control. Omit control.description when the section title and visible label already explain the setting.",
|
|
6051
|
+
]),
|
|
6052
|
+
);
|
|
6053
|
+
});
|
|
6054
|
+
|
|
6000
6055
|
it("requires mode selectors to appear before dependent controls", () => {
|
|
6001
6056
|
const schemaWithLateModeSelector = {
|
|
6002
6057
|
...starterSchema,
|
|
@@ -1561,14 +1561,33 @@ function getToolcraftControlDescriptionError({
|
|
|
1561
1561
|
control,
|
|
1562
1562
|
controlId,
|
|
1563
1563
|
sectionLabel,
|
|
1564
|
+
sectionTitle,
|
|
1564
1565
|
}: {
|
|
1565
1566
|
control: ToolcraftControlSchema;
|
|
1566
1567
|
controlId: string;
|
|
1567
1568
|
sectionLabel: string;
|
|
1569
|
+
sectionTitle: string | undefined;
|
|
1568
1570
|
}): string | undefined {
|
|
1569
1571
|
const description = control.description?.trim();
|
|
1570
1572
|
|
|
1571
|
-
if (!description
|
|
1573
|
+
if (!description) {
|
|
1574
|
+
return undefined;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const label = getControlLabelText(control).trim();
|
|
1578
|
+
|
|
1579
|
+
if (
|
|
1580
|
+
isToolcraftObviousColorSectionControlDescription({
|
|
1581
|
+
control,
|
|
1582
|
+
description,
|
|
1583
|
+
label,
|
|
1584
|
+
sectionTitle,
|
|
1585
|
+
})
|
|
1586
|
+
) {
|
|
1587
|
+
return `${sectionLabel} / ${controlId} description adds a help icon to an obvious color-section control. Omit control.description when the section title and visible label already explain the setting.`;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (control.type !== "fontPicker") {
|
|
1572
1591
|
return undefined;
|
|
1573
1592
|
}
|
|
1574
1593
|
|
|
@@ -1583,6 +1602,52 @@ function getToolcraftControlDescriptionError({
|
|
|
1583
1602
|
return `${sectionLabel} / ${controlId} description repeats FontPicker-owned fields (${repeatedParts.join(", ")}). FontPicker help must explain only non-obvious product behavior; use section titles and visible field labels for font family, weight, size, case, color, opacity, letter spacing, and line height, or omit description.`;
|
|
1584
1603
|
}
|
|
1585
1604
|
|
|
1605
|
+
function isToolcraftColorSectionTitle(sectionTitle: string | undefined): boolean {
|
|
1606
|
+
return /\b(colou?rs?|palette|palettes)\b/i.test(sectionTitle ?? "");
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function isToolcraftSequentialColorLabel(label: string): boolean {
|
|
1610
|
+
return /^colou?r\s+\d+$/i.test(label.trim());
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function isToolcraftSimplePaletteDistributionLabel(label: string): boolean {
|
|
1614
|
+
return /^(spread|mix|distribution)$/i.test(label.trim());
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function isToolcraftGenericControlHelpDescription(description: string): boolean {
|
|
1618
|
+
return /^(adjusts?|changes?|chooses?|controls?|defines?|selects?|sets?|updates?)\b/i.test(
|
|
1619
|
+
description.trim(),
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
function isToolcraftObviousColorSectionControlDescription({
|
|
1624
|
+
control,
|
|
1625
|
+
description,
|
|
1626
|
+
label,
|
|
1627
|
+
sectionTitle,
|
|
1628
|
+
}: {
|
|
1629
|
+
control: ToolcraftControlSchema;
|
|
1630
|
+
description: string;
|
|
1631
|
+
label: string;
|
|
1632
|
+
sectionTitle: string | undefined;
|
|
1633
|
+
}): boolean {
|
|
1634
|
+
if (!isToolcraftColorSectionTitle(sectionTitle)) {
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
if (
|
|
1639
|
+
(control.type === "color" || control.type === "colorOpacity") &&
|
|
1640
|
+
isToolcraftSequentialColorLabel(label)
|
|
1641
|
+
) {
|
|
1642
|
+
return true;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
return (
|
|
1646
|
+
isToolcraftSimplePaletteDistributionLabel(label) &&
|
|
1647
|
+
isToolcraftGenericControlHelpDescription(description)
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1586
1651
|
function getToolcraftControlSectionGroupingErrors(
|
|
1587
1652
|
schema: ResolvedToolcraftAppSchema,
|
|
1588
1653
|
): string[] {
|
|
@@ -1691,6 +1756,7 @@ function getToolcraftControlSectionGroupingErrors(
|
|
|
1691
1756
|
control,
|
|
1692
1757
|
controlId,
|
|
1693
1758
|
sectionLabel,
|
|
1759
|
+
sectionTitle,
|
|
1694
1760
|
});
|
|
1695
1761
|
|
|
1696
1762
|
if (descriptionError) {
|
|
@@ -299,7 +299,7 @@ describe("Toolcraft template app performance coverage", () => {
|
|
|
299
299
|
expect(packageJson.scripts?.["verify:ui"]).toBe("pnpm test:browser");
|
|
300
300
|
expect(packageJson.scripts?.["verify:perf"]).toBe("pnpm test:browser:perf");
|
|
301
301
|
expect(packageJson.scripts?.["verify:final"]).toBe(
|
|
302
|
-
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser
|
|
302
|
+
"pnpm ai:check && pnpm test && pnpm build && pnpm test:browser",
|
|
303
303
|
);
|
|
304
304
|
});
|
|
305
305
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { MinusIcon, PlusIcon } from "@phosphor-icons/react";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { ControlFieldLabel } from "../../control-layout";
|
|
7
|
+
import { Button, Field } from "../../primitives";
|
|
8
|
+
|
|
9
|
+
export type CollectionActionsControlProps = {
|
|
10
|
+
addLabel?: string;
|
|
11
|
+
canAdd?: boolean;
|
|
12
|
+
canRemove?: boolean;
|
|
13
|
+
name: string;
|
|
14
|
+
onAdd?: () => void;
|
|
15
|
+
onRemove?: () => void;
|
|
16
|
+
removeLabel?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function CollectionActionsControl({
|
|
20
|
+
addLabel = "Add item",
|
|
21
|
+
canAdd = true,
|
|
22
|
+
canRemove = true,
|
|
23
|
+
name,
|
|
24
|
+
onAdd,
|
|
25
|
+
onRemove,
|
|
26
|
+
removeLabel = "Remove item",
|
|
27
|
+
}: CollectionActionsControlProps): React.JSX.Element {
|
|
28
|
+
return (
|
|
29
|
+
<Field
|
|
30
|
+
aria-label={name}
|
|
31
|
+
className="min-w-0 items-center justify-between gap-2"
|
|
32
|
+
data-slot="collection-actions-control-header"
|
|
33
|
+
orientation="horizontal"
|
|
34
|
+
>
|
|
35
|
+
<ControlFieldLabel className="flex-1">{name}</ControlFieldLabel>
|
|
36
|
+
<div className="inline-flex shrink-0 items-center gap-1">
|
|
37
|
+
<Button
|
|
38
|
+
aria-label={removeLabel}
|
|
39
|
+
disabled={!canRemove}
|
|
40
|
+
onClick={onRemove}
|
|
41
|
+
size="icon-sm"
|
|
42
|
+
type="button"
|
|
43
|
+
variant="outline"
|
|
44
|
+
>
|
|
45
|
+
<MinusIcon />
|
|
46
|
+
</Button>
|
|
47
|
+
<Button
|
|
48
|
+
aria-label={addLabel}
|
|
49
|
+
disabled={!canAdd}
|
|
50
|
+
onClick={onAdd}
|
|
51
|
+
size="icon-sm"
|
|
52
|
+
type="button"
|
|
53
|
+
variant="outline"
|
|
54
|
+
>
|
|
55
|
+
<PlusIcon />
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
</Field>
|
|
59
|
+
);
|
|
60
|
+
}
|