@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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getToolcraftControlSectionEntityCohesionErrors,
|
|
5
|
+
hasToolcraftInventoryEntityAgreement,
|
|
6
|
+
} from "./control-section-entity-cohesion";
|
|
7
|
+
import type { ToolcraftControlSectionInventoryEntry } from "./types";
|
|
8
|
+
|
|
9
|
+
function makeSection({
|
|
10
|
+
count,
|
|
11
|
+
entity = "Renderable object",
|
|
12
|
+
entityId = "renderable-object",
|
|
13
|
+
id = "object",
|
|
14
|
+
splitReason,
|
|
15
|
+
workflowStage,
|
|
16
|
+
}: {
|
|
17
|
+
count: number;
|
|
18
|
+
entity?: string;
|
|
19
|
+
entityId?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
splitReason?: string;
|
|
22
|
+
workflowStage?: string;
|
|
23
|
+
}): ToolcraftControlSectionInventoryEntry {
|
|
24
|
+
return {
|
|
25
|
+
entity,
|
|
26
|
+
entityId,
|
|
27
|
+
groupingReason: `The ${id} controls edit one coherent part of the entity.`,
|
|
28
|
+
id,
|
|
29
|
+
splitReason,
|
|
30
|
+
targets: Array.from(
|
|
31
|
+
{ length: count },
|
|
32
|
+
(_, index) => `${id}.value${index + 1}`,
|
|
33
|
+
),
|
|
34
|
+
title: id,
|
|
35
|
+
workflowStage,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("Toolcraft control section entity cohesion", () => {
|
|
40
|
+
it.each([1, 7, 8, 10])(
|
|
41
|
+
"accepts one entity in one section with %s controls",
|
|
42
|
+
(count) => {
|
|
43
|
+
expect(
|
|
44
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
45
|
+
makeSection({ count }),
|
|
46
|
+
]),
|
|
47
|
+
).toEqual([]);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
it("rejects splitting an entity that fits in one section", () => {
|
|
52
|
+
const errors = getToolcraftControlSectionEntityCohesionErrors([
|
|
53
|
+
makeSection({ count: 3, id: "object-source" }),
|
|
54
|
+
makeSection({ count: 4, id: "object-style" }),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
expect(errors).toContain(
|
|
58
|
+
'Control Section Inventory entity "renderable-object" owns 7 controls across 2 sections (object-source, object-style). Entities with 10 or fewer controls must stay in one section.',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("rejects an entity above the hard maximum left in one section", () => {
|
|
63
|
+
expect(
|
|
64
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
65
|
+
makeSection({ count: 11 }),
|
|
66
|
+
]),
|
|
67
|
+
).toContain(
|
|
68
|
+
'Control Section Inventory entity "renderable-object" owns 11 controls in section object. Split entities above 10 controls into explicit workflow stages.',
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("accepts a balanced large-entity workflow split", () => {
|
|
73
|
+
expect(
|
|
74
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
75
|
+
makeSection({
|
|
76
|
+
count: 5,
|
|
77
|
+
id: "object-structure",
|
|
78
|
+
splitReason: "Structure is authored before the finishing workflow.",
|
|
79
|
+
workflowStage: "structure",
|
|
80
|
+
}),
|
|
81
|
+
makeSection({
|
|
82
|
+
count: 6,
|
|
83
|
+
id: "object-finish",
|
|
84
|
+
splitReason: "Finish is authored after the structural workflow.",
|
|
85
|
+
workflowStage: "finish",
|
|
86
|
+
}),
|
|
87
|
+
]),
|
|
88
|
+
).toEqual([]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("rejects a one-control tail in a split entity", () => {
|
|
92
|
+
expect(
|
|
93
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
94
|
+
makeSection({
|
|
95
|
+
count: 10,
|
|
96
|
+
id: "object-primary",
|
|
97
|
+
splitReason: "Primary editing precedes the final workflow stage.",
|
|
98
|
+
workflowStage: "primary",
|
|
99
|
+
}),
|
|
100
|
+
makeSection({
|
|
101
|
+
count: 1,
|
|
102
|
+
id: "object-final",
|
|
103
|
+
splitReason: "The final workflow follows primary editing.",
|
|
104
|
+
workflowStage: "final",
|
|
105
|
+
}),
|
|
106
|
+
]),
|
|
107
|
+
).toContain(
|
|
108
|
+
'Control Section Inventory entity "renderable-object" section object-final owns 1 control. Every section in a split entity must own between 2 and 10 controls.',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("rejects a split section above the per-section maximum", () => {
|
|
113
|
+
expect(
|
|
114
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
115
|
+
makeSection({
|
|
116
|
+
count: 11,
|
|
117
|
+
id: "object-primary",
|
|
118
|
+
splitReason: "Primary editing precedes the final workflow stage.",
|
|
119
|
+
workflowStage: "primary",
|
|
120
|
+
}),
|
|
121
|
+
makeSection({
|
|
122
|
+
count: 2,
|
|
123
|
+
id: "object-final",
|
|
124
|
+
splitReason: "The final workflow follows primary editing.",
|
|
125
|
+
workflowStage: "final",
|
|
126
|
+
}),
|
|
127
|
+
]),
|
|
128
|
+
).toContain(
|
|
129
|
+
'Control Section Inventory entity "renderable-object" section object-primary owns 11 controls. Every section in a split entity must own between 2 and 10 controls.',
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("requires a workflow stage on every section of a split entity", () => {
|
|
134
|
+
expect(
|
|
135
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
136
|
+
makeSection({
|
|
137
|
+
count: 6,
|
|
138
|
+
id: "object-primary",
|
|
139
|
+
splitReason: "Primary editing precedes the final workflow stage.",
|
|
140
|
+
}),
|
|
141
|
+
makeSection({
|
|
142
|
+
count: 6,
|
|
143
|
+
id: "object-final",
|
|
144
|
+
splitReason: "The final workflow follows primary editing.",
|
|
145
|
+
workflowStage: "final",
|
|
146
|
+
}),
|
|
147
|
+
]),
|
|
148
|
+
).toContain(
|
|
149
|
+
'Control Section Inventory entity "renderable-object" section object-primary must declare workflowStage because the entity is split across sections.',
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("requires complete, unique split evidence and a consistent entity name", () => {
|
|
154
|
+
const errors = getToolcraftControlSectionEntityCohesionErrors([
|
|
155
|
+
makeSection({ count: 6, id: "object-a", workflowStage: "edit" }),
|
|
156
|
+
makeSection({
|
|
157
|
+
count: 6,
|
|
158
|
+
entity: "Different label",
|
|
159
|
+
id: "object-b",
|
|
160
|
+
splitReason: "This section follows the first editing stage.",
|
|
161
|
+
workflowStage: "edit",
|
|
162
|
+
}),
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
expect(errors).toEqual(
|
|
166
|
+
expect.arrayContaining([
|
|
167
|
+
expect.stringContaining("uses inconsistent entity names"),
|
|
168
|
+
expect.stringContaining("object-a must declare splitReason"),
|
|
169
|
+
expect.stringContaining('repeats workflowStage "edit"'),
|
|
170
|
+
]),
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("keeps independent entities independent", () => {
|
|
175
|
+
expect(
|
|
176
|
+
getToolcraftControlSectionEntityCohesionErrors([
|
|
177
|
+
makeSection({ count: 4, entityId: "entity-a", id: "entity-a" }),
|
|
178
|
+
makeSection({ count: 4, entityId: "entity-b", id: "entity-b" }),
|
|
179
|
+
]),
|
|
180
|
+
).toEqual([]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("requires one shared entityId for target-prefix corroboration", () => {
|
|
184
|
+
const first = makeSection({ count: 6, id: "first" });
|
|
185
|
+
const second = makeSection({
|
|
186
|
+
count: 6,
|
|
187
|
+
entityId: "different-entity",
|
|
188
|
+
id: "second",
|
|
189
|
+
});
|
|
190
|
+
const sectionInventoryById = new Map([
|
|
191
|
+
[first.id, first],
|
|
192
|
+
[second.id, second],
|
|
193
|
+
]);
|
|
194
|
+
const sections = new Set([first.id, second.id]);
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
hasToolcraftInventoryEntityAgreement({
|
|
198
|
+
sectionInventoryById,
|
|
199
|
+
sections,
|
|
200
|
+
}),
|
|
201
|
+
).toBe(false);
|
|
202
|
+
|
|
203
|
+
sectionInventoryById.set(second.id, {
|
|
204
|
+
...second,
|
|
205
|
+
entityId: first.entityId,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(
|
|
209
|
+
hasToolcraftInventoryEntityAgreement({
|
|
210
|
+
sectionInventoryById,
|
|
211
|
+
sections,
|
|
212
|
+
}),
|
|
213
|
+
).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ToolcraftControlSectionInventoryEntry } from "./types";
|
|
2
|
+
|
|
3
|
+
export const TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS = 8;
|
|
4
|
+
|
|
5
|
+
const TOOLCRAFT_MAX_CONTROLS_PER_SECTION = 10;
|
|
6
|
+
const TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION = 2;
|
|
7
|
+
const TOOLCRAFT_MIN_SPLIT_REASON_LENGTH = 12;
|
|
8
|
+
|
|
9
|
+
export function hasToolcraftInventoryEntityAgreement({
|
|
10
|
+
sectionInventoryById,
|
|
11
|
+
sections,
|
|
12
|
+
}: {
|
|
13
|
+
sectionInventoryById: ReadonlyMap<
|
|
14
|
+
string,
|
|
15
|
+
ToolcraftControlSectionInventoryEntry
|
|
16
|
+
>;
|
|
17
|
+
sections: ReadonlySet<string>;
|
|
18
|
+
}): boolean {
|
|
19
|
+
const entries = [...sections].map((sectionId) =>
|
|
20
|
+
sectionInventoryById.get(sectionId),
|
|
21
|
+
);
|
|
22
|
+
const entityIds = entries.map((entry) => entry?.entityId?.trim() ?? "");
|
|
23
|
+
|
|
24
|
+
return entityIds.every(Boolean) && new Set(entityIds).size === 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getToolcraftControlSectionEntityCohesionErrors(
|
|
28
|
+
sectionInventory: readonly ToolcraftControlSectionInventoryEntry[],
|
|
29
|
+
): string[] {
|
|
30
|
+
const errors: string[] = [];
|
|
31
|
+
const entriesByEntityId = new Map<
|
|
32
|
+
string,
|
|
33
|
+
ToolcraftControlSectionInventoryEntry[]
|
|
34
|
+
>();
|
|
35
|
+
|
|
36
|
+
for (const entry of sectionInventory) {
|
|
37
|
+
const entityId = entry.entityId?.trim() ?? "";
|
|
38
|
+
const entity = entry.entity?.trim() ?? "";
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
!entityId ||
|
|
42
|
+
!entity ||
|
|
43
|
+
(entry.id?.trim().length ?? 0) === 0 ||
|
|
44
|
+
!Array.isArray(entry.targets) ||
|
|
45
|
+
entry.targets.length === 0
|
|
46
|
+
) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const entries = entriesByEntityId.get(entityId) ?? [];
|
|
51
|
+
entries.push(entry);
|
|
52
|
+
entriesByEntityId.set(entityId, entries);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const [entityId, entries] of entriesByEntityId) {
|
|
56
|
+
const controlCount = entries.reduce(
|
|
57
|
+
(total, entry) => total + entry.targets.length,
|
|
58
|
+
0,
|
|
59
|
+
);
|
|
60
|
+
const sectionNames = entries.map((entry) => entry.title.trim() || entry.id);
|
|
61
|
+
const entityNames = new Set(entries.map((entry) => entry.entity.trim()));
|
|
62
|
+
|
|
63
|
+
if (entityNames.size > 1) {
|
|
64
|
+
errors.push(
|
|
65
|
+
`Control Section Inventory entity "${entityId}" uses inconsistent entity names: ${[
|
|
66
|
+
...entityNames,
|
|
67
|
+
].join(", ")}. Reuse one human-readable entity name for every section in the entity.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (controlCount <= TOOLCRAFT_MAX_CONTROLS_PER_SECTION) {
|
|
72
|
+
if (entries.length > 1) {
|
|
73
|
+
errors.push(
|
|
74
|
+
`Control Section Inventory entity "${entityId}" owns ${controlCount} controls across ${entries.length} sections (${sectionNames.join(", ")}). Entities with ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} or fewer controls must stay in one section.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (entries.length === 1) {
|
|
81
|
+
errors.push(
|
|
82
|
+
`Control Section Inventory entity "${entityId}" owns ${controlCount} controls in section ${sectionNames[0]}. Split entities above ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} controls into explicit workflow stages.`,
|
|
83
|
+
);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sectionsByStage = new Map<string, string[]>();
|
|
88
|
+
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const sectionName = entry.title.trim() || entry.id;
|
|
91
|
+
const sectionControlCount = entry.targets.length;
|
|
92
|
+
const workflowStage = entry.workflowStage?.trim() ?? "";
|
|
93
|
+
const splitReason = entry.splitReason?.trim() ?? "";
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
sectionControlCount < TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION ||
|
|
97
|
+
sectionControlCount > TOOLCRAFT_MAX_CONTROLS_PER_SECTION
|
|
98
|
+
) {
|
|
99
|
+
errors.push(
|
|
100
|
+
`Control Section Inventory entity "${entityId}" section ${sectionName} owns ${sectionControlCount} ${sectionControlCount === 1 ? "control" : "controls"}. Every section in a split entity must own between ${TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION} and ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} controls.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!workflowStage) {
|
|
105
|
+
errors.push(
|
|
106
|
+
`Control Section Inventory entity "${entityId}" section ${sectionName} must declare workflowStage because the entity is split across sections.`,
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
const normalizedStage = workflowStage.toLowerCase();
|
|
110
|
+
const stageSections = sectionsByStage.get(normalizedStage) ?? [];
|
|
111
|
+
stageSections.push(sectionName);
|
|
112
|
+
sectionsByStage.set(normalizedStage, stageSections);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (splitReason.length < TOOLCRAFT_MIN_SPLIT_REASON_LENGTH) {
|
|
116
|
+
errors.push(
|
|
117
|
+
`Control Section Inventory entity "${entityId}" section ${sectionName} must declare splitReason of at least ${TOOLCRAFT_MIN_SPLIT_REASON_LENGTH} characters because the entity is split across sections.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const [stage, stageSections] of sectionsByStage) {
|
|
123
|
+
if (stageSections.length > 1) {
|
|
124
|
+
errors.push(
|
|
125
|
+
`Control Section Inventory entity "${entityId}" repeats workflowStage "${stage}" across sections ${stageSections.join(", ")}. Workflow stages must be unique within one entity.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return errors;
|
|
132
|
+
}
|
|
@@ -40,6 +40,7 @@ function createSchema({
|
|
|
40
40
|
|
|
41
41
|
const inventoryEntry = {
|
|
42
42
|
entity: "Appearance",
|
|
43
|
+
entityId: "appearance",
|
|
43
44
|
groupingReason: "These controls define the visible appearance of the output.",
|
|
44
45
|
id: "appearance",
|
|
45
46
|
targets: ["appearance.opacity"],
|
|
@@ -56,6 +57,53 @@ describe("Toolcraft Control Section Inventory identity", () => {
|
|
|
56
57
|
).toEqual([]);
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
it("preserves inventory ownership when runtime Setup relocates product background controls", () => {
|
|
61
|
+
const schema = defineToolcraft({
|
|
62
|
+
canvas: { enabled: true },
|
|
63
|
+
panels: {
|
|
64
|
+
controls: {
|
|
65
|
+
sections: [
|
|
66
|
+
{
|
|
67
|
+
controls: {
|
|
68
|
+
background: {
|
|
69
|
+
applicability: { mode: "always" },
|
|
70
|
+
defaultValue: "#101010",
|
|
71
|
+
target: "appearance.background",
|
|
72
|
+
type: "color",
|
|
73
|
+
},
|
|
74
|
+
includeBackground: {
|
|
75
|
+
applicability: { mode: "always" },
|
|
76
|
+
defaultValue: true,
|
|
77
|
+
target: "export.includeBackground",
|
|
78
|
+
type: "switch",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
id: "background",
|
|
82
|
+
title: "Background",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
title: "Controls",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const backgroundInventory = {
|
|
90
|
+
entity: "Output background",
|
|
91
|
+
entityId: "output-background",
|
|
92
|
+
groupingReason:
|
|
93
|
+
"Inclusion and color jointly define the exported background.",
|
|
94
|
+
id: "background",
|
|
95
|
+
targets: ["export.includeBackground", "appearance.background"],
|
|
96
|
+
title: "Background",
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
expect(schema.panels.controls?.sections[0]?.id).toBe("runtime.setup");
|
|
100
|
+
expect(
|
|
101
|
+
getToolcraftControlSectionInventoryErrors(schema, [
|
|
102
|
+
backgroundInventory,
|
|
103
|
+
]),
|
|
104
|
+
).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
59
107
|
it("rejects an inventory id that does not match the schema section", () => {
|
|
60
108
|
const errors = getToolcraftControlSectionInventoryErrors(createSchema(), [
|
|
61
109
|
{ ...inventoryEntry, id: "different-section" },
|
|
@@ -80,6 +128,45 @@ describe("Toolcraft Control Section Inventory identity", () => {
|
|
|
80
128
|
);
|
|
81
129
|
});
|
|
82
130
|
|
|
131
|
+
it.each([
|
|
132
|
+
[
|
|
133
|
+
"blank entityId",
|
|
134
|
+
{ ...inventoryEntry, entityId: "" },
|
|
135
|
+
'Control Section Inventory entry "Original title" must declare a non-empty stable entityId.',
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
"blank entity",
|
|
139
|
+
{ ...inventoryEntry, entity: "" },
|
|
140
|
+
'Control Section Inventory entry "Original title" must declare a human-readable entity name.',
|
|
141
|
+
],
|
|
142
|
+
])("rejects %s", (_label, entry, expectedMessage) => {
|
|
143
|
+
expect(
|
|
144
|
+
getToolcraftControlSectionInventoryErrors(createSchema(), [entry]),
|
|
145
|
+
).toContain(expectedMessage);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("rejects a missing entityId without throwing", () => {
|
|
149
|
+
const { entityId: _entityId, ...entryWithoutEntityId } = inventoryEntry;
|
|
150
|
+
const malformedInventory = [
|
|
151
|
+
entryWithoutEntityId,
|
|
152
|
+
] as unknown as readonly ToolcraftControlSectionInventoryEntry[];
|
|
153
|
+
|
|
154
|
+
expect(() =>
|
|
155
|
+
getToolcraftControlSectionInventoryErrors(
|
|
156
|
+
createSchema(),
|
|
157
|
+
malformedInventory,
|
|
158
|
+
),
|
|
159
|
+
).not.toThrow();
|
|
160
|
+
expect(
|
|
161
|
+
getToolcraftControlSectionInventoryErrors(
|
|
162
|
+
createSchema(),
|
|
163
|
+
malformedInventory,
|
|
164
|
+
),
|
|
165
|
+
).toContain(
|
|
166
|
+
'Control Section Inventory entry "Original title" must declare a non-empty stable entityId.',
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
83
170
|
it.each([
|
|
84
171
|
["missing", undefined],
|
|
85
172
|
["non-string", 42],
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
import { getToolcraftSectionLabel } from "./sections";
|
|
8
8
|
import type { ToolcraftControlSectionInventoryEntry } from "./types";
|
|
9
9
|
|
|
10
|
+
type ToolcraftInventorySchemaSection = Readonly<{
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
targets: readonly string[];
|
|
14
|
+
}>;
|
|
15
|
+
|
|
10
16
|
function getToolcraftControlSectionInventoryEntryId(
|
|
11
17
|
entry: ToolcraftControlSectionInventoryEntry,
|
|
12
18
|
): string | null {
|
|
@@ -36,44 +42,31 @@ export function getToolcraftSectionInventoryById(
|
|
|
36
42
|
return new Map(entries);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
sectionInventoryById: ReadonlyMap<string, ToolcraftControlSectionInventoryEntry>;
|
|
44
|
-
sections: ReadonlySet<string>;
|
|
45
|
-
}): boolean {
|
|
46
|
-
const entries = [...sections].map((sectionId) =>
|
|
47
|
-
sectionInventoryById.get(sectionId),
|
|
48
|
-
);
|
|
45
|
+
function getToolcraftInventoryEntryByTarget(
|
|
46
|
+
sectionInventory: readonly ToolcraftControlSectionInventoryEntry[],
|
|
47
|
+
): ReadonlyMap<string, ToolcraftControlSectionInventoryEntry> {
|
|
48
|
+
const entries = new Map<string, ToolcraftControlSectionInventoryEntry>();
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
for (const entry of sectionInventory) {
|
|
51
|
+
for (const target of entry.targets) {
|
|
52
|
+
if (!entries.has(target)) {
|
|
53
|
+
entries.set(target, entry);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return entries;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
function collectToolcraftInventorySchemaSections(
|
|
62
62
|
schema: ResolvedToolcraftAppSchema,
|
|
63
63
|
sectionInventory: readonly ToolcraftControlSectionInventoryEntry[],
|
|
64
|
-
):
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
if (sectionInventory.length === 0) {
|
|
68
|
-
return errors;
|
|
69
|
-
}
|
|
64
|
+
): ToolcraftInventorySchemaSection[] {
|
|
65
|
+
const inventoryEntryByTarget =
|
|
66
|
+
getToolcraftInventoryEntryByTarget(sectionInventory);
|
|
70
67
|
|
|
71
|
-
|
|
68
|
+
return (schema.panels.controls?.sections ?? []).flatMap(
|
|
72
69
|
(section, sectionIndex) => {
|
|
73
|
-
if (section.id === "runtime.setup") {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
70
|
const targets = Object.values(section.controls)
|
|
78
71
|
.filter(isToolcraftProductSectionControl)
|
|
79
72
|
.map((control) => control.target);
|
|
@@ -82,15 +75,53 @@ export function getToolcraftControlSectionInventoryErrors(
|
|
|
82
75
|
return [];
|
|
83
76
|
}
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
{
|
|
78
|
+
if (section.id !== "runtime.setup") {
|
|
79
|
+
return [{
|
|
87
80
|
id: section.id,
|
|
88
81
|
label: getToolcraftSectionLabel(section.title, sectionIndex),
|
|
89
82
|
targets,
|
|
90
|
-
}
|
|
91
|
-
|
|
83
|
+
}];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const relocatedSections = new Map<string, ToolcraftInventorySchemaSection>();
|
|
87
|
+
|
|
88
|
+
for (const target of targets) {
|
|
89
|
+
const inventoryEntry = inventoryEntryByTarget.get(target);
|
|
90
|
+
const inventoryId = inventoryEntry
|
|
91
|
+
? getToolcraftControlSectionInventoryEntryId(inventoryEntry)
|
|
92
|
+
: null;
|
|
93
|
+
const id = inventoryId ?? section.id;
|
|
94
|
+
const existing = relocatedSections.get(id);
|
|
95
|
+
|
|
96
|
+
relocatedSections.set(id, {
|
|
97
|
+
id,
|
|
98
|
+
label:
|
|
99
|
+
inventoryId && inventoryEntry
|
|
100
|
+
? inventoryEntry.title
|
|
101
|
+
: getToolcraftSectionLabel(section.title, sectionIndex),
|
|
102
|
+
targets: [...(existing?.targets ?? []), target],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [...relocatedSections.values()];
|
|
92
107
|
},
|
|
93
108
|
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getToolcraftControlSectionInventoryErrors(
|
|
112
|
+
schema: ResolvedToolcraftAppSchema,
|
|
113
|
+
sectionInventory: readonly ToolcraftControlSectionInventoryEntry[],
|
|
114
|
+
): string[] {
|
|
115
|
+
const errors: string[] = [];
|
|
116
|
+
|
|
117
|
+
if (sectionInventory.length === 0) {
|
|
118
|
+
return errors;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const schemaSections = collectToolcraftInventorySchemaSections(
|
|
122
|
+
schema,
|
|
123
|
+
sectionInventory,
|
|
124
|
+
);
|
|
94
125
|
const schemaSectionIds = new Set(schemaSections.map((section) => section.id));
|
|
95
126
|
const schemaTargetToSection = new Map<string, string>();
|
|
96
127
|
|
|
@@ -107,9 +138,8 @@ export function getToolcraftControlSectionInventoryErrors(
|
|
|
107
138
|
const id = getToolcraftControlSectionInventoryEntryId(entry);
|
|
108
139
|
const title = entry.title.trim();
|
|
109
140
|
const entity = entry.entity?.trim() ?? "";
|
|
110
|
-
const
|
|
141
|
+
const entityId = entry.entityId?.trim() ?? "";
|
|
111
142
|
const groupingReason = entry.groupingReason.trim();
|
|
112
|
-
const splitReason = entry.splitReason?.trim() ?? "";
|
|
113
143
|
|
|
114
144
|
if (!id) {
|
|
115
145
|
errors.push(
|
|
@@ -133,21 +163,21 @@ export function getToolcraftControlSectionInventoryErrors(
|
|
|
133
163
|
);
|
|
134
164
|
}
|
|
135
165
|
|
|
136
|
-
if (!
|
|
166
|
+
if (!entityId) {
|
|
137
167
|
errors.push(
|
|
138
|
-
`Control Section Inventory entry "${title}" must declare
|
|
168
|
+
`Control Section Inventory entry "${title}" must declare a non-empty stable entityId.`,
|
|
139
169
|
);
|
|
140
170
|
}
|
|
141
171
|
|
|
142
|
-
if (
|
|
172
|
+
if (!entity) {
|
|
143
173
|
errors.push(
|
|
144
|
-
`Control Section Inventory entry "${title}" must
|
|
174
|
+
`Control Section Inventory entry "${title}" must declare a human-readable entity name.`,
|
|
145
175
|
);
|
|
146
176
|
}
|
|
147
177
|
|
|
148
|
-
if (
|
|
178
|
+
if (groupingReason.length < 12) {
|
|
149
179
|
errors.push(
|
|
150
|
-
`Control Section Inventory entry "${title}"
|
|
180
|
+
`Control Section Inventory entry "${title}" must include a concrete groupingReason explaining why these controls belong together.`,
|
|
151
181
|
);
|
|
152
182
|
}
|
|
153
183
|
|
|
@@ -208,7 +238,7 @@ export function getToolcraftControlSectionInventoryErrors(
|
|
|
208
238
|
|
|
209
239
|
if (!entry) {
|
|
210
240
|
errors.push(
|
|
211
|
-
`Control Section Inventory is missing product section id "${section.id}" (${section.label}). Add the same explicit id, entity
|
|
241
|
+
`Control Section Inventory is missing product section id "${section.id}" (${section.label}). Add the same explicit id, entityId, entity, targets, and groupingReason.`,
|
|
212
242
|
);
|
|
213
243
|
continue;
|
|
214
244
|
}
|
|
@@ -109,10 +109,18 @@ export function getBuiltInFitCheckErrors(
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
const ownsRepeatedItems = capabilities.some((capability) =>
|
|
113
|
+
["collection", "reorder", "selection"].includes(capability),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (ownsRepeatedItems && !checkedBuiltIns.includes("sourceCollection")) {
|
|
117
|
+
errors.push(
|
|
118
|
+
`${label} builtInFitCheck.checkedBuiltIns must include sourceCollection when the custom control owns a repeated runtime item set whose cardinality could be source-owned.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
if (
|
|
113
|
-
|
|
114
|
-
["collection", "reorder", "selection"].includes(capability),
|
|
115
|
-
) &&
|
|
123
|
+
ownsRepeatedItems &&
|
|
116
124
|
!checkedBuiltIns.includes("collectionActions")
|
|
117
125
|
) {
|
|
118
126
|
errors.push(
|