@pixel-point/toolcraft 0.0.8 → 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.
Files changed (88) hide show
  1. package/README.md +42 -9
  2. package/package.json +1 -1
  3. package/src/generate.mjs +42 -5
  4. package/src/generate.test.mjs +40 -0
  5. package/src/package-json.mjs +15 -0
  6. package/src/package-json.test.mjs +14 -1
  7. package/templates/runtime/contracts/component-contracts.test.ts +251 -47
  8. package/templates/runtime/contracts/component-contracts.ts +130 -57
  9. package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
  10. package/templates/runtime/contracts/decision-contracts.ts +22 -9
  11. package/templates/runtime/export/export.test.ts +63 -0
  12. package/templates/runtime/export/export.ts +55 -0
  13. package/templates/runtime/index.ts +1 -0
  14. package/templates/runtime/react/canvas-shell.test.tsx +58 -4
  15. package/templates/runtime/react/canvas-shell.tsx +31 -9
  16. package/templates/runtime/react/controls-panel.test.tsx +916 -108
  17. package/templates/runtime/react/controls-panel.tsx +136 -30
  18. package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
  19. package/templates/runtime/react/settings-transfer.test.ts +4 -0
  20. package/templates/runtime/react/settings-transfer.ts +6 -1
  21. package/templates/runtime/react/timeline-panel.test.tsx +14 -0
  22. package/templates/runtime/react/timeline-panel.tsx +44 -7
  23. package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
  24. package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
  25. package/templates/runtime/react/toolcraft-app.tsx +56 -37
  26. package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
  27. package/templates/runtime/schema/define-toolcraft.ts +140 -246
  28. package/templates/runtime/schema/runtime-targets.ts +21 -0
  29. package/templates/runtime/schema/types.ts +44 -0
  30. package/templates/runtime/state/create-template-state.test.ts +156 -0
  31. package/templates/runtime/state/create-template-state.ts +38 -8
  32. package/templates/runtime/state/media-defaults.ts +105 -0
  33. package/templates/runtime/state/persistence.test.ts +58 -0
  34. package/templates/runtime/state/persistence.ts +105 -1
  35. package/templates/runtime/state/reducer.test.ts +280 -4
  36. package/templates/runtime/state/reducer.ts +195 -9
  37. package/templates/runtime/state/timeline-loop.test.ts +71 -0
  38. package/templates/runtime/state/timeline-loop.ts +35 -0
  39. package/templates/runtime/state/types.ts +27 -0
  40. package/templates/runtime/testing/performance.test.ts +810 -21
  41. package/templates/runtime/testing/performance.ts +823 -53
  42. package/templates/starter/AGENTS.md +24 -18
  43. package/templates/starter/docs/toolcraft/README.md +8 -4
  44. package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
  45. package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
  46. package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
  47. package/templates/starter/docs/toolcraft/component-rules.md +75 -42
  48. package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
  49. package/templates/starter/docs/toolcraft/performance.md +37 -11
  50. package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
  51. package/templates/starter/docs/toolcraft/workflow.md +5 -2
  52. package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
  53. package/templates/starter/e2e/app-performance.spec.ts +55 -2
  54. package/templates/starter/e2e/performance-helpers.ts +45 -0
  55. package/templates/starter/gitignore +1 -0
  56. package/templates/starter/index.html +1 -0
  57. package/templates/starter/package.json +3 -0
  58. package/templates/starter/playwright.config.ts +1 -1
  59. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  60. package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
  61. package/templates/starter/scripts/toolcraft-port.mjs +280 -0
  62. package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
  63. package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
  64. package/templates/starter/src/app/starter-acceptance.ts +1453 -97
  65. package/templates/starter/src/app/starter-performance.test.ts +111 -7
  66. package/templates/starter/src/app/starter-performance.ts +5 -0
  67. package/templates/starter/src/app/starter-schema.test.ts +32 -7
  68. package/templates/starter/src/app/starter-schema.ts +6 -2
  69. package/templates/starter/vite.config.ts +58 -2
  70. package/templates/ui/components/control-layout/index.tsx +8 -3
  71. package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
  72. package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
  73. package/templates/ui/components/controls/color/index.ts +4 -1
  74. package/templates/ui/components/controls/color/palette-control.tsx +34 -4
  75. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
  76. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
  77. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
  78. package/templates/ui/components/controls/file-drop/index.ts +6 -1
  79. package/templates/ui/components/controls/index.ts +4 -0
  80. package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
  81. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
  82. package/templates/ui/components/controls/select/select-control.tsx +8 -25
  83. package/templates/ui/components/controls/slider/slider-value.ts +0 -1
  84. package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
  85. package/templates/ui/components/controls/vector/index.ts +1 -0
  86. package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
  87. package/templates/ui/components/panel/panel-actions.tsx +1 -1
  88. package/templates/ui/components/panel/panel-section.tsx +29 -5
