@pixel-point/toolcraft 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate-performance-iteration-execution.test.mjs +2 -2
- package/src/generate-test-demand-only-lifecycle.mjs +14 -3
- package/src/generate-test-dependency-sandbox.mjs +42 -5
- package/src/generate-test-doc-assertions.mjs +109 -69
- package/src/generate-test-doc-assertions.test.mjs +65 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +36 -118
- package/src/generate-test-output-assertions.mjs +95 -0
- package/src/generate-test-package-assertions.mjs +6 -0
- package/src/generate-test-performance-boundary-assertions.mjs +45 -0
- package/src/generate-test-product-fixture.mjs +1 -0
- package/src/generate-test-product-fixture.test.mjs +18 -0
- package/src/generate-test-product-iteration.mjs +1 -0
- package/src/generate-test-product-iteration.test.mjs +18 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/src/generate-test-standalone-delivery-stages.mjs +14 -6
- package/src/generate.test.mjs +14 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +59 -12
- package/templates/runtime/contracts/component-contracts.choices.ts +58 -12
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +21 -6
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +4 -1
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
- package/templates/runtime/contracts/component-contracts.runtime.ts +15 -10
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +43 -4
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
- package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
- package/templates/runtime/export/artifact-download.test.ts +71 -0
- package/templates/runtime/export/artifact-download.ts +80 -0
- package/templates/runtime/export/artifact-export-request.ts +23 -0
- package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
- package/templates/runtime/export/artifact-export-settings.ts +96 -0
- package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
- package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
- package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
- package/templates/runtime/export/artifact-frame-state.ts +44 -0
- package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
- package/templates/runtime/export/artifact-scene-frame.ts +118 -0
- package/templates/runtime/export/export-background.ts +46 -0
- package/templates/runtime/export/export-error.ts +43 -0
- package/templates/runtime/export/export.test.ts +49 -226
- package/templates/runtime/export/image-artifact-export.test.ts +111 -0
- package/templates/runtime/export/image-artifact-export.ts +145 -0
- package/templates/runtime/export/index.ts +44 -3
- package/templates/runtime/export/product-export-renderer.test.ts +77 -0
- package/templates/runtime/export/product-export-renderer.ts +63 -0
- package/templates/runtime/export/video-artifact-export.test.ts +166 -0
- package/templates/runtime/export/video-artifact-export.ts +164 -0
- package/templates/runtime/export/video-encoding-backend.ts +124 -0
- package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
- package/templates/runtime/export/video-encoding-policy.ts +86 -0
- package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
- package/templates/runtime/export/video-frame-schedule.ts +42 -0
- package/templates/runtime/index.ts +3 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
- package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +15 -304
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +41 -27
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +39 -11
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +18 -84
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +43 -87
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +68 -0
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
- package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +32 -19
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/canvas-render-scale.ts +62 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +62 -3
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/define-toolcraft.ts +1 -1
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/schema-resolvers.ts +0 -56
- package/templates/runtime/schema/types.ts +72 -17
- package/templates/runtime/state/canvas-background-state.ts +6 -0
- package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
- package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
- package/templates/starter/AGENTS.md +18 -15
- package/templates/starter/NOTICE.md +6 -0
- package/templates/starter/docs/toolcraft/README.md +2 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +23 -35
- package/templates/starter/docs/toolcraft/agent-worklog.md +18 -3
- package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -5
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +4 -2
- package/templates/starter/docs/toolcraft/core/performance.md +14 -3
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +16 -3
- package/templates/starter/docs/toolcraft/core/setup-export.md +49 -19
- package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/performance.md +20 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +5 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +23 -25
- package/templates/starter/docs/toolcraft/workflow.md +7 -2
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +50 -3
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
- package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
- package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
- package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +5 -34
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -16
- package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
- package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
- package/templates/starter/e2e/app-performance.spec.ts +16 -9
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +29 -341
- package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
- package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
- package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +13 -3
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +72 -35
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
- package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
- package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
- package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
- package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
- package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
- package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
- package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
- package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
- package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
- package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
- package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
- package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
- package/templates/starter/e2e/performance-path-helpers.ts +210 -91
- package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
- package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
- package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
- package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
- package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
- package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
- package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +95 -103
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -26
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
- package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +157 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +157 -206
- package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
- package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
- package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +35 -12
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +30 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +20 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +46 -1
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +13 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
- package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
- package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +4 -2
- package/templates/starter/src/routes/index.tsx +2 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/runtime/export/export.ts +0 -166
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
|
@@ -26,7 +26,8 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
|
|
|
26
26
|
"Infinity canvas removes finite artboard bounds and clipping, hides Aspect ratio, Canvas width, and Canvas height, and preserves the dormant finite canvas size for exact restoration when disabled.",
|
|
27
27
|
"Infinity canvas suppresses the bounded product-rendered preview background so the dormant finite output does not appear as a second canvas; when the standard Background pair is enabled, CanvasShell fills the complete infinite viewport with the selected Background color.",
|
|
28
28
|
"When the standard Background pair exists, disabling Background atomically restores finite mode and disables Infinity canvas; re-enabling Background restores switch availability without enabling Infinity automatically.",
|
|
29
|
-
"Infinite exports use canonical world-space scene frames: ToolcraftAppComposition.sceneBoundsProvider contributes product bounds, runtime image and model bounds are unioned
|
|
29
|
+
"Infinite exports use canonical world-space scene frames: ToolcraftAppComposition.sceneBoundsProvider contributes exact-state product bounds, runtime image and model bounds are unioned, hidden or suppressed content is excluded, PNG crops tightly, and runtime video unions every scheduled frame state into one envelope.",
|
|
30
|
+
"Infinite live preview uses that same exact-state product frame: runtime positions one product scene surface from sceneBoundsProvider, and custom raster/WebGL output consumes useToolcraftProductSceneFrame for backing size and world-to-local translation instead of dormant canvas.size.",
|
|
30
31
|
"Do not let canvas.upload choose intrinsic-media by default. Upload without explicit sizing resolves to editable-output so source/background images do not own product output size.",
|
|
31
32
|
"Use intrinsic-media only for true media-viewer or source-native apps where the natural uploaded/generated media size is the product output; record the reason and prove it with intrinsic-media-size acceptance.",
|
|
32
33
|
"Use editable-output for generated, exportable, shader, poster, badge, wall, banner, thumbnail, procedural, reference-clone, and product-output apps so users always see Aspect ratio, Canvas width, and Canvas height.",
|
|
@@ -104,6 +105,7 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
|
|
|
104
105
|
runtimeSetupModeRowRule,
|
|
105
106
|
runtimeSetupModeHelpRule,
|
|
106
107
|
"Keep sticky footer panelActions for product delivery actions only, such as Export PNG, Export Video, Copy, Generate, Apply, or Download.",
|
|
108
|
+
"Typed Export PNG and Export Video actions are runtime-owned and consume one ToolcraftAppComposition.exportRenderer; use onPanelAction only for non-export product actions.",
|
|
107
109
|
],
|
|
108
110
|
capabilities: ["settings-import-export"],
|
|
109
111
|
commands: ["controls.setValue", "timeline.setCurrentTime", "timeline.setDuration"],
|
|
@@ -126,11 +128,12 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
|
|
|
126
128
|
"A generic canvas hash difference is not enough for workload or semantic controls; assert the intended direction of the effect.",
|
|
127
129
|
"Component variants are accepted entities too; tests should fail if a non-default Toolcraft control variant falls back to the default variant or custom markup.",
|
|
128
130
|
"Conditional entities require fixtures that make the condition observable.",
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
131
|
+
'Every generated product control declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only and fail starter acceptance.',
|
|
132
|
+
"Conditional applicability combines every predicate with AND, hides controls outside mode/type/source/include/variant/count branches, and preserves their runtime values while hidden.",
|
|
133
|
+
"A visible product control must prove its existing product outcome in every supported finite sibling-selector branch from its Control Section Inventory, including always controls and selectors omitted from its predicates.",
|
|
134
|
+
"When applicability references a selector for the same product entity or selected branch, keep the selector and its dependent controls in one semantic section. A section that exists only because one selector option is active is not a separate product section just because the branch uses a standalone control.",
|
|
135
|
+
"When a count/quantity control determines how many sibling controls are applicable, conditional applicability hides unavailable siblings; do not render all possible controls while the renderer reads only the first N.",
|
|
136
|
+
"Do not use schema disabled: true or disabledWhen for generated product availability; runtime primitives may use disabled styling internally, but product controls outside the active branch are absent.",
|
|
134
137
|
"Do not leave inactive conditional controls visible while making the renderer ignore them.",
|
|
135
138
|
"Before controls or canvas interactions, declare typed interactionOwnership for every operation that could plausibly live on either surface. User request, inspected reference, or product usability selects one primary owner.",
|
|
136
139
|
"Do not mirror one operation across canvas and panel, even with different labels, ids, styling, or custom chrome. The same target and operation capability has one surface owner.",
|
|
@@ -237,9 +240,10 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
|
|
|
237
240
|
"A concise property label such as Speed, Color, Size, or Opacity is allowed when the nearest visible section or group clearly names the affected product entity.",
|
|
238
241
|
"When the section is generic, mixed, missing, or otherwise weak context, include the affected entity or role in the label: Pattern color, Background opacity, Wave speed, Stroke width.",
|
|
239
242
|
"Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
+
"One to seven controls is the normal section size. Eight to ten controls are allowed only for one cohesive entity and require semanticGroup on every control; ten is the hard maximum.",
|
|
244
|
+
"Every Control Section Inventory entry declares required entityId, entity, exact targets, and groupingReason. One entity with ten or fewer controls stays in one section regardless of control type, visual height, or target namespace.",
|
|
245
|
+
"An entity above ten controls splits into balanced workflow sections of two to ten controls. Every split section keeps the same entityId and entity and declares a unique workflowStage plus splitReason.",
|
|
246
|
+
"Section splitting must preserve dependency cohesion: a selector stays with the applicability-gated controls it owns when they share the same target entity or selected branch. Use internal spacing/dividers or a more specific section title before splitting dependent branch controls away.",
|
|
243
247
|
"Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created Setup renders as the first visible headerless controls block with no title, reset action, collapse button, or collapsed state; sticky footer action sections use the technical title Export but render without a visible heading.",
|
|
244
248
|
"Every visible app-authored controls-panel section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon; generated apps must not hand-build section headers.",
|
|
245
249
|
"Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
|
|
@@ -318,13 +322,14 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
|
|
|
318
322
|
"Before choosing no timeline for any animated product, write an Animation Intent Inventory: product transport, editable keyframes, or autonomous decorative output, plus the user-facing time behaviors present or intentionally absent.",
|
|
319
323
|
'User-requested product animation defaults to panels.timeline mode "playback" unless the spec explicitly declares autonomous decorative/self-running output with no play, pause, scrub, duration, loop, export-at-time behavior, or video export.',
|
|
320
324
|
'Any product app with Export Video must enable the top Toolcraft timeline: use panels.timeline mode "playback" for product animation transport, or mode "keyframes" when exported animation is driven by keyframes.',
|
|
325
|
+
'Export Video is valid only when productReadiness.exportIntent.video is "user-requested" with non-empty explicit user-request evidence; animation, playback, keyframes, and timeline presence never authorize video export.',
|
|
321
326
|
'Use panels.timeline: { mode: "playback" } when the product needs user-facing play, pause, scrubbing, duration, loop, restart, time progress, export-at-time controls, or video export.',
|
|
322
327
|
'When panels.timeline is enabled for a new Toolcraft app, appTransferMode.animationIntent must match it: mode "timeline-playback" for playback, or mode "timeline-keyframes" for keyframes.',
|
|
323
328
|
"Playback renderers must consume runtime timeline state; pause freezes output, scrubbing renders a deterministic frame, and the full animation cycle maps to state.timeline.durationSeconds instead of a local fixed duration.",
|
|
324
329
|
"When the product has a known loop duration, declare it as panels.timeline.defaultDurationSeconds; the runtime timeline duration starts from that loop duration instead of an unrelated 8s default.",
|
|
325
330
|
'Timeline animation intent must declare loopDuration with source "reference", "user-request", or "product-derived", plus seconds and evidence; runtime/template fallback 8s is not a valid source.',
|
|
326
331
|
"panels.timeline.defaultDurationSeconds must match animationIntent.loopDuration.seconds for playback/keyframe animation, so the initial timeline UI shows the declared product loop instead of a generic default.",
|
|
327
|
-
"Video export
|
|
332
|
+
"Video export frames are rendered deterministically from the runtime-owned 30 FPS timeline schedule; product code must not use autonomous wall-clock time, captureStream, or MediaRecorder as the source or fallback for product duration.",
|
|
328
333
|
"Playback renderers may compute an initial loop duration default during app initialization or reset, but must not watch state.timeline.durationSeconds and dispatch timeline.setDuration back to a computed local duration. User-edited timeline duration becomes the loop duration source of truth after initialization or reset.",
|
|
329
334
|
"Playback renderers should use getToolcraftTimelineLoopTime or getToolcraftTimelineLoopProgress to derive product loop phase from state.timeline.currentTimeSeconds and state.timeline.durationSeconds; do not hand-roll wall-clock, fixed-duration, mirror, yoyo, ping-pong, or reverse phase math.",
|
|
330
335
|
"Product animation loop means a seamless forward-only cycle by default: motion advances in one direction, first and last frames stitch without a visible jump, and mirror/yoyo/ping-pong/reverse loops require explicit user request.",
|
|
@@ -26,6 +26,7 @@ describe("Toolcraft template component contracts: core", () => {
|
|
|
26
26
|
"checkbox",
|
|
27
27
|
"actions",
|
|
28
28
|
"collectionActions",
|
|
29
|
+
"sourceCollection",
|
|
29
30
|
"panelActions",
|
|
30
31
|
"colorOpacity",
|
|
31
32
|
"palette",
|
|
@@ -72,6 +73,7 @@ describe("Toolcraft template component contracts: core", () => {
|
|
|
72
73
|
"palette",
|
|
73
74
|
"actions",
|
|
74
75
|
"collectionActions",
|
|
76
|
+
"sourceCollection",
|
|
75
77
|
"panelActions",
|
|
76
78
|
"customControl",
|
|
77
79
|
] as const) {
|
|
@@ -54,8 +54,10 @@ describe("Toolcraft template decision contract", () => {
|
|
|
54
54
|
|
|
55
55
|
expect(rule?.level).toBe("invariant");
|
|
56
56
|
expect(rule?.desiredBehavior).toMatch(/sceneBoundsProvider/i);
|
|
57
|
-
expect(rule?.desiredBehavior).toMatch(/
|
|
58
|
-
expect(rule?.desiredBehavior).toMatch(/
|
|
57
|
+
expect(rule?.desiredBehavior).toMatch(/useToolcraftProductSceneFrame/i);
|
|
58
|
+
expect(rule?.desiredBehavior).toMatch(/live infinite preview/i);
|
|
59
|
+
expect(rule?.desiredBehavior).toMatch(/one exact state/i);
|
|
60
|
+
expect(rule?.desiredBehavior).toMatch(/runtime-owned timestamp schedule/i);
|
|
59
61
|
expect(rule?.desiredBehavior).toMatch(/finite canvas size/i);
|
|
60
62
|
expect(rule?.enforcement).toContain("acceptance-validator");
|
|
61
63
|
expect(rule?.enforcement).toContain("browser-helper");
|
|
@@ -116,6 +118,22 @@ describe("Toolcraft template decision contract", () => {
|
|
|
116
118
|
);
|
|
117
119
|
});
|
|
118
120
|
|
|
121
|
+
it("requires artifact delivery to correspond exactly to explicit product export intent", () => {
|
|
122
|
+
const rule = getToolcraftDecisionRule("output-export-required");
|
|
123
|
+
|
|
124
|
+
expect(rule?.level).toBe("invariant");
|
|
125
|
+
expect(rule?.desiredBehavior).toMatch(/productReadiness\.exportIntent/);
|
|
126
|
+
expect(rule?.desiredBehavior).toMatch(/image[^.]*Toolcraft default/i);
|
|
127
|
+
expect(rule?.desiredBehavior).toMatch(/video[^.]*explicit user request[^.]*evidence/i);
|
|
128
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
129
|
+
/animation, playback, keyframes, or timeline[^.]*never/i,
|
|
130
|
+
);
|
|
131
|
+
expect(rule?.desiredBehavior).toMatch(/image[^.]*removed[^.]*explicit user[^.]*evidence/i);
|
|
132
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
133
|
+
/intent[^.]*correspond exactly[^.]*schema actions, settings sections, and acceptance coverage/i,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
119
137
|
it("makes renderer technology a default with escape hatches", () => {
|
|
120
138
|
const rule = getToolcraftDecisionRule("renderer-technique-inventory");
|
|
121
139
|
|
|
@@ -228,10 +246,28 @@ describe("Toolcraft template decision contract", () => {
|
|
|
228
246
|
expect(rule?.desiredBehavior).toMatch(/performance-iteration/i);
|
|
229
247
|
expect(rule?.desiredBehavior).toMatch(/bounded targeted iteration/i);
|
|
230
248
|
expect(rule?.desiredBehavior).toMatch(
|
|
231
|
-
/
|
|
249
|
+
/only a localized complaint or a post-clarification targeted choice creates performance-iteration intent and one bounded iteration over exact canonical affected paths/iu,
|
|
250
|
+
);
|
|
251
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
252
|
+
/classifier output establishes complaint authority only and never path localization/iu,
|
|
232
253
|
);
|
|
233
254
|
expect(rule?.desiredBehavior).toMatch(
|
|
234
|
-
/
|
|
255
|
+
/regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority/iu,
|
|
256
|
+
);
|
|
257
|
+
expect(rule?.desiredBehavior).not.toMatch(
|
|
258
|
+
/one complaint maps to one exact authority-selected targeted performance iteration/iu,
|
|
259
|
+
);
|
|
260
|
+
expect(rule?.desiredBehavior).not.toMatch(
|
|
261
|
+
/direct and repeated performance complaints each start one bounded targeted iteration/iu,
|
|
262
|
+
);
|
|
263
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
264
|
+
/localized complaint[\s\S]*without asking the user/iu,
|
|
265
|
+
);
|
|
266
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
267
|
+
/ambiguous or unresolved localization[\s\S]*one user-facing clarification[\s\S]*targeted diagnosis or a complete review/iu,
|
|
268
|
+
);
|
|
269
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
270
|
+
/repetition alone never expands scope or selects the complete performance matrix/i,
|
|
235
271
|
);
|
|
236
272
|
expect(rule?.desiredBehavior).toMatch(
|
|
237
273
|
/explicit natural-language request for the complete audit/i,
|
|
@@ -244,6 +280,9 @@ describe("Toolcraft template decision contract", () => {
|
|
|
244
280
|
expect(rule?.desiredBehavior).toMatch(
|
|
245
281
|
/renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled/i,
|
|
246
282
|
);
|
|
283
|
+
expect(rule?.desiredBehavior).toMatch(
|
|
284
|
+
/every measured path[\s\S]*devicePixelRatio × 2[\s\S]*each measured phase/iu,
|
|
285
|
+
);
|
|
247
286
|
expect(rule?.desiredBehavior).toMatch(
|
|
248
287
|
/never downsample[\s\S]*clamp the selected render scale to pass a performance budget/i,
|
|
249
288
|
);
|
|
@@ -58,7 +58,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
58
58
|
area: "runtime-shell",
|
|
59
59
|
currentConstraint: "Apps must assemble through defineToolcraft and ToolcraftApp.",
|
|
60
60
|
desiredBehavior:
|
|
61
|
-
"Generated apps use the Toolcraft runtime shell and keep product-specific rendering inside supported extension points: schema controls, canvasContent product output, controlRenderers for true custom controls, ToolcraftApp onPanelAction, and runtime commands. App-specific code must not import or render low-level runtime surfaces or built-in control components directly.",
|
|
61
|
+
"Generated apps use the Toolcraft runtime shell and keep product-specific rendering inside supported extension points: schema controls, bounded canvasContent product output, editor-only full-viewport infiniteCanvasContent product output, controlRenderers for true custom controls, one shared ToolcraftAppComposition.exportRenderer for typed image/video actions, ToolcraftApp onPanelAction for non-export product actions, and runtime commands. App-specific code must not import or render low-level runtime surfaces or built-in control components directly, instantiate encoders, or own artifact download mechanics.",
|
|
62
62
|
enforcement: [
|
|
63
63
|
"cli-integrity-check",
|
|
64
64
|
"acceptance-validator",
|
|
@@ -87,7 +87,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
87
87
|
currentConstraint:
|
|
88
88
|
"The Toolcraft canvas shell owns the visible workspace backing behind product output.",
|
|
89
89
|
desiredBehavior:
|
|
90
|
-
"Generated apps preserve the runtime canvas surface and do not replace or hide it when product renderers customize their own background. In infinite mode the shell fills the complete viewport with the selected standard Background color, while bounded product background pixels remain suppressed.",
|
|
90
|
+
"Generated apps preserve the runtime canvas surface and do not replace or hide it when product renderers customize their own background. In infinite mode the shell fills the complete viewport with the selected standard Background color, while bounded product background pixels remain suppressed. Preview-only product environments may layer through infiniteCanvasContent across the complete viewport under the transformed world; that layer is pointer-transparent and excluded from scene bounds and export.",
|
|
91
91
|
enforcement: ["browser-helper", "starter-agents", "spec-checklist"],
|
|
92
92
|
id: "canvas-surface-preserved",
|
|
93
93
|
level: "invariant",
|
|
@@ -99,7 +99,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
99
99
|
currentConstraint:
|
|
100
100
|
"Editable output can switch from a finite artboard to an unbounded workspace, but product renderers and export actions still need one canonical world-space frame contract.",
|
|
101
101
|
desiredBehavior:
|
|
102
|
-
"Editable-output apps expose one runtime-owned Infinity canvas mode. Enabling it removes finite artboard clipping and size controls while preserving the dormant finite canvas size for exact restoration. When the standard Background pair exists, its selected color fills the complete infinite viewport and Background off atomically resolves finite mode and disables Infinity. Runtime image and model assets retain explicit world-space frames. ToolcraftAppComposition.sceneBoundsProvider supplies product-renderer bounds through one direct composition boundary
|
|
102
|
+
"Editable-output apps expose one runtime-owned Infinity canvas mode. Enabling it removes finite artboard clipping and size controls while preserving the dormant finite canvas size for exact restoration. When the standard Background pair exists, its selected color fills the complete infinite viewport and Background off atomically resolves finite mode and disables Infinity. Runtime image and model assets retain explicit world-space frames. ToolcraftAppComposition.sceneBoundsProvider supplies product-renderer bounds for one exact state through one direct composition boundary. Runtime applies that same product frame to the live infinite preview, and custom raster/WebGL renderers consume useToolcraftProductSceneFrame for backing size and world-to-local translation instead of the dormant finite canvas size. ToolcraftAppComposition.infiniteCanvasContent is reserved for preview-only product environments that fill the complete Infinity viewport without world transforms, pointer input, scene-bounds participation, or export inclusion. Image export resolves the current frame and video export unions every state from the runtime-owned timestamp schedule. Hidden layers, suppressed default consumers, viewport environments, and editor-only UI do not expand preview or output bounds. Finite preview and export remain the full finite canvas and do not call the provider, while empty, unavailable, and unsafe oversized infinite exports return typed visible failures.",
|
|
103
103
|
enforcement: [
|
|
104
104
|
"acceptance-validator",
|
|
105
105
|
"browser-helper",
|
|
@@ -203,9 +203,9 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
203
203
|
{
|
|
204
204
|
area: "controls",
|
|
205
205
|
currentConstraint:
|
|
206
|
-
"Every visible
|
|
206
|
+
"Every product control explicitly declares always or conditional applicability; every visible finite sibling branch binds to runtime state, resets from schema defaults, and passes its accepted browser outcome.",
|
|
207
207
|
desiredBehavior:
|
|
208
|
-
"
|
|
208
|
+
"Non-applicable controls are absent with values preserved, and no always or conditional control remains visible in a finite product branch where its value is ignored.",
|
|
209
209
|
enforcement: ["acceptance-validator", "browser-helper"],
|
|
210
210
|
id: "controls-product-coverage",
|
|
211
211
|
level: "invariant",
|
|
@@ -215,13 +215,13 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
215
215
|
{
|
|
216
216
|
area: "controls",
|
|
217
217
|
currentConstraint:
|
|
218
|
-
"Product
|
|
218
|
+
"Product artifact delivery must match the required productReadiness.exportIntent declaration.",
|
|
219
219
|
desiredBehavior:
|
|
220
|
-
'
|
|
220
|
+
'Every product declares productReadiness.exportIntent. Image export starts as the Toolcraft default and is removed only with explicit user-removal evidence; video export requires explicit user request evidence. Animation, playback, keyframes, or timeline presence never changes artifact delivery intent. Resolved intent capabilities correspond exactly to schema actions, settings sections, and acceptance coverage: image-only, image-plus-video, video-only, and explicit no-export products expose only the runtime-owned artifact surfaces their intent enables. Product apps declare the standard background pair and runtime places it in Setup: Background beside Infinity canvas, Color below, and Timeline last. One shared product exportRenderer draws deterministic scene-coordinate frames for enabled artifact types; runtime owns settings, scene crop, PNG/JPEG/video background semantics, visible runtime media/model composition, exact timestamped encoding, download, progress, and typed failures.',
|
|
221
221
|
enforcement: ["acceptance-validator", "performance-validator", "browser-helper", "starter-agents"],
|
|
222
222
|
id: "output-export-required",
|
|
223
223
|
level: "invariant",
|
|
224
|
-
title: "Product output
|
|
224
|
+
title: "Product output follows artifact export intent",
|
|
225
225
|
verdict: "move-to-validator",
|
|
226
226
|
},
|
|
227
227
|
{
|
|
@@ -229,7 +229,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
229
229
|
currentConstraint:
|
|
230
230
|
"Every product controls section must be represented in the exported Control Section Inventory before schema authoring.",
|
|
231
231
|
desiredBehavior:
|
|
232
|
-
"Generated product apps publish a typed Control Section Inventory with
|
|
232
|
+
"Generated product apps publish a typed Control Section Inventory with stable entityId, human-readable entity, exact targets, and a concrete grouping reason. One entity stays in one section through ten controls; larger entities use balanced two-to-ten-control workflow stages with explicit split evidence, so section ownership and reset boundaries are machine-checkable without runtime rewriting.",
|
|
233
233
|
enforcement: ["acceptance-validator", "docs", "starter-agents", "spec-checklist"],
|
|
234
234
|
id: "controls-section-inventory-required",
|
|
235
235
|
level: "invariant",
|
|
@@ -253,7 +253,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
|
|
|
253
253
|
currentConstraint:
|
|
254
254
|
"Labels, color placement, section grouping, selector order, and inline density need product-aware decisions.",
|
|
255
255
|
desiredBehavior:
|
|
256
|
-
"Given a valid Control Section Inventory, ordering,
|
|
256
|
+
"Given a valid Control Section Inventory, ordering, labels, color placement, and compact density remain product-aware recommendations. The ten-control maximum and entity cohesion are inventory invariants; heuristics may report questionable presentation without rejecting a coherent product-specific choice solely because it differs from a preferred layout.",
|
|
257
257
|
enforcement: ["acceptance-validator", "schema-normalization", "docs", "starter-agents"],
|
|
258
258
|
id: "controls-layout-heuristics",
|
|
259
259
|
level: "heuristic",
|
|
@@ -26,6 +26,8 @@ describe("Toolcraft canonical performance verification policy", () => {
|
|
|
26
26
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY).toEqual({
|
|
27
27
|
agentBrowser:
|
|
28
28
|
"Agent-controlled browser checks are limited to diagnosis and targeted visual investigation; they do not mint durable performance baselines or receipts.",
|
|
29
|
+
complaintRouting:
|
|
30
|
+
"Performance authority and path localization are separate decisions. Classifier output establishes complaint authority only and never path localization. A localized complaint lets the agent select canonical affected paths and run one targeted iteration without asking the user. An ambiguous or unresolved localization requires one user-facing clarification naming visible operations and offering targeted diagnosis or a complete review; never ask the user for internal path ids. Regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority. A broad or honestly unlocalizable problem may justify recommending the complete review immediately, but only the user's explicit request or acceptance authorizes it. A direct request for a complete review runs the full audit without another clarification.",
|
|
29
31
|
deliveryBatch:
|
|
30
32
|
"Classify and verify one coherent user-visible delivery batch instead of treating each internal implementation pass or steering correction as a separate aggregate verification boundary.",
|
|
31
33
|
deliveryCommand:
|
|
@@ -37,13 +39,13 @@ describe("Toolcraft canonical performance verification policy", () => {
|
|
|
37
39
|
fullAuditDiscovery:
|
|
38
40
|
"The complete performance matrix remains outside conversational delivery. Complaints, repetition, tier, filename, and touched subsystem never launch it automatically. After two consecutive compatible protected performance iterations, the agent must offer a slower complete audit if the user remains unsatisfied; a demonstrably broad or unlocalizable cross-system problem may justify the same offer earlier. An explicit natural-language request for the complete audit, or explicit acceptance of the agent's offer, authorizes the agent acting as operator to run pnpm verify:perf; the user does not need to know the command name. After the audit, repair and rerun failed paths with focused checks, then run the complete matrix only once at the requested certification boundary.",
|
|
39
41
|
performanceIterationDelivery:
|
|
40
|
-
"The runtime request classifier is a tri-state guard, not a replacement for AI semantic judgment: high-confidence runtime complaints, responsiveness optimization, or excessive CPU, GPU, memory, or resource use classify as performance-iteration; high-confidence product motion, speed, freeze, terminology, or locally negated complaint commands classify as ordinary-product-work; ambiguous or unrecognized wording classifies as needs-agent-judgment and the AI decides from the actual user request. Ordinary work is rejected only for a high-confidence performance iteration. Every performance-iteration worklog intent quotes a nontrivial exact raw substring of Request; whitespace and Unicode code units must match before normalized tri-state classification. Invented, whitespace-collapsed, NFKC-equivalent, or mismatched evidence is rejected; high-confidence ordinary evidence is rejected; exact high-confidence performance-iteration or needs-agent-judgment evidence is accepted under agent ownership. Each performance iteration requires an exact reachable development fixture and fails with a configuration error instead of falling back to maximum.
|
|
42
|
+
"The runtime request classifier is a tri-state guard, not a replacement for AI semantic judgment: high-confidence runtime complaints, responsiveness optimization, or excessive CPU, GPU, memory, or resource use classify as performance-iteration; high-confidence product motion, speed, freeze, terminology, or locally negated complaint commands classify as ordinary-product-work; ambiguous or unrecognized wording classifies as needs-agent-judgment and the AI decides from the actual user request. Ordinary work is rejected only for a high-confidence performance iteration. Every performance-iteration worklog intent quotes a nontrivial exact raw substring of Request; whitespace and Unicode code units must match before normalized tri-state classification. Invented, whitespace-collapsed, NFKC-equivalent, or mismatched evidence is rejected; high-confidence ordinary evidence is rejected; exact high-confidence performance-iteration evidence, or needs-agent-judgment evidence after semantic resolution, is accepted under agent ownership. Classifier output establishes complaint authority only and never path localization. Each performance iteration requires an exact reachable development fixture and fails with a configuration error instead of falling back to maximum. Only a localized complaint or a post-clarification targeted choice creates performance-iteration intent and one bounded iteration over exact canonical affected paths. Regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority. Each repeated localized request may create one new bounded targeted iteration over its affected paths; repetition alone never expands scope or selects the complete performance matrix, creates or refreshes a durable baseline, or grants full-performance certification authority.",
|
|
41
43
|
functionalInitialDelivery:
|
|
42
44
|
'A functional delivery plan with basis "initial" runs complete functional proof and no measured performance. It preserves any independent full-performance baseline and cannot claim targeted or full performance evidence.',
|
|
43
45
|
functionalTargetedDelivery:
|
|
44
46
|
'A functional delivery plan with basis "changed" runs exact affected ownership-derived functional proof and no measured performance. Functional delivery cannot silently invoke targeted performance, the full performance matrix, or create or refresh a performance baseline.',
|
|
45
47
|
renderScaleFidelity:
|
|
46
|
-
'canvas.renderScale is visible functional fidelity. Raster products declare typed renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled, and prove real canvas backing pixels in every declared state. Never downsample, stretch a lower-resolution backing canvas, blur output, or clamp the selected render scale to pass a performance budget.',
|
|
48
|
+
'canvas.renderScale is visible functional fidelity. Raster products declare typed renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled, and prove real canvas backing pixels in every declared state. Every measured path in a render-scale-enabled raster product proves actual CSS × devicePixelRatio × 2 backing after each measured phase. Never downsample, stretch a lower-resolution backing canvas, blur output, or clamp the selected render scale to pass a performance budget.',
|
|
47
49
|
});
|
|
48
50
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_LIFECYCLE).toEqual({
|
|
49
51
|
delivery: {
|
|
@@ -87,7 +89,7 @@ describe("Toolcraft canonical performance verification policy", () => {
|
|
|
87
89
|
/ambiguous or unrecognized wording classifies as needs-agent-judgment/i,
|
|
88
90
|
);
|
|
89
91
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
90
|
-
/high-confidence performance-iteration or needs-agent-judgment evidence is accepted under agent ownership/i,
|
|
92
|
+
/exact high-confidence performance-iteration evidence, or needs-agent-judgment evidence after semantic resolution, is accepted under agent ownership/i,
|
|
91
93
|
);
|
|
92
94
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
93
95
|
/whitespace and Unicode code units must match/i,
|
|
@@ -96,13 +98,31 @@ describe("Toolcraft canonical performance verification policy", () => {
|
|
|
96
98
|
/performance iteration requires an exact reachable development fixture/i,
|
|
97
99
|
);
|
|
98
100
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
99
|
-
/
|
|
101
|
+
/only a localized complaint or a post-clarification targeted choice creates performance-iteration intent and one bounded iteration over exact canonical affected paths/iu,
|
|
100
102
|
);
|
|
101
103
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
102
|
-
/
|
|
104
|
+
/regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority/iu,
|
|
103
105
|
);
|
|
104
106
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
105
|
-
/
|
|
107
|
+
/each repeated localized request may create one new bounded targeted iteration/iu,
|
|
108
|
+
);
|
|
109
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).not.toMatch(
|
|
110
|
+
/one complaint maps to one exact authority-selected targeted performance iteration/iu,
|
|
111
|
+
);
|
|
112
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).not.toMatch(
|
|
113
|
+
/direct and repeated performance complaints each start one bounded targeted iteration/iu,
|
|
114
|
+
);
|
|
115
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
116
|
+
/localized complaint[\s\S]*without asking the user/iu,
|
|
117
|
+
);
|
|
118
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
119
|
+
/ambiguous or unresolved localization[\s\S]*one user-facing clarification[\s\S]*targeted diagnosis or a complete review/iu,
|
|
120
|
+
);
|
|
121
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
122
|
+
/direct request for a complete review[\s\S]*without another clarification/iu,
|
|
123
|
+
);
|
|
124
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
125
|
+
/repetition alone never expands scope or selects the complete performance matrix/i,
|
|
106
126
|
);
|
|
107
127
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
108
128
|
/two consecutive compatible protected performance iterations[\s\S]*must offer/i,
|
|
@@ -122,8 +142,41 @@ describe("Toolcraft canonical performance verification policy", () => {
|
|
|
122
142
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
123
143
|
/typed renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled/i,
|
|
124
144
|
);
|
|
145
|
+
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
146
|
+
/every measured path[\s\S]*devicePixelRatio × 2[\s\S]*each measured phase/iu,
|
|
147
|
+
);
|
|
125
148
|
expect(TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT).toMatch(
|
|
126
149
|
/never downsample[\s\S]*clamp the selected render scale to pass a performance budget/i,
|
|
127
150
|
);
|
|
128
151
|
});
|
|
152
|
+
|
|
153
|
+
it.each([
|
|
154
|
+
{
|
|
155
|
+
axis: "high-confidence but unlocalized complaint",
|
|
156
|
+
expected:
|
|
157
|
+
/classifier output establishes complaint authority only and never path localization[\s\S]*regardless of whether the classifier returned high-confidence performance-iteration[\s\S]*unresolved localization creates neither performance-iteration intent nor canonical path authority/iu,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
axis: "localized complaint",
|
|
161
|
+
expected:
|
|
162
|
+
/localized complaint[\s\S]*one targeted iteration without asking the user/iu,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
axis: "needs-agent-judgment with unresolved localization",
|
|
166
|
+
expected:
|
|
167
|
+
/regardless of whether the classifier returned[\s\S]*needs-agent-judgment[\s\S]*unresolved localization creates neither performance-iteration intent nor canonical path authority/iu,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
axis: "post-clarification targeted choice",
|
|
171
|
+
expected:
|
|
172
|
+
/post-clarification targeted choice creates performance-iteration intent and one bounded iteration over exact canonical affected paths/iu,
|
|
173
|
+
},
|
|
174
|
+
])("routes $axis without coupling authority to localization", ({ expected }) => {
|
|
175
|
+
const canonicalRoutingPolicy = [
|
|
176
|
+
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.complaintRouting,
|
|
177
|
+
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.performanceIterationDelivery,
|
|
178
|
+
].join(" ");
|
|
179
|
+
|
|
180
|
+
expect(canonicalRoutingPolicy).toMatch(expected);
|
|
181
|
+
});
|
|
129
182
|
});
|
|
@@ -3,6 +3,8 @@ export * from "./performance-request-classifier";
|
|
|
3
3
|
export const TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY = {
|
|
4
4
|
agentBrowser:
|
|
5
5
|
"Agent-controlled browser checks are limited to diagnosis and targeted visual investigation; they do not mint durable performance baselines or receipts.",
|
|
6
|
+
complaintRouting:
|
|
7
|
+
"Performance authority and path localization are separate decisions. Classifier output establishes complaint authority only and never path localization. A localized complaint lets the agent select canonical affected paths and run one targeted iteration without asking the user. An ambiguous or unresolved localization requires one user-facing clarification naming visible operations and offering targeted diagnosis or a complete review; never ask the user for internal path ids. Regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority. A broad or honestly unlocalizable problem may justify recommending the complete review immediately, but only the user's explicit request or acceptance authorizes it. A direct request for a complete review runs the full audit without another clarification.",
|
|
6
8
|
deliveryBatch:
|
|
7
9
|
"Classify and verify one coherent user-visible delivery batch instead of treating each internal implementation pass or steering correction as a separate aggregate verification boundary.",
|
|
8
10
|
deliveryCommand:
|
|
@@ -14,13 +16,13 @@ export const TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY = {
|
|
|
14
16
|
fullAuditDiscovery:
|
|
15
17
|
"The complete performance matrix remains outside conversational delivery. Complaints, repetition, tier, filename, and touched subsystem never launch it automatically. After two consecutive compatible protected performance iterations, the agent must offer a slower complete audit if the user remains unsatisfied; a demonstrably broad or unlocalizable cross-system problem may justify the same offer earlier. An explicit natural-language request for the complete audit, or explicit acceptance of the agent's offer, authorizes the agent acting as operator to run pnpm verify:perf; the user does not need to know the command name. After the audit, repair and rerun failed paths with focused checks, then run the complete matrix only once at the requested certification boundary.",
|
|
16
18
|
performanceIterationDelivery:
|
|
17
|
-
"The runtime request classifier is a tri-state guard, not a replacement for AI semantic judgment: high-confidence runtime complaints, responsiveness optimization, or excessive CPU, GPU, memory, or resource use classify as performance-iteration; high-confidence product motion, speed, freeze, terminology, or locally negated complaint commands classify as ordinary-product-work; ambiguous or unrecognized wording classifies as needs-agent-judgment and the AI decides from the actual user request. Ordinary work is rejected only for a high-confidence performance iteration. Every performance-iteration worklog intent quotes a nontrivial exact raw substring of Request; whitespace and Unicode code units must match before normalized tri-state classification. Invented, whitespace-collapsed, NFKC-equivalent, or mismatched evidence is rejected; high-confidence ordinary evidence is rejected; exact high-confidence performance-iteration or needs-agent-judgment evidence is accepted under agent ownership. Each performance iteration requires an exact reachable development fixture and fails with a configuration error instead of falling back to maximum.
|
|
19
|
+
"The runtime request classifier is a tri-state guard, not a replacement for AI semantic judgment: high-confidence runtime complaints, responsiveness optimization, or excessive CPU, GPU, memory, or resource use classify as performance-iteration; high-confidence product motion, speed, freeze, terminology, or locally negated complaint commands classify as ordinary-product-work; ambiguous or unrecognized wording classifies as needs-agent-judgment and the AI decides from the actual user request. Ordinary work is rejected only for a high-confidence performance iteration. Every performance-iteration worklog intent quotes a nontrivial exact raw substring of Request; whitespace and Unicode code units must match before normalized tri-state classification. Invented, whitespace-collapsed, NFKC-equivalent, or mismatched evidence is rejected; high-confidence ordinary evidence is rejected; exact high-confidence performance-iteration evidence, or needs-agent-judgment evidence after semantic resolution, is accepted under agent ownership. Classifier output establishes complaint authority only and never path localization. Each performance iteration requires an exact reachable development fixture and fails with a configuration error instead of falling back to maximum. Only a localized complaint or a post-clarification targeted choice creates performance-iteration intent and one bounded iteration over exact canonical affected paths. Regardless of whether the classifier returned high-confidence performance-iteration or needs-agent-judgment, unresolved localization creates neither performance-iteration intent nor canonical path authority. Each repeated localized request may create one new bounded targeted iteration over its affected paths; repetition alone never expands scope or selects the complete performance matrix, creates or refreshes a durable baseline, or grants full-performance certification authority.",
|
|
18
20
|
functionalInitialDelivery:
|
|
19
21
|
'A functional delivery plan with basis "initial" runs complete functional proof and no measured performance. It preserves any independent full-performance baseline and cannot claim targeted or full performance evidence.',
|
|
20
22
|
functionalTargetedDelivery:
|
|
21
23
|
'A functional delivery plan with basis "changed" runs exact affected ownership-derived functional proof and no measured performance. Functional delivery cannot silently invoke targeted performance, the full performance matrix, or create or refresh a performance baseline.',
|
|
22
24
|
renderScaleFidelity:
|
|
23
|
-
'canvas.renderScale is visible functional fidelity. Raster products declare typed renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled, and prove real canvas backing pixels in every declared state. Never downsample, stretch a lower-resolution backing canvas, blur output, or clamp the selected render scale to pass a performance budget.',
|
|
25
|
+
'canvas.renderScale is visible functional fidelity. Raster products declare typed renderScaleCoverage "selected-backing-pixels" for interaction and steady, plus playback when timeline is enabled, and prove real canvas backing pixels in every declared state. Every measured path in a render-scale-enabled raster product proves actual CSS × devicePixelRatio × 2 backing after each measured phase. Never downsample, stretch a lower-resolution backing canvas, blur output, or clamp the selected render scale to pass a performance budget.',
|
|
24
26
|
} as const;
|
|
25
27
|
|
|
26
28
|
export const TOOLCRAFT_PERFORMANCE_VERIFICATION_LIFECYCLE = {
|
|
@@ -37,6 +39,7 @@ export const TOOLCRAFT_PERFORMANCE_VERIFICATION_LIFECYCLE = {
|
|
|
37
39
|
|
|
38
40
|
export const TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY_TEXT = [
|
|
39
41
|
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.agentBrowser,
|
|
42
|
+
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.complaintRouting,
|
|
40
43
|
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.deliveryBatch,
|
|
41
44
|
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.deliveryCommand,
|
|
42
45
|
TOOLCRAFT_PERFORMANCE_VERIFICATION_POLICY.demandOnlyPerformance,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { downloadToolcraftArtifact } from "./artifact-download";
|
|
4
|
+
|
|
5
|
+
describe("downloadToolcraftArtifact", () => {
|
|
6
|
+
it("sanitizes the filename and revokes the object URL after the click", () => {
|
|
7
|
+
const events: string[] = [];
|
|
8
|
+
const anchor = {
|
|
9
|
+
click: vi.fn(() => events.push("click")),
|
|
10
|
+
download: "",
|
|
11
|
+
href: "",
|
|
12
|
+
remove: vi.fn(() => events.push("remove")),
|
|
13
|
+
};
|
|
14
|
+
const schedule = vi.fn((callback: () => void) => {
|
|
15
|
+
events.push("schedule-revoke");
|
|
16
|
+
callback();
|
|
17
|
+
return 1;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const result = downloadToolcraftArtifact({
|
|
21
|
+
appendAnchor: () => events.push("append"),
|
|
22
|
+
blob: new Blob(["png"], { type: "image/png" }),
|
|
23
|
+
createAnchor: () => anchor,
|
|
24
|
+
createObjectUrl: () => "blob:artifact",
|
|
25
|
+
extension: ".png",
|
|
26
|
+
rawBaseFileName: " Unsafe / name ",
|
|
27
|
+
revokeObjectUrl: () => events.push("revoke"),
|
|
28
|
+
schedule,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(result.fileName).toBe("Unsafe-name.png");
|
|
32
|
+
expect(anchor.download).toBe("Unsafe-name.png");
|
|
33
|
+
expect(events).toEqual([
|
|
34
|
+
"append",
|
|
35
|
+
"click",
|
|
36
|
+
"remove",
|
|
37
|
+
"schedule-revoke",
|
|
38
|
+
"revoke",
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("types click failures and still removes and revokes", () => {
|
|
43
|
+
const remove = vi.fn();
|
|
44
|
+
const revoke = vi.fn();
|
|
45
|
+
|
|
46
|
+
expect(() =>
|
|
47
|
+
downloadToolcraftArtifact({
|
|
48
|
+
appendAnchor: vi.fn(),
|
|
49
|
+
blob: new Blob(["png"]),
|
|
50
|
+
createAnchor: () => ({
|
|
51
|
+
click: () => {
|
|
52
|
+
throw new Error("blocked");
|
|
53
|
+
},
|
|
54
|
+
download: "",
|
|
55
|
+
href: "",
|
|
56
|
+
remove,
|
|
57
|
+
}),
|
|
58
|
+
createObjectUrl: () => "blob:artifact",
|
|
59
|
+
extension: ".png",
|
|
60
|
+
rawBaseFileName: "",
|
|
61
|
+
revokeObjectUrl: revoke,
|
|
62
|
+
schedule: (callback) => {
|
|
63
|
+
callback();
|
|
64
|
+
return 1;
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
).toThrow("could not start the artifact download");
|
|
68
|
+
expect(remove).toHaveBeenCalledOnce();
|
|
69
|
+
expect(revoke).toHaveBeenCalledWith("blob:artifact");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeToolcraftExportError,
|
|
3
|
+
type ToolcraftArtifactExportFailure,
|
|
4
|
+
} from "./export-error";
|
|
5
|
+
|
|
6
|
+
export type ToolcraftArtifactFileExtension =
|
|
7
|
+
| ".jpg"
|
|
8
|
+
| ".mp4"
|
|
9
|
+
| ".png"
|
|
10
|
+
| ".webm";
|
|
11
|
+
|
|
12
|
+
type ToolcraftDownloadAnchor = {
|
|
13
|
+
click: () => void;
|
|
14
|
+
download: string;
|
|
15
|
+
href: string;
|
|
16
|
+
remove: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ToolcraftArtifactDownloadRequest = Readonly<{
|
|
20
|
+
appendAnchor?: (anchor: ToolcraftDownloadAnchor) => void;
|
|
21
|
+
blob: Blob;
|
|
22
|
+
createAnchor?: () => ToolcraftDownloadAnchor;
|
|
23
|
+
createObjectUrl?: (blob: Blob) => string;
|
|
24
|
+
extension: ToolcraftArtifactFileExtension;
|
|
25
|
+
rawBaseFileName: string;
|
|
26
|
+
revokeObjectUrl?: (url: string) => void;
|
|
27
|
+
schedule?: (callback: () => void, delay: number) => unknown;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export type ToolcraftArtifactDownloadResult = Readonly<{ fileName: string }>;
|
|
31
|
+
|
|
32
|
+
export function sanitizeToolcraftArtifactBaseFileName(value: string): string {
|
|
33
|
+
const sanitized = value
|
|
34
|
+
.trim()
|
|
35
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
36
|
+
.replace(/^[.-]+|[.-]+$/g, "");
|
|
37
|
+
|
|
38
|
+
return sanitized || "toolcraft-export";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const downloadFailure: ToolcraftArtifactExportFailure = Object.freeze({
|
|
42
|
+
code: "artifact-download-failed",
|
|
43
|
+
message: "Toolcraft could not start the artifact download.",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export function downloadToolcraftArtifact(
|
|
47
|
+
request: ToolcraftArtifactDownloadRequest,
|
|
48
|
+
): ToolcraftArtifactDownloadResult {
|
|
49
|
+
const createAnchor =
|
|
50
|
+
request.createAnchor ?? (() => document.createElement("a"));
|
|
51
|
+
const appendAnchor =
|
|
52
|
+
request.appendAnchor ??
|
|
53
|
+
((anchor) => document.body.append(anchor as HTMLAnchorElement));
|
|
54
|
+
const createObjectUrl = request.createObjectUrl ?? URL.createObjectURL;
|
|
55
|
+
const revokeObjectUrl = request.revokeObjectUrl ?? URL.revokeObjectURL;
|
|
56
|
+
const schedule = request.schedule ?? globalThis.setTimeout;
|
|
57
|
+
const fileName = `${sanitizeToolcraftArtifactBaseFileName(
|
|
58
|
+
request.rawBaseFileName,
|
|
59
|
+
)}${request.extension}`;
|
|
60
|
+
let anchor: ToolcraftDownloadAnchor | null = null;
|
|
61
|
+
let objectUrl: string | null = null;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
objectUrl = createObjectUrl(request.blob);
|
|
65
|
+
anchor = createAnchor();
|
|
66
|
+
anchor.download = fileName;
|
|
67
|
+
anchor.href = objectUrl;
|
|
68
|
+
appendAnchor(anchor);
|
|
69
|
+
anchor.click();
|
|
70
|
+
return { fileName };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw normalizeToolcraftExportError(error, downloadFailure);
|
|
73
|
+
} finally {
|
|
74
|
+
anchor?.remove();
|
|
75
|
+
if (objectUrl) {
|
|
76
|
+
const urlToRevoke = objectUrl;
|
|
77
|
+
schedule(() => revokeObjectUrl(urlToRevoke), 0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ToolcraftRendererPipelineClient } from "../rendering";
|
|
2
|
+
import type {
|
|
3
|
+
ToolcraftProductSceneBoundsProvider,
|
|
4
|
+
ToolcraftRuntimeSceneVisibility,
|
|
5
|
+
} from "../scene";
|
|
6
|
+
import type { ToolcraftState } from "../state/types";
|
|
7
|
+
import type { ToolcraftExportFrame } from "./export-frame";
|
|
8
|
+
import type { ToolcraftProductExportRenderer } from "./product-export-renderer";
|
|
9
|
+
|
|
10
|
+
export type ToolcraftArtifactExportRequest = Readonly<{
|
|
11
|
+
boundsProvider: ToolcraftProductSceneBoundsProvider | undefined;
|
|
12
|
+
exportRenderer: ToolcraftProductExportRenderer | undefined;
|
|
13
|
+
productSceneRequired: boolean;
|
|
14
|
+
renderRuntimeScene: (
|
|
15
|
+
canvas: HTMLCanvasElement,
|
|
16
|
+
frame: ToolcraftExportFrame,
|
|
17
|
+
state: ToolcraftState,
|
|
18
|
+
) => Promise<unknown>;
|
|
19
|
+
rendererPipeline: ToolcraftRendererPipelineClient | null;
|
|
20
|
+
reportProgress: (progress: number) => void;
|
|
21
|
+
state: ToolcraftState;
|
|
22
|
+
visibility: ToolcraftRuntimeSceneVisibility;
|
|
23
|
+
}>;
|