@pixel-point/toolcraft 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +14 -6
- package/src/generate.test.mjs +28 -0
- package/templates/runtime/contracts/component-contracts.test.ts +148 -34
- package/templates/runtime/contracts/component-contracts.ts +79 -38
- package/templates/runtime/contracts/decision-contracts.test.ts +33 -1
- package/templates/runtime/contracts/decision-contracts.ts +19 -6
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +474 -27
- package/templates/runtime/react/controls-panel.tsx +71 -26
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +225 -173
- package/templates/runtime/schema/define-toolcraft.ts +117 -247
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +41 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +657 -2
- package/templates/runtime/testing/performance.ts +789 -49
- package/templates/starter/AGENTS.md +22 -16
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +38 -7
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +51 -20
- package/templates/starter/docs/toolcraft/component-rules.md +49 -37
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +30 -10
- package/templates/starter/docs/toolcraft/schema-reference.md +134 -33
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +82 -16
- package/templates/starter/scripts/toolcraft-port.mjs +178 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +147 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +2573 -313
- package/templates/starter/src/app/starter-acceptance.ts +978 -81
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/controls/actions/actions-control.tsx +46 -3
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +2 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +4 -26
- package/templates/ui/components/controls/vector/vector-control.tsx +25 -4
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
collectToolcraftPerformanceSensitiveControls,
|
|
6
6
|
collectToolcraftUnclassifiedPerformanceControls,
|
|
7
7
|
defineToolcraftPerformance,
|
|
8
|
+
defaultToolcraftBrowserCheckPolicy,
|
|
9
|
+
type ToolcraftBrowserCheckPolicy,
|
|
8
10
|
validateToolcraftPerformanceCoverage,
|
|
9
11
|
} from "./performance";
|
|
10
12
|
|
|
@@ -180,6 +182,14 @@ function createViewportZoomStressScenario() {
|
|
|
180
182
|
function createMaxValueStressFixture(value: unknown, reason = "Maximum workload value.") {
|
|
181
183
|
return {
|
|
182
184
|
kind: "max-value" as const,
|
|
185
|
+
loadProfile: {
|
|
186
|
+
hardLimit: value,
|
|
187
|
+
metric: "numeric-max" as const,
|
|
188
|
+
smoothTarget: value,
|
|
189
|
+
smoothTargetRatio: 1,
|
|
190
|
+
target: "render.density",
|
|
191
|
+
userFacingRange: "fully-guaranteed" as const,
|
|
192
|
+
},
|
|
183
193
|
reason,
|
|
184
194
|
value,
|
|
185
195
|
};
|
|
@@ -191,6 +201,14 @@ function createCustomStressFixture(
|
|
|
191
201
|
) {
|
|
192
202
|
return {
|
|
193
203
|
kind: "custom" as const,
|
|
204
|
+
loadProfile: {
|
|
205
|
+
hardLimit: value,
|
|
206
|
+
metric: "custom" as const,
|
|
207
|
+
smoothTarget: value,
|
|
208
|
+
smoothTargetRatio: 1,
|
|
209
|
+
target: "renderer.output",
|
|
210
|
+
userFacingRange: "fully-guaranteed" as const,
|
|
211
|
+
},
|
|
194
212
|
reason,
|
|
195
213
|
value,
|
|
196
214
|
};
|
|
@@ -227,11 +245,60 @@ function createMediaStressFixture(
|
|
|
227
245
|
) {
|
|
228
246
|
return {
|
|
229
247
|
kind: "media" as const,
|
|
248
|
+
loadProfile: {
|
|
249
|
+
hardLimit: value,
|
|
250
|
+
metric: "media-area" as const,
|
|
251
|
+
smoothTarget: value,
|
|
252
|
+
smoothTargetRatio: 1,
|
|
253
|
+
target: "source.image",
|
|
254
|
+
userFacingRange: "fully-guaranteed" as const,
|
|
255
|
+
},
|
|
230
256
|
reason,
|
|
231
257
|
value,
|
|
232
258
|
};
|
|
233
259
|
}
|
|
234
260
|
|
|
261
|
+
function createDegradedMaxValueStressFixture() {
|
|
262
|
+
return {
|
|
263
|
+
kind: "max-value" as const,
|
|
264
|
+
loadProfile: {
|
|
265
|
+
degradationStepPercent: 10 as const,
|
|
266
|
+
evidence: [
|
|
267
|
+
{
|
|
268
|
+
attemptedTarget: 12,
|
|
269
|
+
decision: "Keep 12 available as high density while guaranteeing smooth drag through 11.",
|
|
270
|
+
measuredResult: "At density 12, maxFrameGapMs 148 exceeded the 80ms budget.",
|
|
271
|
+
optimizationAttempted: "Cached glyph atlas and coalesced preview work to requestAnimationFrame.",
|
|
272
|
+
result: "failed" as const,
|
|
273
|
+
scenarioId: "density-drag",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
hardLimit: 12,
|
|
277
|
+
metric: "numeric-max" as const,
|
|
278
|
+
smoothTarget: 11,
|
|
279
|
+
smoothTargetRatio: 0.9,
|
|
280
|
+
target: "render.density",
|
|
281
|
+
userFacingRange: "experimental-above-smooth" as const,
|
|
282
|
+
},
|
|
283
|
+
reason: "Density 11 is the measured smooth target after hard-limit testing.",
|
|
284
|
+
value: 11,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function createMeasuredWebglEvidence(scenarioId = "text-preview-render") {
|
|
289
|
+
return [
|
|
290
|
+
{
|
|
291
|
+
alternativeStrategy: "webgl" as const,
|
|
292
|
+
decision:
|
|
293
|
+
"Canvas 2D remains selected because cached CPU rendering stayed within budget for this product fixture while preserving export parity.",
|
|
294
|
+
fixture: "large product stress fixture at render scale 2",
|
|
295
|
+
measuredResult:
|
|
296
|
+
"WebGL comparison: maxFrameGapMs 72, maxLongTaskMs 64; Canvas 2D comparison: maxFrameGapMs 68, maxLongTaskMs 58.",
|
|
297
|
+
scenarioId,
|
|
298
|
+
},
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
|
|
235
302
|
function createTextRendererPipeline() {
|
|
236
303
|
return {
|
|
237
304
|
interactionInvalidation: [
|
|
@@ -395,6 +462,61 @@ function createDenseCanvasPipeline() {
|
|
|
395
462
|
} as const;
|
|
396
463
|
}
|
|
397
464
|
|
|
465
|
+
function createMainThreadCanvasCompositePipeline() {
|
|
466
|
+
return {
|
|
467
|
+
interactionInvalidation: [
|
|
468
|
+
{
|
|
469
|
+
interaction: "control-drag",
|
|
470
|
+
invalidates: ["text-texture", "warp-composite", "optical-composite"],
|
|
471
|
+
targets: ["render.density"],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
interaction: "control-change",
|
|
475
|
+
invalidates: ["text-texture", "warp-composite", "optical-composite"],
|
|
476
|
+
targets: ["render.mode"],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
interaction: "viewport-zoom",
|
|
480
|
+
invalidates: [],
|
|
481
|
+
mustNotInvalidate: ["text-texture", "warp-composite", "optical-composite"],
|
|
482
|
+
targets: ["canvas.viewport"],
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
passes: [
|
|
486
|
+
{
|
|
487
|
+
cacheKey: ["product.content", "render.density", "canvas.size", "canvas.renderScale"],
|
|
488
|
+
id: "text-texture",
|
|
489
|
+
inputs: ["product.content", "render.density", "canvas.size", "canvas.renderScale"],
|
|
490
|
+
invalidatedBy: ["product.content", "render.density", "canvas.size", "canvas.renderScale"],
|
|
491
|
+
kind: "rasterize",
|
|
492
|
+
output: "intermediate",
|
|
493
|
+
quality: "retina",
|
|
494
|
+
runsOn: "main",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
cacheKey: ["text-texture", "render.density", "canvas.size"],
|
|
498
|
+
id: "warp-composite",
|
|
499
|
+
inputs: ["text-texture", "render.density", "canvas.size"],
|
|
500
|
+
invalidatedBy: ["text-texture", "render.density", "canvas.size"],
|
|
501
|
+
kind: "composite",
|
|
502
|
+
output: "preview",
|
|
503
|
+
quality: "retina",
|
|
504
|
+
runsOn: "main",
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
cacheKey: ["warp-composite", "render.mode", "canvas.renderScale"],
|
|
508
|
+
id: "optical-composite",
|
|
509
|
+
inputs: ["warp-composite", "render.mode", "canvas.renderScale"],
|
|
510
|
+
invalidatedBy: ["warp-composite", "render.mode", "canvas.renderScale"],
|
|
511
|
+
kind: "composite",
|
|
512
|
+
output: "preview",
|
|
513
|
+
quality: "retina",
|
|
514
|
+
runsOn: "main",
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
} as const;
|
|
518
|
+
}
|
|
519
|
+
|
|
398
520
|
function createPixelRendererPipeline() {
|
|
399
521
|
return {
|
|
400
522
|
interactionInvalidation: [
|
|
@@ -628,6 +750,71 @@ function createAnimatedVectorRendererConfig(options: { includeViewportDrag: bool
|
|
|
628
750
|
}
|
|
629
751
|
|
|
630
752
|
describe("Toolcraft template performance contract", () => {
|
|
753
|
+
it("defaults browser checks to agent browser with Playwright fallback", () => {
|
|
754
|
+
const config = defineToolcraftPerformance({
|
|
755
|
+
rendererStrategy: "none",
|
|
756
|
+
rendererWorkload: "none",
|
|
757
|
+
scenarios: [
|
|
758
|
+
{
|
|
759
|
+
automated: true,
|
|
760
|
+
automatedTestName: "perf: opacity drag stays responsive",
|
|
761
|
+
browser: true,
|
|
762
|
+
browserTestName: "browser perf: opacity drag stays responsive",
|
|
763
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
764
|
+
controlLabel: "Opacity",
|
|
765
|
+
expectedObservable: "Dragging Opacity stays responsive.",
|
|
766
|
+
fixture: "opacity responsiveness fixture",
|
|
767
|
+
id: "opacity-drag",
|
|
768
|
+
interaction: "control-drag",
|
|
769
|
+
target: "render.opacity",
|
|
770
|
+
workload: false,
|
|
771
|
+
},
|
|
772
|
+
],
|
|
773
|
+
usesCustomRenderer: false,
|
|
774
|
+
workloadTargets: [],
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
expect(config.browserCheckPolicy).toEqual(defaultToolcraftBrowserCheckPolicy);
|
|
778
|
+
expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toEqual([]);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("rejects explicit Playwright-first browser check policies", () => {
|
|
782
|
+
const config = defineToolcraftPerformance({
|
|
783
|
+
browserCheckPolicy: {
|
|
784
|
+
fallbackRunner: "playwright",
|
|
785
|
+
fallbackWhen: ["agent-browser-unavailable"],
|
|
786
|
+
preferredRunner: "playwright",
|
|
787
|
+
} as unknown as ToolcraftBrowserCheckPolicy,
|
|
788
|
+
rendererStrategy: "none",
|
|
789
|
+
rendererWorkload: "none",
|
|
790
|
+
scenarios: [
|
|
791
|
+
{
|
|
792
|
+
automated: true,
|
|
793
|
+
automatedTestName: "perf: opacity drag stays responsive",
|
|
794
|
+
browser: true,
|
|
795
|
+
browserTestName: "browser perf: opacity drag stays responsive",
|
|
796
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
797
|
+
controlLabel: "Opacity",
|
|
798
|
+
expectedObservable: "Dragging Opacity stays responsive.",
|
|
799
|
+
fixture: "opacity responsiveness fixture",
|
|
800
|
+
id: "opacity-drag",
|
|
801
|
+
interaction: "control-drag",
|
|
802
|
+
target: "render.opacity",
|
|
803
|
+
workload: false,
|
|
804
|
+
},
|
|
805
|
+
],
|
|
806
|
+
usesCustomRenderer: false,
|
|
807
|
+
workloadTargets: [],
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toEqual(
|
|
811
|
+
expect.arrayContaining([
|
|
812
|
+
'browserCheckPolicy.preferredRunner must be "agent-browser" so AI agents use their controlled browser before fallback automation.',
|
|
813
|
+
'browserCheckPolicy.fallbackWhen must include "ci" so CI/non-agent automation has a portable performance runner.',
|
|
814
|
+
]),
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
|
|
631
818
|
it("collects performance-sensitive controls from schema", () => {
|
|
632
819
|
expect(collectToolcraftPerformanceSensitiveControls(testSchema)).toEqual([
|
|
633
820
|
expect.objectContaining({
|
|
@@ -682,6 +869,7 @@ describe("Toolcraft template performance contract", () => {
|
|
|
682
869
|
const unclassifiedSchema = defineToolcraft({
|
|
683
870
|
canvas: {
|
|
684
871
|
enabled: true,
|
|
872
|
+
renderScale: true,
|
|
685
873
|
sizing: { mode: "intrinsic-media" },
|
|
686
874
|
},
|
|
687
875
|
panels: {
|
|
@@ -704,6 +892,7 @@ describe("Toolcraft template performance contract", () => {
|
|
|
704
892
|
],
|
|
705
893
|
title: "Runtime Controls",
|
|
706
894
|
},
|
|
895
|
+
timeline: { mode: "playback" },
|
|
707
896
|
},
|
|
708
897
|
});
|
|
709
898
|
|
|
@@ -1195,6 +1384,81 @@ describe("Toolcraft template performance contract", () => {
|
|
|
1195
1384
|
);
|
|
1196
1385
|
});
|
|
1197
1386
|
|
|
1387
|
+
it("rejects short large-text values hidden inside custom fixtures", () => {
|
|
1388
|
+
const config = defineToolcraftPerformance({
|
|
1389
|
+
rendererStrategy: "none",
|
|
1390
|
+
rendererWorkload: "none",
|
|
1391
|
+
scenarios: [
|
|
1392
|
+
{
|
|
1393
|
+
automated: true,
|
|
1394
|
+
automatedTestName: "perf: preview render stays under budget",
|
|
1395
|
+
browser: true,
|
|
1396
|
+
browserTestName: "browser perf: preview render stays under budget",
|
|
1397
|
+
budget: { maxLongTaskMs: 80, maxPreviewMs: 1000 },
|
|
1398
|
+
expectedObservable: "Preview renders the heaviest output without freezing.",
|
|
1399
|
+
fixture: "combined large text fixture",
|
|
1400
|
+
id: "preview-render",
|
|
1401
|
+
interaction: "preview-render",
|
|
1402
|
+
stress: true,
|
|
1403
|
+
stressFixture: {
|
|
1404
|
+
kind: "custom",
|
|
1405
|
+
reason: "Combined renderer stress includes pasted product content.",
|
|
1406
|
+
value: {
|
|
1407
|
+
content: "short text",
|
|
1408
|
+
renderScale: 2,
|
|
1409
|
+
},
|
|
1410
|
+
},
|
|
1411
|
+
workload: false,
|
|
1412
|
+
},
|
|
1413
|
+
],
|
|
1414
|
+
usesCustomRenderer: false,
|
|
1415
|
+
workloadTargets: [],
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual(
|
|
1419
|
+
expect.arrayContaining([
|
|
1420
|
+
"preview-render custom stressFixture.content must contain at least 50000 characters.",
|
|
1421
|
+
"preview-render custom stressFixture.content must contain at least 1000 lines.",
|
|
1422
|
+
]),
|
|
1423
|
+
);
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
it("rejects custom render scale fixtures that are not concrete high-resolution values", () => {
|
|
1427
|
+
const config = defineToolcraftPerformance({
|
|
1428
|
+
rendererStrategy: "none",
|
|
1429
|
+
rendererWorkload: "none",
|
|
1430
|
+
scenarios: [
|
|
1431
|
+
{
|
|
1432
|
+
automated: true,
|
|
1433
|
+
automatedTestName: "perf: preview render stays under budget",
|
|
1434
|
+
browser: true,
|
|
1435
|
+
browserTestName: "browser perf: preview render stays under budget",
|
|
1436
|
+
budget: { maxLongTaskMs: 80, maxPreviewMs: 1000 },
|
|
1437
|
+
expectedObservable: "Preview renders the heaviest output without freezing.",
|
|
1438
|
+
fixture: "combined render scale fixture",
|
|
1439
|
+
id: "preview-render",
|
|
1440
|
+
interaction: "preview-render",
|
|
1441
|
+
stress: true,
|
|
1442
|
+
stressFixture: {
|
|
1443
|
+
kind: "custom",
|
|
1444
|
+
reason: "Combined renderer stress includes selected resolution scale.",
|
|
1445
|
+
value: {
|
|
1446
|
+
density: 12,
|
|
1447
|
+
renderScale: "2",
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
workload: false,
|
|
1451
|
+
},
|
|
1452
|
+
],
|
|
1453
|
+
usesCustomRenderer: false,
|
|
1454
|
+
workloadTargets: [],
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toContain(
|
|
1458
|
+
"preview-render custom stressFixture.renderScale must be a numeric Resolution scale greater than 1 so browser tests prove high-resolution backing pixels.",
|
|
1459
|
+
);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1198
1462
|
it("accepts large text workload scenarios with machine-checkable stress fixtures", () => {
|
|
1199
1463
|
const config = defineToolcraftPerformance({
|
|
1200
1464
|
rendererStrategy: "none",
|
|
@@ -1224,6 +1488,352 @@ describe("Toolcraft template performance contract", () => {
|
|
|
1224
1488
|
expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual([]);
|
|
1225
1489
|
});
|
|
1226
1490
|
|
|
1491
|
+
it("requires max-value fixtures to declare a measured load profile", () => {
|
|
1492
|
+
const config = defineToolcraftPerformance({
|
|
1493
|
+
rendererStrategy: "none",
|
|
1494
|
+
rendererWorkload: "none",
|
|
1495
|
+
scenarios: [
|
|
1496
|
+
{
|
|
1497
|
+
automated: true,
|
|
1498
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1499
|
+
browser: true,
|
|
1500
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1501
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1502
|
+
controlLabel: "Density",
|
|
1503
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1504
|
+
fixture: "runtime density fixture",
|
|
1505
|
+
id: "density-drag",
|
|
1506
|
+
interaction: "control-drag",
|
|
1507
|
+
stressFixture: {
|
|
1508
|
+
kind: "max-value",
|
|
1509
|
+
reason: "Density 11 is claimed as the smooth target without hard-limit evidence.",
|
|
1510
|
+
value: 11,
|
|
1511
|
+
},
|
|
1512
|
+
target: "render.density",
|
|
1513
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1514
|
+
workload: true,
|
|
1515
|
+
},
|
|
1516
|
+
],
|
|
1517
|
+
usesCustomRenderer: false,
|
|
1518
|
+
workloadTargets: ["render.density"],
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1522
|
+
"density-drag stressFixture.loadProfile must declare hardLimit, smoothTarget, and smoothTargetRatio so performance workload is a measured smooth target rather than a hidden toy fixture.",
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
it("rejects load profiles whose smooth target does not match the applied fixture", () => {
|
|
1527
|
+
const config = defineToolcraftPerformance({
|
|
1528
|
+
rendererStrategy: "none",
|
|
1529
|
+
rendererWorkload: "none",
|
|
1530
|
+
scenarios: [
|
|
1531
|
+
{
|
|
1532
|
+
automated: true,
|
|
1533
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1534
|
+
browser: true,
|
|
1535
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1536
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1537
|
+
controlLabel: "Density",
|
|
1538
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1539
|
+
fixture: "runtime density fixture",
|
|
1540
|
+
id: "density-drag",
|
|
1541
|
+
interaction: "control-drag",
|
|
1542
|
+
stressFixture: {
|
|
1543
|
+
kind: "max-value",
|
|
1544
|
+
loadProfile: {
|
|
1545
|
+
hardLimit: 12,
|
|
1546
|
+
metric: "numeric-max",
|
|
1547
|
+
smoothTarget: 11,
|
|
1548
|
+
smoothTargetRatio: 0.9,
|
|
1549
|
+
target: "render.density",
|
|
1550
|
+
userFacingRange: "experimental-above-smooth",
|
|
1551
|
+
},
|
|
1552
|
+
reason: "Density 10 does not match the documented smooth target.",
|
|
1553
|
+
value: 10,
|
|
1554
|
+
},
|
|
1555
|
+
target: "render.density",
|
|
1556
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1557
|
+
workload: true,
|
|
1558
|
+
},
|
|
1559
|
+
],
|
|
1560
|
+
usesCustomRenderer: false,
|
|
1561
|
+
workloadTargets: ["render.density"],
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
1565
|
+
expect.arrayContaining([
|
|
1566
|
+
"density-drag stressFixture.loadProfile.smoothTarget must match stressFixture.value so browser tests apply the documented smooth workload target.",
|
|
1567
|
+
"density-drag stressFixture.loadProfile.degradationStepPercent must be 10 when smoothTargetRatio is below 1.",
|
|
1568
|
+
"density-drag stressFixture.loadProfile.evidence must include failed measurements for each 10 percent step above smoothTargetRatio 0.9.",
|
|
1569
|
+
]),
|
|
1570
|
+
);
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
it("rejects load profiles whose ratio does not describe the measured smooth target", () => {
|
|
1574
|
+
const config = defineToolcraftPerformance({
|
|
1575
|
+
rendererStrategy: "none",
|
|
1576
|
+
rendererWorkload: "none",
|
|
1577
|
+
scenarios: [
|
|
1578
|
+
{
|
|
1579
|
+
automated: true,
|
|
1580
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1581
|
+
browser: true,
|
|
1582
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1583
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1584
|
+
controlLabel: "Density",
|
|
1585
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1586
|
+
fixture: "runtime density fixture",
|
|
1587
|
+
id: "density-drag",
|
|
1588
|
+
interaction: "control-drag",
|
|
1589
|
+
stressFixture: {
|
|
1590
|
+
kind: "max-value",
|
|
1591
|
+
loadProfile: {
|
|
1592
|
+
degradationStepPercent: 10,
|
|
1593
|
+
evidence: [
|
|
1594
|
+
{
|
|
1595
|
+
attemptedTarget: 12,
|
|
1596
|
+
decision:
|
|
1597
|
+
"Density 12 remains experimental because it misses frame budget after optimization.",
|
|
1598
|
+
measuredResult: "At density 12, maxFrameGapMs 148 exceeded the 80ms budget.",
|
|
1599
|
+
optimizationAttempted:
|
|
1600
|
+
"Cached glyph atlas and coalesced preview work to requestAnimationFrame.",
|
|
1601
|
+
result: "failed",
|
|
1602
|
+
scenarioId: "density-drag",
|
|
1603
|
+
},
|
|
1604
|
+
],
|
|
1605
|
+
hardLimit: 12,
|
|
1606
|
+
metric: "numeric-max",
|
|
1607
|
+
smoothTarget: 2,
|
|
1608
|
+
smoothTargetRatio: 0.9,
|
|
1609
|
+
target: "render.density",
|
|
1610
|
+
userFacingRange: "experimental-above-smooth",
|
|
1611
|
+
},
|
|
1612
|
+
reason: "Density 2 is far below the claimed 90 percent smooth target.",
|
|
1613
|
+
value: 2,
|
|
1614
|
+
},
|
|
1615
|
+
target: "render.density",
|
|
1616
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1617
|
+
workload: true,
|
|
1618
|
+
},
|
|
1619
|
+
],
|
|
1620
|
+
usesCustomRenderer: false,
|
|
1621
|
+
workloadTargets: ["render.density"],
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1625
|
+
"density-drag stressFixture.loadProfile.smoothTargetRatio 0.9 must describe the actual smooth target ratio 0.1667.",
|
|
1626
|
+
);
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
it("rejects fully guaranteed custom load profiles whose smooth target is not the hard limit", () => {
|
|
1630
|
+
const config = defineToolcraftPerformance({
|
|
1631
|
+
rendererStrategy: "canvas-2d",
|
|
1632
|
+
rendererWorkload: "pixel-output",
|
|
1633
|
+
scenarios: [
|
|
1634
|
+
{
|
|
1635
|
+
automated: true,
|
|
1636
|
+
automatedTestName: "perf: combined preview stays responsive",
|
|
1637
|
+
browser: true,
|
|
1638
|
+
browserTestName: "browser perf: combined preview stays responsive",
|
|
1639
|
+
budget: { maxPreviewMs: 200, maxRenderMs: 200 },
|
|
1640
|
+
expectedObservable: "Preview renders the combined source and effect workload.",
|
|
1641
|
+
fixture: "combined custom fixture",
|
|
1642
|
+
id: "combined-preview",
|
|
1643
|
+
interaction: "preview-render",
|
|
1644
|
+
stress: true,
|
|
1645
|
+
stressFixture: {
|
|
1646
|
+
kind: "custom",
|
|
1647
|
+
loadProfile: {
|
|
1648
|
+
hardLimit: { density: 12, sourceMedia: { height: 2160, width: 3840 } },
|
|
1649
|
+
metric: "custom",
|
|
1650
|
+
smoothTarget: { density: 4, sourceMedia: { height: 1080, width: 1920 } },
|
|
1651
|
+
smoothTargetRatio: 1,
|
|
1652
|
+
target: "renderer.output",
|
|
1653
|
+
userFacingRange: "fully-guaranteed",
|
|
1654
|
+
},
|
|
1655
|
+
reason: "This claims full coverage while applying a smaller custom state.",
|
|
1656
|
+
value: { density: 4, sourceMedia: { height: 1080, width: 1920 } },
|
|
1657
|
+
},
|
|
1658
|
+
target: "renderer.output",
|
|
1659
|
+
workload: false,
|
|
1660
|
+
},
|
|
1661
|
+
],
|
|
1662
|
+
usesCustomRenderer: true,
|
|
1663
|
+
workloadTargets: [],
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1667
|
+
"combined-preview stressFixture.loadProfile.smoothTargetRatio 1 requires smoothTarget to equal hardLimit.",
|
|
1668
|
+
);
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
it("accepts a 10 percent degraded smooth target with hard-limit failure evidence", () => {
|
|
1672
|
+
const config = defineToolcraftPerformance({
|
|
1673
|
+
rendererStrategy: "none",
|
|
1674
|
+
rendererWorkload: "none",
|
|
1675
|
+
scenarios: [
|
|
1676
|
+
{
|
|
1677
|
+
automated: true,
|
|
1678
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1679
|
+
browser: true,
|
|
1680
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1681
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1682
|
+
controlLabel: "Density",
|
|
1683
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1684
|
+
fixture: "runtime density fixture",
|
|
1685
|
+
id: "density-drag",
|
|
1686
|
+
interaction: "control-drag",
|
|
1687
|
+
stressFixture: createDegradedMaxValueStressFixture(),
|
|
1688
|
+
target: "render.density",
|
|
1689
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1690
|
+
workload: true,
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
automated: true,
|
|
1694
|
+
automatedTestName: "perf: mode change stays responsive",
|
|
1695
|
+
browser: true,
|
|
1696
|
+
browserTestName: "browser perf: mode change stays responsive",
|
|
1697
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1698
|
+
controlLabel: "Mode",
|
|
1699
|
+
expectedObservable: "Changing Mode updates text output without blocking the UI.",
|
|
1700
|
+
fixture: "runtime mode fixture",
|
|
1701
|
+
id: "mode-change",
|
|
1702
|
+
interaction: "control-change",
|
|
1703
|
+
target: "render.mode",
|
|
1704
|
+
values: { default: "soft", max: "sharp", min: "soft" },
|
|
1705
|
+
workload: false,
|
|
1706
|
+
},
|
|
1707
|
+
],
|
|
1708
|
+
usesCustomRenderer: false,
|
|
1709
|
+
workloadTargets: ["render.density"],
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
it("requires evidence for every skipped 10 percent degradation step", () => {
|
|
1716
|
+
const degradedFixture = createDegradedMaxValueStressFixture();
|
|
1717
|
+
const config = defineToolcraftPerformance({
|
|
1718
|
+
rendererStrategy: "none",
|
|
1719
|
+
rendererWorkload: "none",
|
|
1720
|
+
scenarios: [
|
|
1721
|
+
{
|
|
1722
|
+
automated: true,
|
|
1723
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1724
|
+
browser: true,
|
|
1725
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1726
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1727
|
+
controlLabel: "Density",
|
|
1728
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1729
|
+
fixture: "runtime density fixture",
|
|
1730
|
+
id: "density-drag",
|
|
1731
|
+
interaction: "control-drag",
|
|
1732
|
+
stressFixture: {
|
|
1733
|
+
...degradedFixture,
|
|
1734
|
+
loadProfile: {
|
|
1735
|
+
...degradedFixture.loadProfile,
|
|
1736
|
+
smoothTarget: 10,
|
|
1737
|
+
smoothTargetRatio: 0.8,
|
|
1738
|
+
},
|
|
1739
|
+
value: 10,
|
|
1740
|
+
},
|
|
1741
|
+
target: "render.density",
|
|
1742
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1743
|
+
workload: true,
|
|
1744
|
+
},
|
|
1745
|
+
],
|
|
1746
|
+
usesCustomRenderer: false,
|
|
1747
|
+
workloadTargets: ["render.density"],
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1751
|
+
"density-drag stressFixture.loadProfile.evidence must include failed measurements for each 10 percent step above smoothTargetRatio 0.8.",
|
|
1752
|
+
);
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
it("rejects numeric hard limits that do not match schema max", () => {
|
|
1756
|
+
const fixture = createDegradedMaxValueStressFixture();
|
|
1757
|
+
const config = defineToolcraftPerformance({
|
|
1758
|
+
rendererStrategy: "none",
|
|
1759
|
+
rendererWorkload: "none",
|
|
1760
|
+
scenarios: [
|
|
1761
|
+
{
|
|
1762
|
+
automated: true,
|
|
1763
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
1764
|
+
browser: true,
|
|
1765
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
1766
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1767
|
+
controlLabel: "Density",
|
|
1768
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
1769
|
+
fixture: "runtime density fixture",
|
|
1770
|
+
id: "density-drag",
|
|
1771
|
+
interaction: "control-drag",
|
|
1772
|
+
stressFixture: {
|
|
1773
|
+
...fixture,
|
|
1774
|
+
loadProfile: {
|
|
1775
|
+
...fixture.loadProfile,
|
|
1776
|
+
hardLimit: 10,
|
|
1777
|
+
},
|
|
1778
|
+
},
|
|
1779
|
+
target: "render.density",
|
|
1780
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1781
|
+
workload: true,
|
|
1782
|
+
},
|
|
1783
|
+
],
|
|
1784
|
+
usesCustomRenderer: false,
|
|
1785
|
+
workloadTargets: ["render.density"],
|
|
1786
|
+
});
|
|
1787
|
+
|
|
1788
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1789
|
+
"density-drag stressFixture.loadProfile.hardLimit must match schema max 12.",
|
|
1790
|
+
);
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
it("rejects toy many-items fixtures below their declared useful count", () => {
|
|
1794
|
+
const config = defineToolcraftPerformance({
|
|
1795
|
+
rendererStrategy: "none",
|
|
1796
|
+
rendererWorkload: "none",
|
|
1797
|
+
scenarios: [
|
|
1798
|
+
{
|
|
1799
|
+
automated: true,
|
|
1800
|
+
automatedTestName: "perf: items drag stays responsive",
|
|
1801
|
+
browser: true,
|
|
1802
|
+
browserTestName: "browser perf: items drag stays responsive",
|
|
1803
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
1804
|
+
controlLabel: "Density",
|
|
1805
|
+
expectedObservable: "Dragging Density updates many items without blocking the UI.",
|
|
1806
|
+
fixture: "many item fixture",
|
|
1807
|
+
id: "items-density-drag",
|
|
1808
|
+
interaction: "control-drag",
|
|
1809
|
+
stressFixture: {
|
|
1810
|
+
kind: "many-items",
|
|
1811
|
+
loadProfile: {
|
|
1812
|
+
hardLimit: 100,
|
|
1813
|
+
metric: "count",
|
|
1814
|
+
smoothTarget: 2,
|
|
1815
|
+
smoothTargetRatio: 1,
|
|
1816
|
+
target: "items.count",
|
|
1817
|
+
userFacingRange: "fully-guaranteed",
|
|
1818
|
+
},
|
|
1819
|
+
minCount: 10,
|
|
1820
|
+
reason: "Two items is too small to prove collection performance.",
|
|
1821
|
+
value: 2,
|
|
1822
|
+
},
|
|
1823
|
+
target: "render.density",
|
|
1824
|
+
values: { default: 4, max: 12, min: 1 },
|
|
1825
|
+
workload: true,
|
|
1826
|
+
},
|
|
1827
|
+
],
|
|
1828
|
+
usesCustomRenderer: false,
|
|
1829
|
+
workloadTargets: ["render.density"],
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1833
|
+
"items-density-drag stressFixture.value must include at least 10 items for many-items fixtures.",
|
|
1834
|
+
);
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1227
1837
|
it("requires workload controls in custom renderers to declare an independent workload fixture", () => {
|
|
1228
1838
|
const config = defineToolcraftPerformance({
|
|
1229
1839
|
rendererStrategy: "canvas-2d",
|
|
@@ -1469,6 +2079,49 @@ describe("Toolcraft template performance contract", () => {
|
|
|
1469
2079
|
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
1470
2080
|
});
|
|
1471
2081
|
|
|
2082
|
+
it("rejects main-thread Canvas 2D raster/composite pipelines misclassified as text output", () => {
|
|
2083
|
+
const config = defineToolcraftPerformance({
|
|
2084
|
+
rendererStrategy: "canvas-2d",
|
|
2085
|
+
rendererTechnique: {
|
|
2086
|
+
exportRenderer: "canvas-2d",
|
|
2087
|
+
fidelityRisks: ["rasterized text must preserve the shader-style composite"],
|
|
2088
|
+
layers: [
|
|
2089
|
+
{
|
|
2090
|
+
content: ["text", "noise"],
|
|
2091
|
+
exportMode: "composited",
|
|
2092
|
+
id: "product-text-composite",
|
|
2093
|
+
intentionalRasterizationReason:
|
|
2094
|
+
"The product text is warped, tinted, and exported as final pixels.",
|
|
2095
|
+
kind: "product-foreground",
|
|
2096
|
+
primitiveCount: "medium",
|
|
2097
|
+
renderer: "canvas-2d",
|
|
2098
|
+
uiSelector: '[data-toolcraft-renderer-layer="product-text-composite"]',
|
|
2099
|
+
},
|
|
2100
|
+
],
|
|
2101
|
+
performanceRisks: [
|
|
2102
|
+
"WebGL measured stress evidence was reviewed before keeping the text composite on Canvas 2D.",
|
|
2103
|
+
],
|
|
2104
|
+
previewRenderer: "canvas-2d",
|
|
2105
|
+
productRepresentation: "mixed",
|
|
2106
|
+
rendererStrategy: "canvas-2d",
|
|
2107
|
+
rendererWorkload: "text-output",
|
|
2108
|
+
sourceRepresentation: "dom-text",
|
|
2109
|
+
whyNotAlternativeStrategies: [
|
|
2110
|
+
"WebGL measured stress evidence showed text atlas uploads dominated this fixture.",
|
|
2111
|
+
],
|
|
2112
|
+
},
|
|
2113
|
+
rendererWorkload: "text-output",
|
|
2114
|
+
rendererPipeline: createMainThreadCanvasCompositePipeline(),
|
|
2115
|
+
scenarios: createTextRendererScenarios(),
|
|
2116
|
+
usesCustomRenderer: true,
|
|
2117
|
+
workloadTargets: ["render.density"],
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
2121
|
+
'Canvas 2D pipelines with main-thread rasterize/composite preview pressure must use rendererWorkload "pixel-output" or move expensive passes off the main thread; received "text-output".',
|
|
2122
|
+
);
|
|
2123
|
+
});
|
|
2124
|
+
|
|
1472
2125
|
it("requires animated custom renderers to test viewport dragging during animation", () => {
|
|
1473
2126
|
const config = createAnimatedVectorRendererConfig({ includeViewportDrag: false });
|
|
1474
2127
|
|
|
@@ -1621,10 +2274,10 @@ describe("Toolcraft template performance contract", () => {
|
|
|
1621
2274
|
});
|
|
1622
2275
|
|
|
1623
2276
|
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1624
|
-
"Detail-heavy Canvas 2D renderers must
|
|
2277
|
+
"Detail-heavy Canvas 2D renderers must include rendererTechnique.measuredAlternativeEvidence for WebGL/WebGPU stress comparison before keeping the pixel work on CPU.",
|
|
1625
2278
|
);
|
|
1626
2279
|
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
1627
|
-
'rendererWorkload "pixel-output" should use rendererStrategy "webgl" or "webgpu", received "canvas-2d". Keeping a CPU renderer requires
|
|
2280
|
+
'rendererWorkload "pixel-output" should use rendererStrategy "webgl" or "webgpu", received "canvas-2d". Keeping a CPU renderer requires rendererTechnique.measuredAlternativeEvidence for WebGL/WebGPU stress comparison.',
|
|
1628
2281
|
);
|
|
1629
2282
|
});
|
|
1630
2283
|
|
|
@@ -1637,6 +2290,7 @@ describe("Toolcraft template performance contract", () => {
|
|
|
1637
2290
|
performanceRisks: [
|
|
1638
2291
|
"Measured WebGL stress evidence showed setup overhead exceeded cached Canvas 2D worker output for this static fixture.",
|
|
1639
2292
|
],
|
|
2293
|
+
measuredAlternativeEvidence: createMeasuredWebglEvidence(),
|
|
1640
2294
|
previewRenderer: "canvas-2d",
|
|
1641
2295
|
productRepresentation: "pixel",
|
|
1642
2296
|
rendererStrategy: "canvas-2d",
|
|
@@ -2267,6 +2921,7 @@ describe("Toolcraft template performance contract", () => {
|
|
|
2267
2921
|
renderer: "canvas-2d",
|
|
2268
2922
|
},
|
|
2269
2923
|
],
|
|
2924
|
+
measuredAlternativeEvidence: createMeasuredWebglEvidence(),
|
|
2270
2925
|
performanceRisks: ["dense background redraw can be expensive at large output size"],
|
|
2271
2926
|
previewRenderer: "canvas-2d",
|
|
2272
2927
|
productRepresentation: "mixed",
|