@@ -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
 
@@ -76,6 +78,34 @@ const ordinarySliderSchema = defineToolcraft({
76
78
  },
77
79
  });
78
80
 
81
+ const rangeSliderSchema = defineToolcraft({
82
+ canvas: {
83
+ enabled: true,
84
+ sizing: { mode: "intrinsic-media" },
85
+ },
86
+ panels: {
87
+ controls: {
88
+ sections: [
89
+ {
90
+ controls: {
91
+ band: {
92
+ defaultValue: [20, 80],
93
+ label: "Band",
94
+ max: 100,
95
+ min: 0,
96
+ performanceReason: "Band changes a lightweight bounded interval.",
97
+ performanceRole: "responsiveness",
98
+ target: "render.band",
99
+ type: "rangeSlider",
100
+ },
101
+ },
102
+ },
103
+ ],
104
+ title: "Runtime Controls",
105
+ },
106
+ },
107
+ });
108
+
79
109
  const largeTextSchema = defineToolcraft({
80
110
  canvas: {
81
111
  enabled: true,
@@ -152,6 +182,14 @@ function createViewportZoomStressScenario() {
152
182
  function createMaxValueStressFixture(value: unknown, reason = "Maximum workload value.") {
153
183
  return {
154
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
+ },
155
193
  reason,
156
194
  value,
157
195
  };
@@ -163,6 +201,14 @@ function createCustomStressFixture(
163
201
  ) {
164
202
  return {
165
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
+ },
166
212
  reason,
167
213
  value,
168
214
  };
@@ -199,11 +245,60 @@ function createMediaStressFixture(
199
245
  ) {
200
246
  return {
201
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
+ },
202
256
  reason,
203
257
  value,
204
258
  };
205
259
  }
206
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
+
207
302
  function createTextRendererPipeline() {
208
303
  return {
209
304
  interactionInvalidation: [
@@ -367,6 +462,61 @@ function createDenseCanvasPipeline() {
367
462
  } as const;
368
463
  }
369
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
+
370
520
  function createPixelRendererPipeline() {
371
521
  return {
372
522
  interactionInvalidation: [
@@ -600,6 +750,71 @@ function createAnimatedVectorRendererConfig(options: { includeViewportDrag: bool
600
750
  }
601
751
 
602
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
+
603
818
  it("collects performance-sensitive controls from schema", () => {
604
819
  expect(collectToolcraftPerformanceSensitiveControls(testSchema)).toEqual([
605
820
  expect.objectContaining({
@@ -654,6 +869,7 @@ describe("Toolcraft template performance contract", () => {
654
869
  const unclassifiedSchema = defineToolcraft({
655
870
  canvas: {
656
871
  enabled: true,
872
+ renderScale: true,
657
873
  sizing: { mode: "intrinsic-media" },
658
874
  },
659
875
  panels: {
@@ -676,6 +892,7 @@ describe("Toolcraft template performance contract", () => {
676
892
  ],
677
893
  title: "Runtime Controls",
678
894
  },
895
+ timeline: { mode: "playback" },
679
896
  },
680
897
  });
681
898
 
@@ -714,6 +931,64 @@ describe("Toolcraft template performance contract", () => {
714
931
  expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toEqual([]);
715
932
  });
716
933
 
934
+ it("rejects slider performance coverage that does not drag the real control", () => {
935
+ const config = defineToolcraftPerformance({
936
+ rendererStrategy: "none",
937
+ rendererWorkload: "none",
938
+ scenarios: [
939
+ {
940
+ automated: true,
941
+ automatedTestName: "perf: opacity change stays responsive",
942
+ browser: true,
943
+ browserTestName: "browser perf: opacity change stays responsive",
944
+ budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
945
+ controlLabel: "Opacity",
946
+ expectedObservable: "Changing Opacity stays responsive.",
947
+ fixture: "opacity responsiveness fixture",
948
+ id: "opacity-change",
949
+ interaction: "control-change",
950
+ target: "render.opacity",
951
+ workload: false,
952
+ },
953
+ ],
954
+ usesCustomRenderer: false,
955
+ workloadTargets: [],
956
+ });
957
+
958
+ expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toContain(
959
+ "render.opacity is a slider and must have a control-drag performance scenario proving live canvas/product feedback while dragging.",
960
+ );
961
+ });
962
+
963
+ it("rejects range slider performance coverage that does not drag the real control", () => {
964
+ const config = defineToolcraftPerformance({
965
+ rendererStrategy: "none",
966
+ rendererWorkload: "none",
967
+ scenarios: [
968
+ {
969
+ automated: true,
970
+ automatedTestName: "perf: band change stays responsive",
971
+ browser: true,
972
+ browserTestName: "browser perf: band change stays responsive",
973
+ budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
974
+ controlLabel: "Band",
975
+ expectedObservable: "Changing Band stays responsive.",
976
+ fixture: "band responsiveness fixture",
977
+ id: "band-change",
978
+ interaction: "control-change",
979
+ target: "render.band",
980
+ workload: false,
981
+ },
982
+ ],
983
+ usesCustomRenderer: false,
984
+ workloadTargets: [],
985
+ });
986
+
987
+ expect(validateToolcraftPerformanceCoverage(rangeSliderSchema, config)).toContain(
988
+ "render.band is a rangeSlider and must have a control-drag performance scenario proving live canvas/product feedback while dragging.",
989
+ );
990
+ });
991
+
717
992
  it("requires performance-sensitive controls to be listed in workloadTargets", () => {
718
993
  const config = defineToolcraftPerformance({
719
994
  rendererStrategy: "none",
@@ -758,6 +1033,54 @@ describe("Toolcraft template performance contract", () => {
758
1033
  );
759
1034
  });
760
1035
 
1036
+ it("requires workload slider coverage to use real control drag", () => {
1037
+ const config = defineToolcraftPerformance({
1038
+ rendererStrategy: "none",
1039
+ rendererWorkload: "none",
1040
+ scenarios: [
1041
+ {
1042
+ automated: true,
1043
+ automatedTestName: "perf: density change stays responsive",
1044
+ browser: true,
1045
+ browserTestName: "browser perf: density change stays responsive",
1046
+ budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1047
+ controlLabel: "Density",
1048
+ expectedObservable: "Changing Density updates the product without blocking the UI.",
1049
+ fixture: "runtime density fixture",
1050
+ id: "density-change",
1051
+ interaction: "control-change",
1052
+ target: "render.density",
1053
+ values: { default: 4, max: 12, min: 1 },
1054
+ workload: true,
1055
+ },
1056
+ {
1057
+ automated: true,
1058
+ automatedTestName: "perf: mode change stays responsive",
1059
+ browser: true,
1060
+ browserTestName: "browser perf: mode change stays responsive",
1061
+ budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1062
+ controlLabel: "Mode",
1063
+ expectedObservable: "Changing Mode updates the product without blocking the UI.",
1064
+ fixture: "runtime mode fixture",
1065
+ id: "mode-change",
1066
+ interaction: "control-change",
1067
+ target: "render.mode",
1068
+ values: { default: "soft", max: "sharp", min: "soft" },
1069
+ workload: false,
1070
+ },
1071
+ ],
1072
+ usesCustomRenderer: false,
1073
+ workloadTargets: ["render.density"],
1074
+ });
1075
+
1076
+ expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
1077
+ expect.arrayContaining([
1078
+ "render.density must have min/default/max workload performance coverage through a real control-drag scenario.",
1079
+ "render.density is a slider and must have a control-drag performance scenario proving live canvas/product feedback while dragging.",
1080
+ ]),
1081
+ );
1082
+ });
1083
+
761
1084
  it("rejects responsiveness role on semantically workload controls", () => {
762
1085
  const misclassifiedSchema = defineToolcraft({
763
1086
  canvas: {
@@ -1061,43 +1384,464 @@ describe("Toolcraft template performance contract", () => {
1061
1384
  );
1062
1385
  });
1063
1386
 
1064
- it("accepts large text workload scenarios with machine-checkable stress fixtures", () => {
1387
+ it("rejects short large-text values hidden inside custom fixtures", () => {
1065
1388
  const config = defineToolcraftPerformance({
1066
1389
  rendererStrategy: "none",
1067
1390
  rendererWorkload: "none",
1068
1391
  scenarios: [
1069
1392
  {
1070
1393
  automated: true,
1071
- automatedTestName: "perf: content input stays responsive",
1394
+ automatedTestName: "perf: preview render stays under budget",
1072
1395
  browser: true,
1073
- browserTestName: "browser perf: content input stays responsive",
1074
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1075
- controlLabel: "Content",
1076
- expectedObservable: "Large text content updates without blocking the UI.",
1077
- fixture: "large text content fixture",
1078
- id: "content-change",
1079
- interaction: "control-change",
1080
- stressFixture: createLargeTextStressFixture(),
1081
- target: "product.content",
1082
- values: { default: "", max: createLargeTextStressValue(), min: "" },
1083
- workload: 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,
1084
1412
  },
1085
1413
  ],
1086
1414
  usesCustomRenderer: false,
1087
- workloadTargets: ["product.content"],
1415
+ workloadTargets: [],
1088
1416
  });
1089
1417
 
1090
- expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual([]);
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
+ );
1091
1424
  });
1092
1425
 
1093
- it("requires workload controls in custom renderers to declare an independent workload fixture", () => {
1426
+ it("rejects custom render scale fixtures that are not concrete high-resolution values", () => {
1094
1427
  const config = defineToolcraftPerformance({
1095
- rendererStrategy: "canvas-2d",
1096
- rendererWorkload: "text-output",
1428
+ rendererStrategy: "none",
1429
+ rendererWorkload: "none",
1097
1430
  scenarios: [
1098
1431
  {
1099
1432
  automated: true,
1100
- automatedTestName: "perf: density drag stays responsive",
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
+
1462
+ it("accepts large text workload scenarios with machine-checkable stress fixtures", () => {
1463
+ const config = defineToolcraftPerformance({
1464
+ rendererStrategy: "none",
1465
+ rendererWorkload: "none",
1466
+ scenarios: [
1467
+ {
1468
+ automated: true,
1469
+ automatedTestName: "perf: content input stays responsive",
1470
+ browser: true,
1471
+ browserTestName: "browser perf: content input stays responsive",
1472
+ budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1473
+ controlLabel: "Content",
1474
+ expectedObservable: "Large text content updates without blocking the UI.",
1475
+ fixture: "large text content fixture",
1476
+ id: "content-change",
1477
+ interaction: "control-change",
1478
+ stressFixture: createLargeTextStressFixture(),
1479
+ target: "product.content",
1480
+ values: { default: "", max: createLargeTextStressValue(), min: "" },
1481
+ workload: true,
1482
+ },
1483
+ ],
1484
+ usesCustomRenderer: false,
1485
+ workloadTargets: ["product.content"],
1486
+ });
1487
+
1488
+ expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual([]);
1489
+ });
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
+
1837
+ it("requires workload controls in custom renderers to declare an independent workload fixture", () => {
1838
+ const config = defineToolcraftPerformance({
1839
+ rendererStrategy: "canvas-2d",
1840
+ rendererWorkload: "text-output",
1841
+ scenarios: [
1842
+ {
1843
+ automated: true,
1844
+ automatedTestName: "perf: density drag stays responsive",
1101
1845
  browser: true,
1102
1846
  browserTestName: "browser perf: density drag stays responsive",
1103
1847
  budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
@@ -1335,6 +2079,49 @@ describe("Toolcraft template performance contract", () => {
1335
2079
  expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
1336
2080
  });
1337
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
+
1338
2125
  it("requires animated custom renderers to test viewport dragging during animation", () => {
1339
2126
  const config = createAnimatedVectorRendererConfig({ includeViewportDrag: false });
1340
2127
 
@@ -1487,10 +2274,10 @@ describe("Toolcraft template performance contract", () => {
1487
2274
  });
1488
2275
 
1489
2276
  expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
1490
- "Detail-heavy Canvas 2D renderers must evaluate WebGL/WebGPU in rendererTechnique.whyNotAlternativeStrategies or performanceRisks with measured stress evidence before keeping the pixel work on CPU.",
2277
+ "Detail-heavy Canvas 2D renderers must include rendererTechnique.measuredAlternativeEvidence for WebGL/WebGPU stress comparison before keeping the pixel work on CPU.",
1491
2278
  );
1492
2279
  expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
1493
- 'rendererWorkload "pixel-output" should use rendererStrategy "webgl" or "webgpu", received "canvas-2d". Keeping a CPU renderer requires measured WebGL/WebGPU evidence in rendererTechnique.whyNotAlternativeStrategies or performanceRisks.',
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.',
1494
2281
  );
1495
2282
  });
1496
2283
 
@@ -1503,6 +2290,7 @@ describe("Toolcraft template performance contract", () => {
1503
2290
  performanceRisks: [
1504
2291
  "Measured WebGL stress evidence showed setup overhead exceeded cached Canvas 2D worker output for this static fixture.",
1505
2292
  ],
2293
+ measuredAlternativeEvidence: createMeasuredWebglEvidence(),
1506
2294
  previewRenderer: "canvas-2d",
1507
2295
  productRepresentation: "pixel",
1508
2296
  rendererStrategy: "canvas-2d",
@@ -2133,6 +2921,7 @@ describe("Toolcraft template performance contract", () => {
2133
2921
  renderer: "canvas-2d",
2134
2922
  },
2135
2923
  ],
2924
+ measuredAlternativeEvidence: createMeasuredWebglEvidence(),
2136
2925
  performanceRisks: ["dense background redraw can be expensive at large output size"],
2137
2926
  previewRenderer: "canvas-2d",
2138
2927
  productRepresentation: "mixed",