@salsita-ai/sdk 0.50.0-develop.9feae4851f → 0.50.0-develop.a4dde5f252
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/admin/create-admin-pages-from-spec.d.ts +1 -1
- package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +1 -1
- package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +36 -36
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.d.ts +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain.js +1 -1
- package/dist/application-spec/generate-application/code/templates/index.d.ts +0 -6
- package/dist/application-spec/generate-application/code/templates/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/package-json.js +1 -1
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.js +66 -42
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +10 -12
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +6 -8
- package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
- package/dist/client/phoenix-api.d.ts +5 -3
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +34 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
- package/dist/configurator/pages/DefaultConfiguratorLayout.d.ts +6 -0
- package/dist/configurator/pages/DefaultConfiguratorLayout.js +1 -0
- package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
- package/dist/docs/index.json +1 -1
- package/dist/docs/integrations/woocommerce/local-development.mdx +1 -1
- package/dist/docs/phoenix/configurator-interface.mdx +33 -6
- package/dist/docs/phoenix/expression-context.mdx +11 -3
- package/dist/docs/phoenix/getting-started.mdx +1 -1
- package/dist/docs/phoenix/phoenix-cli.mdx +17 -26
- package/dist/docs/phoenix/project-structure.mdx +5 -3
- package/dist/docs/phoenix/quickstart.mdx +2 -2
- package/dist/docs/phoenix/sdk-builder-workflow.mdx +2 -2
- package/dist/docs/phoenix/webhooks.mdx +1 -1
- package/dist/docs/ux-guidelines/choosing-a-control.mdx +11 -0
- package/dist/domain/admin-domain/implementation/common-admin-expressions.d.ts +10 -10
- package/dist/domain/admin-domain/index.d.ts +7 -2
- package/dist/domain/application/GetApplicationWrapperHandler.d.ts +1 -1
- package/dist/domain/application/GetThemeHandler.d.ts +1 -1
- package/dist/domain/application/GetWebComponentOptions.d.ts +1 -1
- package/dist/domain/application/admin-page-factories.d.ts +1 -2
- package/dist/domain/application/application-config.d.ts +3 -24
- package/dist/domain/application/assistant-mutations.d.ts +2 -1
- package/dist/domain/application/assistant-recipe-groups.d.ts +1 -1
- package/dist/domain/application/configurator-builder.d.ts +1 -1
- package/dist/domain/application/create-initial-published-data.d.ts +1 -2
- package/dist/domain/application/index.d.ts +1 -4
- package/dist/domain/application/index.js +1 -1
- package/dist/domain/application/published-data/DataRowValidatorHandler.d.ts +1 -1
- package/dist/domain/application/published-data/FilterPublishedChunksHandler.d.ts +1 -1
- package/dist/domain/application/published-data/MapPublishedChunkedDataHandler.d.ts +1 -1
- package/dist/domain/application/published-data/MapPublishedRevisionDataHandler.d.ts +1 -1
- package/dist/domain/application/published-data/SheetValidatorHandler.d.ts +1 -1
- package/dist/domain/application/published-data/build-legend-data.d.ts +1 -1
- package/dist/domain/application/published-data/format-field-type.d.ts +1 -1
- package/dist/domain/application/published-data/map-data-to-sheet.d.ts +1 -1
- package/dist/domain/application/published-data/map-published-chunked-data.d.ts +2 -2
- package/dist/domain/application/published-data/validate-sheet-data.d.ts +1 -1
- package/dist/domain/core/action/action-factory.d.ts +2 -2
- package/dist/domain/core/action/action.d.ts +19 -14
- package/dist/domain/core/component/domain-component-optic.d.ts +30 -40
- package/dist/domain/core/component/domain-component-optic.js +2 -2
- package/dist/domain/core/component/legacy-filter.d.ts +2 -17
- package/dist/domain/core/component/system-component-filter.d.ts +23 -18
- package/dist/domain/core/data-schema/data-cell-value.d.ts +4 -0
- package/dist/domain/core/data-schema/data-cell-value.js +1 -0
- package/dist/domain/core/data-schema/data-schema-array-field.d.ts +5 -0
- package/dist/domain/core/data-schema/data-schema-array-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-field-object.d.ts +4 -0
- package/dist/domain/core/data-schema/data-schema-field-object.js +1 -0
- package/dist/domain/core/data-schema/data-schema-field.d.ts +2 -0
- package/dist/domain/core/data-schema/data-schema-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-localized-field.d.ts +4 -0
- package/dist/domain/core/data-schema/data-schema-localized-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-primitive-field.d.ts +5 -0
- package/dist/domain/core/data-schema/data-schema-primitive-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema.d.ts +2 -0
- package/dist/domain/core/data-schema/data-schema.js +1 -0
- package/dist/domain/core/data-schema/index.d.ts +7 -0
- package/dist/domain/core/data-schema/index.js +1 -0
- package/dist/domain/core/domain.d.ts +2 -2
- package/dist/domain/core/emission/index.d.ts +8 -0
- package/dist/domain/core/emission/index.js +1 -0
- package/dist/domain/core/emission/reset-state-emission.d.ts +12 -0
- package/dist/domain/core/emission/reset-state-emission.js +1 -0
- package/dist/domain/core/emission/state-emission.d.ts +4 -0
- package/dist/domain/core/emission/state-emission.js +1 -0
- package/dist/domain/core/emission/state-update-emission.d.ts +14 -0
- package/dist/domain/core/emission/state-update-emission.js +1 -0
- package/dist/domain/core/emission/state-validation-result.d.ts +18 -0
- package/dist/domain/core/emission/state-validation-result.js +1 -0
- package/dist/domain/core/emission/transformation-log.d.ts +2 -0
- package/dist/domain/core/emission/transformation-log.js +1 -0
- package/dist/domain/core/emission/transformer-log-entry.d.ts +6 -0
- package/dist/domain/core/emission/transformer-log-entry.js +1 -0
- package/dist/domain/core/emission/transformer-log.d.ts +11 -0
- package/dist/domain/core/emission/transformer-log.js +1 -0
- package/dist/domain/core/emission/transformer-phase.d.ts +1 -0
- package/dist/domain/core/emission/transformer-phase.js +1 -0
- package/dist/domain/core/expression/aliased-expressions.d.ts +2 -2
- package/dist/domain/core/expression/constant-expressions.d.ts +2 -2
- package/dist/domain/core/expression/constant-expressions.js +1 -1
- package/dist/domain/core/expression/custom-expression.d.ts +6 -6
- package/dist/domain/core/expression/custom-expression.js +1 -1
- package/dist/domain/core/expression/expression.d.ts +15 -17
- package/dist/domain/core/index.d.ts +2 -0
- package/dist/domain/core/invariant/custom-invariants.d.ts +5 -5
- package/dist/domain/core/invariant/index.d.ts +1 -1
- package/dist/domain/core/invariant/index.js +1 -1
- package/dist/domain/core/invariant/invariants.d.ts +4 -67
- package/dist/domain/core/invariant/invariants.js +1 -1
- package/dist/domain/core/invariant/violation.d.ts +1 -1
- package/dist/domain/core/relation/reference-component.d.ts +2 -2
- package/dist/domain/core/state/migrations.d.ts +14 -45
- package/dist/domain/core/state/migrations.js +1 -1
- package/dist/domain/domain-builder.d.ts +1 -1
- package/dist/domain/domain-builder.js +1 -1
- package/dist/domain/domain-context/action-system/action-system.d.ts +2 -2
- package/dist/domain/domain-context/action-system/action-system.js +1 -1
- package/dist/domain/domain-context/action-system/index.d.ts +1 -0
- package/dist/domain/domain-context/action-system/index.js +1 -0
- package/dist/domain/domain-context/analytics-system/index.d.ts +2 -0
- package/dist/domain/domain-context/analytics-system/index.js +1 -0
- package/dist/domain/domain-context/create-transformer.d.ts +1 -1
- package/dist/domain/domain-context/dimension-system/dimension-system.d.ts +2 -2
- package/dist/domain/domain-context/dimension-system/dimension-system.js +1 -1
- package/dist/domain/domain-context/dimension-system/index.d.ts +1 -0
- package/dist/domain/domain-context/dimension-system/index.js +1 -0
- package/dist/domain/domain-context/domain-context-systems.d.ts +7 -7
- package/dist/domain/domain-context/index.d.ts +8 -17
- package/dist/domain/domain-context/index.js +1 -1
- package/dist/domain/domain-context/navigation-system/index.d.ts +3 -0
- package/dist/domain/domain-context/navigation-system/index.js +1 -0
- package/dist/domain/domain-context/navigation-system/navigation-system.d.ts +2 -3
- package/dist/domain/domain-context/navigation-system/navigation-system.js +1 -1
- package/dist/domain/domain-context/navigation-system/selection-transformers.d.ts +1 -1
- package/dist/domain/domain-context/recipe-system/index.d.ts +1 -0
- package/dist/domain/domain-context/recipe-system/index.js +1 -0
- package/dist/domain/domain-context/recipe-system/recipe-cooker-system.d.ts +1 -1
- package/dist/domain/domain-context/recipe-system/recipe-cooker-system.js +1 -1
- package/dist/domain/domain-context/relation-transformers/cascade-deletes.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/cascade-ref-pruning.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/create-entity-refs.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/index.d.ts +4 -0
- package/dist/domain/domain-context/relation-transformers/index.js +1 -0
- package/dist/domain/domain-context/relation-transformers/replace-refs.d.ts +2 -2
- package/dist/domain/domain-context/store-system/index.d.ts +4 -0
- package/dist/domain/domain-context/store-system/index.js +1 -0
- package/dist/domain/domain-context/store-system/reconcile-projected-entities.js +1 -1
- package/dist/domain/domain-context/store-system/store-system.d.ts +2 -35
- package/dist/domain/domain-context/store-system/store-system.js +2 -2
- package/dist/domain/domain-context/store-system/system-state.d.ts +1 -1
- package/dist/domain/domain-context/transformers/actions-builder.js +1 -1
- package/dist/domain/domain-context/transformers/auto-snap-transformer.d.ts +5 -7
- package/dist/domain/domain-context/transformers/auto-snap-transformer.js +1 -1
- package/dist/domain/domain-context/transformers/chain-builder.d.ts +1 -1
- package/dist/domain/domain-context/transformers/context.d.ts +2 -3
- package/dist/domain/domain-context/transformers/context.js +1 -1
- package/dist/domain/domain-context/transformers/entity-builder.js +1 -1
- package/dist/domain/domain-context/transformers/index.d.ts +1 -1
- package/dist/domain/domain-context/transformers/index.js +1 -1
- package/dist/domain/domain-context/transformers/when-builder.d.ts +2 -3
- package/dist/domain/domain-context/transformers/when-builder.js +1 -1
- package/dist/domain/domain-context/undo-system.d.ts +2 -3
- package/dist/domain/domain-runtime/domain-context/action-context.d.ts +11 -0
- package/dist/domain/domain-runtime/domain-context/action-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/domain-context.js +1 -1
- package/dist/domain/domain-runtime/domain-context/expression-context.d.ts +8 -0
- package/dist/domain/domain-runtime/domain-context/expression-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/index.d.ts +3 -0
- package/dist/domain/domain-runtime/domain-context/runtime-expression-context.d.ts +18 -0
- package/dist/domain/domain-runtime/domain-context/runtime-expression-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/selectors.d.ts +5 -10
- package/dist/domain/domain-runtime/domain-context/selectors.js +1 -1
- package/dist/domain/domain-runtime/domain-runtime.d.ts +52 -52
- package/dist/domain/domain-runtime/spec/index.d.ts +2 -0
- package/dist/domain/domain-runtime/spec/index.js +1 -1
- package/dist/domain/domain-runtime/spec/initialize.js +1 -1
- package/dist/domain/domain-runtime/spec/invariant-builder.d.ts +26 -0
- package/dist/domain/domain-runtime/spec/invariant-builder.js +1 -0
- package/dist/domain/{application → domain-runtime/spec/module-checks}/element-registration.d.ts +2 -2
- package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +1 -0
- package/dist/domain/{application → domain-runtime/spec/module-checks}/handler-registration.d.ts +1 -1
- package/dist/domain/domain-runtime/spec/module-checks/handler-registration.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +8 -0
- package/dist/domain/domain-runtime/spec/module-checks/index.js +1 -0
- package/dist/domain/{project-modules → domain-runtime/spec/module-checks}/project-modules.d.ts +0 -16
- package/dist/domain/domain-runtime/spec/module-checks/project-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.d.ts +4 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.d.ts +4 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.d.ts +15 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.js +1 -0
- package/dist/domain/{application → domain-runtime/spec/module-checks}/resolve-imported-transformers.d.ts +4 -5
- package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.d.ts +6 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/selectors.d.ts +5 -6
- package/dist/domain/domain-runtime/spec/selectors.js +1 -1
- package/dist/domain/domain-runtime/spec/types.d.ts +3 -2
- package/dist/domain/index.d.ts +0 -1
- package/dist/domain/index.js +1 -1
- package/dist/domain/mocks/create-mock-domain-runtime-services.d.ts +1 -1
- package/dist/domain/mocks/create-mock-domain-runtime.d.ts +10 -10
- package/dist/domain/projections/blender-projections/blender-projections.d.ts +1 -2
- package/dist/domain/projections/blender-projections/camera.d.ts +1 -1
- package/dist/domain/projections/collision-projections.d.ts +1 -1
- package/dist/domain/projections/composition-projections.d.ts +1 -1
- package/dist/domain/projections/constraint-projections.d.ts +1 -1
- package/dist/domain/projections/index.d.ts +1 -1
- package/dist/domain/projections/position-projections.d.ts +1 -1
- package/dist/domain/projections/projected-entities-projections.d.ts +1 -1
- package/dist/domain/projections/snapping-projections.d.ts +1 -1
- package/dist/domain/projections/validation-projections.d.ts +3 -19
- package/dist/domain/sketching/sketching-projections.d.ts +1 -1
- package/dist/domain/system-components/phoenix-entity-elements/admin-data-table-sidebar.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-connected-resource-page.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-actions.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-page.d.ts +1 -1
- package/dist/domain/utils/memo-eval.d.ts +2 -2
- package/dist/legacy/domain-cli/gen/constant-expressions.js +1 -3
- package/dist/legacy/domain-cli/gen/implementation.js +7 -11
- package/dist/legacy/domain-cli/gen/invariants.js +5 -3
- package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
- package/dist/legacy/domain-cli/utils/cache.js +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +1 -0
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/domain-index.js +4 -4
- package/dist/phoenix/commands/init.d.ts +4 -4
- package/dist/phoenix/commands/init.js +1 -1
- package/dist/phoenix/commands/prettier.js +1 -1
- package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +20 -20
- package/dist/react-sdk/domain-context/common-hooks/use-domain-context.d.ts +5 -5
- package/package.json +3 -3
- package/dist/application-spec/generate-application/code/templates/env-ci.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/env-ci.js +0 -25
- package/dist/application-spec/generate-application/code/templates/env-example.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/env-example.js +0 -94
- package/dist/application-spec/generate-application/code/templates/private-assets-readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/private-assets-readme.js +0 -24
- package/dist/application-spec/generate-application/code/templates/public-assets-readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/public-assets-readme.js +0 -24
- package/dist/application-spec/generate-application/code/templates/readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/readme.js +0 -23
- package/dist/application-spec/generate-application/code/templates/skills/agents-md.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +0 -2
- package/dist/domain/application/element-registration.js +0 -1
- package/dist/domain/application/handler-registration.js +0 -1
- package/dist/domain/application/resolve-imported-transformers.js +0 -1
- package/dist/domain/project-modules/index.d.ts +0 -1
- package/dist/domain/project-modules/index.js +0 -1
- package/dist/domain/project-modules/project-modules.js +0 -1
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Documented Phoenix SDK guidance, one Markdown section per entry.
|
|
3
3
|
*
|
|
4
|
-
* The agent guidance shipped into generated projects — the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The agent guidance shipped into generated projects — the Phoenix-owned agent skills — is composed
|
|
5
|
+
* from these sections, so each documented fact has exactly one home and the generated files differ
|
|
6
|
+
* only in which sections they carry.
|
|
7
7
|
*/
|
|
8
8
|
export declare const sdkGuidanceSections: {
|
|
9
|
-
/** Opening description of what a Phoenix configurator project is. */
|
|
10
|
-
readonly projectIntro: `## Phoenix Configurator Project
|
|
11
|
-
|
|
12
|
-
Declarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.`;
|
|
13
9
|
/** The three everyday `phoenix` package scripts. */
|
|
14
10
|
readonly buildCommands: `## Build Commands
|
|
15
11
|
- \`pnpm phoenix build\` — Full production build
|
|
@@ -30,6 +26,7 @@ Declarative 3D product configurator built with Phoenix SDK. Domain model defined
|
|
|
30
26
|
- \`src/transformers/\` — Automatic state fixers (\`phx.frontend.transformerBuilder\`)
|
|
31
27
|
- \`src/components/\` — ECS system components (\`phx.domain.attachComponent.withEntity()\`)
|
|
32
28
|
- \`src/elements/\` — Page-level React components (\`phx.frontend.createCustomPhoenixLayoutElement()\`)
|
|
29
|
+
- A layout element named \`ConfiguratorLayout\` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount \`LayoutSlots.SharedCanvas\` (from \`@salsita-ai/sdk/configurator\`) in it, or the app loader stays on screen
|
|
33
30
|
- \`src/handlers/\` — Event & lifecycle handlers
|
|
34
31
|
- \`src/webhooks/\` — Server-side webhook handlers (\`phx.webhooks.*Builder\`)
|
|
35
32
|
- \`src/migrations/\` — Schema version migrations (v*.ts)
|
|
@@ -78,7 +75,8 @@ The default export must be what the folder's phx factory returns (for example \`
|
|
|
78
75
|
readonly doNotEdit: `## Do Not Edit
|
|
79
76
|
- \`.phoenix/\` directory — completely regenerated on each build. The one exception is \`.phoenix/variants/\`, where the \`phoenix-variants\` skill has you write option files; a rebuild keeps it
|
|
80
77
|
- \`tsconfig.json\`, \`eslint.config.js\`, \`prettier.config.js\`, \`.prettierignore\`, \`.linguirc\`, \`.nvmrc\`
|
|
81
|
-
- \`src/locales/\` .ts files — compiled from .po files
|
|
78
|
+
- \`src/locales/\` .ts files — compiled from .po files
|
|
79
|
+
- \`phoenix\` and \`phoenix-*\` folders under \`.claude/skills/\` and \`.agents/skills/\` — Phoenix rewrites its skills on every sync, removes anything else inside their folders and removes any other \`phoenix-*\` skill folder. To customise a skill, copy it to a folder whose name does not start with \`phoenix\``;
|
|
82
80
|
/** When and how a deployed domain model gets a new version and a migration, with the recipe for replacing the starter. */
|
|
83
81
|
readonly modelVersioning: `## Version the Domain Model Once It Is Deployed
|
|
84
82
|
A deployed model version cannot change, because saved configurations depend on it. \`pnpm phoenix deploy\` is refused, before anything is uploaded, when the model differs from the one already deployed under the same version. The version is not written anywhere: \`model.latest\` is always the highest numbered version plus one, so a spec with only \`latest\` is v1, and \`applicationVersion\` plays no part. \`pnpm phoenix dev\` accepts every model change, so a working preview does not show that the deploy will pass.
|
|
@@ -284,10 +282,10 @@ Rendered appearance is a deliverable, not a side effect. Before presenting any p
|
|
|
284
282
|
Inspect and report:
|
|
285
283
|
|
|
286
284
|
1. Geometry. Every part rests on its support. Name where the product meets the ground and confirm its shadow and any selection indicator sit there too. Nothing floats, intersects, or protrudes. Scale and orientation are consistent between parts and match the stated dimensions. Each part is complete and present.
|
|
287
|
-
2.
|
|
288
|
-
3.
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
2. Response. Every configurable surface of every part reacts to its control, not only the parts you just changed.
|
|
286
|
+
3. Loading. How long until a meaningful first frame, and whether anything indicates progress meanwhile.
|
|
287
|
+
|
|
288
|
+
This covers what follows from the change you just made, in the state you are already in. Fidelity against the client's own material, the density of the controls, and every state you did not open belong to an independent reviewer, which the \`phoenix-create-configurator\` skill's Appearance review covers.
|
|
291
289
|
|
|
292
290
|
Report defects you are deferring as defects. Do not call work done, verified, or complete while any of the above is unchecked or failing: say which, and recommend fixing now or later.
|
|
293
291
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
export const sdkGuidanceSections={
|
|
2
|
-
|
|
3
|
-
Declarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.`,buildCommands:"## Build Commands\n- `pnpm phoenix build` — Full production build\n- `pnpm phoenix dev` — Development mode with hot reload\n- `pnpm phoenix sync` — Sync generated files without full build",devLogs:"## Dev Logs\n- `pnpm phoenix dev` writes all logs to `.phoenix/phoenix-dev.log`\n- Contains: spec parse errors, build errors, browser runtime errors, file watcher events\n- After making changes, always check the log for errors: `tail -50 .phoenix/phoenix-dev.log` or `grep ERROR .phoenix/phoenix-dev.log`\n- Rotated automatically at 10MB (`.log.1`, `.log.2` are older rotations)",projectStructure:"## Project Structure\n- `spec/` — Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` — Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/actions/` — Domain operations declared under `actions:` in the spec (`phx.domain.implementActions()`), run by controls and exposed to the assistant\n- `src/invariants/` — Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` — Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` — ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` — Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n- `src/handlers/` — Event & lifecycle handlers\n- `src/webhooks/` — Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` — Schema version migrations (v*.ts)\n- `src/locales/` — Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` — Admin interface (mirrors main src/ structure)\n- `src/_shared/` — Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` — Auto-generated files (DO NOT EDIT, except the option files in `.phoenix/variants/`)\n- `public/` — Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` — Uploadable resources (PDF templates, email assets)",architecturePrinciples:"## Architecture Principles\n- YAML-first: 99% of configuration in spec/*.yaml, custom code is last resort\n- ECS pattern: entities (domain objects) + components (behavior/UI) + systems (SDK runtime)\n- Reactive state: immutable domain state flows through RxJS pipelines\n- Data binding: `$read` (read-only), `$write` (write-only), `$bind` (bidirectional) connect components to domain\n- memoEval: all component implementations must use memoEval for stable object references",keyPatterns:"## Key Patterns\n- Expressions = computed properties on entities (prefix: `$`), implemented per-entity in src/expressions/\n- Actions = named domain operations (`actions:` in YAML with `params` and `result` schemas), implemented in src/actions/, prepared through `actionSystem.prepareAction()` and exposed as assistant mutations through `assistant.actions`\n- Invariants = validation rules returning violations, static or dynamic\n- Transformers = auto-fixers maintaining consistency after state changes\n- Components defined in YAML with `$read`/`$bind` bindings, or custom in src/components/ using attachComponent builder\n- Webhooks = server-side finalization tasks (PDF, email, CSV, image rendering)\n- Filters = contextual component activation via navigationStates, viewStates, modes, selectedEntityIds",fileConventions:`## File Conventions
|
|
1
|
+
export const sdkGuidanceSections={buildCommands:"## Build Commands\n- `pnpm phoenix build` — Full production build\n- `pnpm phoenix dev` — Development mode with hot reload\n- `pnpm phoenix sync` — Sync generated files without full build",devLogs:"## Dev Logs\n- `pnpm phoenix dev` writes all logs to `.phoenix/phoenix-dev.log`\n- Contains: spec parse errors, build errors, browser runtime errors, file watcher events\n- After making changes, always check the log for errors: `tail -50 .phoenix/phoenix-dev.log` or `grep ERROR .phoenix/phoenix-dev.log`\n- Rotated automatically at 10MB (`.log.1`, `.log.2` are older rotations)",projectStructure:"## Project Structure\n- `spec/` — Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` — Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/actions/` — Domain operations declared under `actions:` in the spec (`phx.domain.implementActions()`), run by controls and exposed to the assistant\n- `src/invariants/` — Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` — Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` — ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` — Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n - A layout element named `ConfiguratorLayout` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in it, or the app loader stays on screen\n- `src/handlers/` — Event & lifecycle handlers\n- `src/webhooks/` — Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` — Schema version migrations (v*.ts)\n- `src/locales/` — Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` — Admin interface (mirrors main src/ structure)\n- `src/_shared/` — Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` — Auto-generated files (DO NOT EDIT, except the option files in `.phoenix/variants/`)\n- `public/` — Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` — Uploadable resources (PDF templates, email assets)",architecturePrinciples:"## Architecture Principles\n- YAML-first: 99% of configuration in spec/*.yaml, custom code is last resort\n- ECS pattern: entities (domain objects) + components (behavior/UI) + systems (SDK runtime)\n- Reactive state: immutable domain state flows through RxJS pipelines\n- Data binding: `$read` (read-only), `$write` (write-only), `$bind` (bidirectional) connect components to domain\n- memoEval: all component implementations must use memoEval for stable object references",keyPatterns:"## Key Patterns\n- Expressions = computed properties on entities (prefix: `$`), implemented per-entity in src/expressions/\n- Actions = named domain operations (`actions:` in YAML with `params` and `result` schemas), implemented in src/actions/, prepared through `actionSystem.prepareAction()` and exposed as assistant mutations through `assistant.actions`\n- Invariants = validation rules returning violations, static or dynamic\n- Transformers = auto-fixers maintaining consistency after state changes\n- Components defined in YAML with `$read`/`$bind` bindings, or custom in src/components/ using attachComponent builder\n- Webhooks = server-side finalization tasks (PDF, email, CSV, image rendering)\n- Filters = contextual component activation via navigationStates, viewStates, modes, selectedEntityIds",fileConventions:`## File Conventions
|
|
4
2
|
- Components: PascalCase (.tsx)
|
|
5
3
|
- Utilities: kebab-case (.ts)
|
|
6
4
|
- Expressions: entity-name.ts
|
|
7
5
|
- Migrations: v*.ts (sequential)
|
|
8
6
|
- Specs: kebab-case.yaml
|
|
9
|
-
- Underscore folders (_shared/, _utils/) are NOT auto-imported`,exportRules:'## Export Rules\nAll files in src/expressions/, src/actions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/elements/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.\nThe default export must be what the folder\'s phx factory returns (for example `phx.frontend.createCustomPhoenixLayoutElement()` in src/elements/). Any other value, such as a plain React component, stops the application at start with an "Invalid default export in <file>" error. Put plain components and helpers in an underscore folder.',webhookMembership:"## Webhook Membership\n- User-authored components, admin components, and handlers are frontend-only by default.\n- A handler that registers `createMapPublishedRevisionDataHandler`, `createMapPublishedChunkedDataHandler`, `createDataRowValidatorHandler`, or `createSheetValidatorHandler` is included in the webhook automatically.\n- To include another component or handler in the webhook, put `'use webhook'` as its first statement (leading comments are allowed).\n- A webhook-included file and its runtime local-import closure must not import browser SDK entrypoints (`application`, `admin`, `configurator`, `editor`, `react`, `ui`) or style/font/image assets. Spec parsing reports the unsafe import and inclusion reason as a diagnostic.\n- Keep browser rendering in an unpinned file. Move shared server-safe logic into a separate module when both frontend and webhook code need it.",doNotEdit:"## Do Not Edit\n- `.phoenix/` directory — completely regenerated on each build. The one exception is `.phoenix/variants/`, where the `phoenix-variants` skill has you write option files; a rebuild keeps it\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files — compiled from .po files",modelVersioning:"## Version the Domain Model Once It Is Deployed\nA deployed model version cannot change, because saved configurations depend on it. `pnpm phoenix deploy` is refused, before anything is uploaded, when the model differs from the one already deployed under the same version. The version is not written anywhere: `model.latest` is always the highest numbered version plus one, so a spec with only `latest` is v1, and `applicationVersion` plays no part. `pnpm phoenix dev` accepts every model change, so a working preview does not show that the deploy will pass.\n\nTo change a model that has been deployed as v<N>:\n1. Keep the deployed model, unchanged, as `model.v<N>` in the spec. A file of its own, such as `spec/model-v<N>.yaml`, keeps `latest` readable.\n2. Make the change in `model.latest`, which is now v<N+1>.\n3. Add `src/migrations/v<N+1>.ts` exporting `migrateToV<N+1>`. It receives the saved entities of v<N> and returns entities that are valid in v<N+1>; `ModelV<N>` and `ModelV<N+1>` are generated global namespaces.\n\nThe admin model is versioned on its own, under `src/admin/migrations/`, with its types in `AdminV<N>`.\n\n**Replacing the starter.** When the first deployment of the workspace was the starter, its `Box` model is v1 in production. When the real model replaces it, keep the starter's model as `model.v1` and define the real model in `latest`. Configurations saved from the starter are not worth converting, so the migration returns the starting state of the new model, the `initialDomainEntities` of its preset, and drops the boxes:\n```typescript\n// src/migrations/v2.ts\nimport type { Migration } from '@salsita-ai/sdk/domain'\n\nexport const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [\n // the entities of the new model's preset\n]\n```\nAn entity of a type the new version no longer has must not stay in the returned state. An entity id starts with its type (`Box-1`), so an entity that keeps its data under a new type name needs a new id and every relation pointing at it rewritten. Read `docs/phoenix/migrations.mdx` before writing any other migration.",sdkDocumentation:'## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` — Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` — Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` — Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` — Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` — List docs in a category\n- `pnpm phoenix docs -l` — List all doc paths with titles\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` — Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` — End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` — Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` — ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` — Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` — YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` — Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` — Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` — Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` — Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` — Schema versioning, migration files\n\n### Agent Workflow\n1. Find relevant docs by content: `pnpm phoenix docs -s \'camera\'`\n2. Read matching doc: `pnpm phoenix docs -r docs/3d-scene/cameras/transitions.mdx`\n3. Search within a doc: `pnpm phoenix docs -r <path> | grep "example"`\n4. Search titles only as a fallback: `pnpm phoenix docs -l | grep "camera"`\n5. Search generated files directly when needed: `grep -rn "camera" "$(pnpm phoenix docs --dir)"`\n6. Browse a topic area: `pnpm phoenix docs -c "Layouts"`',assetInspection:"## 3D Asset Inspection\n\nRun `pnpm phoenix inspect:assets` to discover all glTF/GLB files in `public/` and get a summary table. Use `--file <path>` for detailed single-file inspection.\n\n**When to use:** before writing YAML/code that references objectName, materialsToOverride, morphTargetInfluences, or object positioning.\n\n**What it shows per node:**\n- Name, type (Mesh/Light/Camera/Empty), children\n- Transform: `pos=`, `rot=`, `scale=` (only non-default values shown)\n- Per-node morph targets: `morphs=Width,Height`\n- Bounding box: `bounds=WxHxD` (width x height x depth in meters)\n- Origin placement: `origin=center`, `bottom-center`, `top-center`, `bottom-corner`, etc. with numerical offset\n\n**Use cases:**\n- Find exact node names for `objectName` in scene entity components\n- Find material names for `materialsToOverride`\n- Find morph target names for `morphTargetInfluences`\n- Check model dimensions (bounds) for positioning, dimension indicators, or invariants\n- Check origin placement to understand how models anchor (center vs bottom-center)\n- Use `--json` for programmatic access to all data\n\n**Flags:** `--objects`, `--materials`, `--morphs`, `--animations`, `--json`",screenshotCli:"## Screenshot CLI\n\nCapture 3D canvas screenshots from a running dev server via `pnpm phoenix screenshot`.\n\n**Prerequisites:**\n1. `phoenix dev` must be running (starts the WebSocket server on port 3020)\n2. Configurator must be open in a browser (the browser does the actual rendering)\n\n**Options:**\n- `--output, -o` — Output file path (default: `./screenshot.png`)\n- `--width, -w` — Width in pixels (default: 1920)\n- `--height` — Height in pixels (default: 1080)\n- `--format, -f` — Image format: PNG or JPEG (default: PNG)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example workflow:**\n1. Start dev server: `pnpm phoenix dev`\n2. Open configurator in browser (or via Playwright MCP)\n3. Take screenshot: `pnpm phoenix screenshot -o ./render.png -w 2560 --height 1440`\n\n**Use cases:** visual regression testing, generating product images, debugging 3D renders",inspectState:"## Inspect: State\n\nGet the current editor runtime state as JSON via `pnpm phoenix inspect:state`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` — Output file path (prints to stdout if omitted)\n- `--sections, -s` — Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` — Pretty-print JSON (default: true)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example:** `pnpm phoenix inspect:state -o ./state.json -s entities,navigation`\n\n**Lifecycle status:** Always included in response. Shows which systems are ready:\n- `domainLoaded` — domain data loaded\n- `domainContextReady` — domain context initialized (entities available)\n- `configuratorReady` — 3D configurator initialized\n- `sessionAuthenticated` — session service active\n- `editorMode` — editor mode enabled\n\n**Tips:** Use `--sections` to limit payload size. Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:state | head -50`).\n\n**Use cases:** AI agent context, debugging entity state, inspecting navigation/validation",inspectData:`## Inspect: Data
|
|
7
|
+
- Underscore folders (_shared/, _utils/) are NOT auto-imported`,exportRules:'## Export Rules\nAll files in src/expressions/, src/actions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/elements/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.\nThe default export must be what the folder\'s phx factory returns (for example `phx.frontend.createCustomPhoenixLayoutElement()` in src/elements/). Any other value, such as a plain React component, stops the application at start with an "Invalid default export in <file>" error. Put plain components and helpers in an underscore folder.',webhookMembership:"## Webhook Membership\n- User-authored components, admin components, and handlers are frontend-only by default.\n- A handler that registers `createMapPublishedRevisionDataHandler`, `createMapPublishedChunkedDataHandler`, `createDataRowValidatorHandler`, or `createSheetValidatorHandler` is included in the webhook automatically.\n- To include another component or handler in the webhook, put `'use webhook'` as its first statement (leading comments are allowed).\n- A webhook-included file and its runtime local-import closure must not import browser SDK entrypoints (`application`, `admin`, `configurator`, `editor`, `react`, `ui`) or style/font/image assets. Spec parsing reports the unsafe import and inclusion reason as a diagnostic.\n- Keep browser rendering in an unpinned file. Move shared server-safe logic into a separate module when both frontend and webhook code need it.",doNotEdit:"## Do Not Edit\n- `.phoenix/` directory — completely regenerated on each build. The one exception is `.phoenix/variants/`, where the `phoenix-variants` skill has you write option files; a rebuild keeps it\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files — compiled from .po files\n- `phoenix` and `phoenix-*` folders under `.claude/skills/` and `.agents/skills/` — Phoenix rewrites its skills on every sync, removes anything else inside their folders and removes any other `phoenix-*` skill folder. To customise a skill, copy it to a folder whose name does not start with `phoenix`",modelVersioning:"## Version the Domain Model Once It Is Deployed\nA deployed model version cannot change, because saved configurations depend on it. `pnpm phoenix deploy` is refused, before anything is uploaded, when the model differs from the one already deployed under the same version. The version is not written anywhere: `model.latest` is always the highest numbered version plus one, so a spec with only `latest` is v1, and `applicationVersion` plays no part. `pnpm phoenix dev` accepts every model change, so a working preview does not show that the deploy will pass.\n\nTo change a model that has been deployed as v<N>:\n1. Keep the deployed model, unchanged, as `model.v<N>` in the spec. A file of its own, such as `spec/model-v<N>.yaml`, keeps `latest` readable.\n2. Make the change in `model.latest`, which is now v<N+1>.\n3. Add `src/migrations/v<N+1>.ts` exporting `migrateToV<N+1>`. It receives the saved entities of v<N> and returns entities that are valid in v<N+1>; `ModelV<N>` and `ModelV<N+1>` are generated global namespaces.\n\nThe admin model is versioned on its own, under `src/admin/migrations/`, with its types in `AdminV<N>`.\n\n**Replacing the starter.** When the first deployment of the workspace was the starter, its `Box` model is v1 in production. When the real model replaces it, keep the starter's model as `model.v1` and define the real model in `latest`. Configurations saved from the starter are not worth converting, so the migration returns the starting state of the new model, the `initialDomainEntities` of its preset, and drops the boxes:\n```typescript\n// src/migrations/v2.ts\nimport type { Migration } from '@salsita-ai/sdk/domain'\n\nexport const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [\n // the entities of the new model's preset\n]\n```\nAn entity of a type the new version no longer has must not stay in the returned state. An entity id starts with its type (`Box-1`), so an entity that keeps its data under a new type name needs a new id and every relation pointing at it rewritten. Read `docs/phoenix/migrations.mdx` before writing any other migration.",sdkDocumentation:'## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` — Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` — Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` — Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` — Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` — List docs in a category\n- `pnpm phoenix docs -l` — List all doc paths with titles\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` — Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` — End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` — Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` — ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` — Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` — YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` — Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` — Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` — Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` — Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` — Schema versioning, migration files\n\n### Agent Workflow\n1. Find relevant docs by content: `pnpm phoenix docs -s \'camera\'`\n2. Read matching doc: `pnpm phoenix docs -r docs/3d-scene/cameras/transitions.mdx`\n3. Search within a doc: `pnpm phoenix docs -r <path> | grep "example"`\n4. Search titles only as a fallback: `pnpm phoenix docs -l | grep "camera"`\n5. Search generated files directly when needed: `grep -rn "camera" "$(pnpm phoenix docs --dir)"`\n6. Browse a topic area: `pnpm phoenix docs -c "Layouts"`',assetInspection:"## 3D Asset Inspection\n\nRun `pnpm phoenix inspect:assets` to discover all glTF/GLB files in `public/` and get a summary table. Use `--file <path>` for detailed single-file inspection.\n\n**When to use:** before writing YAML/code that references objectName, materialsToOverride, morphTargetInfluences, or object positioning.\n\n**What it shows per node:**\n- Name, type (Mesh/Light/Camera/Empty), children\n- Transform: `pos=`, `rot=`, `scale=` (only non-default values shown)\n- Per-node morph targets: `morphs=Width,Height`\n- Bounding box: `bounds=WxHxD` (width x height x depth in meters)\n- Origin placement: `origin=center`, `bottom-center`, `top-center`, `bottom-corner`, etc. with numerical offset\n\n**Use cases:**\n- Find exact node names for `objectName` in scene entity components\n- Find material names for `materialsToOverride`\n- Find morph target names for `morphTargetInfluences`\n- Check model dimensions (bounds) for positioning, dimension indicators, or invariants\n- Check origin placement to understand how models anchor (center vs bottom-center)\n- Use `--json` for programmatic access to all data\n\n**Flags:** `--objects`, `--materials`, `--morphs`, `--animations`, `--json`",screenshotCli:"## Screenshot CLI\n\nCapture 3D canvas screenshots from a running dev server via `pnpm phoenix screenshot`.\n\n**Prerequisites:**\n1. `phoenix dev` must be running (starts the WebSocket server on port 3020)\n2. Configurator must be open in a browser (the browser does the actual rendering)\n\n**Options:**\n- `--output, -o` — Output file path (default: `./screenshot.png`)\n- `--width, -w` — Width in pixels (default: 1920)\n- `--height` — Height in pixels (default: 1080)\n- `--format, -f` — Image format: PNG or JPEG (default: PNG)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example workflow:**\n1. Start dev server: `pnpm phoenix dev`\n2. Open configurator in browser (or via Playwright MCP)\n3. Take screenshot: `pnpm phoenix screenshot -o ./render.png -w 2560 --height 1440`\n\n**Use cases:** visual regression testing, generating product images, debugging 3D renders",inspectState:"## Inspect: State\n\nGet the current editor runtime state as JSON via `pnpm phoenix inspect:state`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` — Output file path (prints to stdout if omitted)\n- `--sections, -s` — Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` — Pretty-print JSON (default: true)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example:** `pnpm phoenix inspect:state -o ./state.json -s entities,navigation`\n\n**Lifecycle status:** Always included in response. Shows which systems are ready:\n- `domainLoaded` — domain data loaded\n- `domainContextReady` — domain context initialized (entities available)\n- `configuratorReady` — 3D configurator initialized\n- `sessionAuthenticated` — session service active\n- `editorMode` — editor mode enabled\n\n**Tips:** Use `--sections` to limit payload size. Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:state | head -50`).\n\n**Use cases:** AI agent context, debugging entity state, inspecting navigation/validation",inspectData:`## Inspect: Data
|
|
10
8
|
|
|
11
9
|
Get the current editor context data as JSON via \`pnpm phoenix inspect:data\`.
|
|
12
10
|
|
|
@@ -71,10 +69,10 @@ Rendered appearance is a deliverable, not a side effect. Before presenting any p
|
|
|
71
69
|
Inspect and report:
|
|
72
70
|
|
|
73
71
|
1. Geometry. Every part rests on its support. Name where the product meets the ground and confirm its shadow and any selection indicator sit there too. Nothing floats, intersects, or protrudes. Scale and orientation are consistent between parts and match the stated dimensions. Each part is complete and present.
|
|
74
|
-
2.
|
|
75
|
-
3.
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
2. Response. Every configurable surface of every part reacts to its control, not only the parts you just changed.
|
|
73
|
+
3. Loading. How long until a meaningful first frame, and whether anything indicates progress meanwhile.
|
|
74
|
+
|
|
75
|
+
This covers what follows from the change you just made, in the state you are already in. Fidelity against the client's own material, the density of the controls, and every state you did not open belong to an independent reviewer, which the \`phoenix-create-configurator\` skill's Appearance review covers.
|
|
78
76
|
|
|
79
77
|
Report defects you are deferring as defects. Do not call work done, verified, or complete while any of the above is unchecked or failing: say which, and recommend fixing now or later.
|
|
80
78
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as t}from"zod";import{ASSISTANT_AI_PROVIDERS as e,ASSISTANT_MODEL_TIERS as o,ASSISTANT_REASONING_LEVELS as i}from"@salsita-ai/contracts/assistant";export const ASSISTANT_OPTION_COMPONENT_TYPES=["materialSwatch","attributeSelection","distanceDimensionSelect"];export const ASSISTANT_HINTS=["sameForAllInstances","constantDuringSession"];let n=t.string().regex(/^(?!__)[_A-Za-z][_0-9A-Za-z]*$/,'must be a GraphQL name: letters, digits and "_", not starting with a digit or "__"'),r=e=>t.enum(e,{errorMap:()=>({message:`must be one of: ${e.join(", ")}`})}),s=t.object({description:t.string().optional(),as:n.optional()}).strict(),a=t.array(r(ASSISTANT_HINTS)),
|
|
1
|
+
import{z as t}from"zod";import{ASSISTANT_AI_PROVIDERS as e,ASSISTANT_MODEL_TIERS as o,ASSISTANT_REASONING_LEVELS as i}from"@salsita-ai/contracts/assistant";export const ASSISTANT_OPTION_COMPONENT_TYPES=["materialSwatch","attributeSelection","distanceDimensionSelect"];export const ASSISTANT_HINTS=["sameForAllInstances","constantDuringSession"];let n=t.string().regex(/^(?!__)[_A-Za-z][_0-9A-Za-z]*$/,'must be a GraphQL name: letters, digits and "_", not starting with a digit or "__"'),r=e=>t.enum(e,{errorMap:()=>({message:`must be one of: ${e.join(", ")}`})}),s=t.object({description:t.string().optional(),as:n.optional()}).strict(),a=t.array(r(ASSISTANT_HINTS)),l=t.object({options:t.object({componentType:r(ASSISTANT_OPTION_COMPONENT_TYPES),instanceName:t.string().min(1,"must not be empty; name the component instance that holds the choices"),as:n.optional(),validate:t.boolean().optional(),allowDryRuns:t.boolean().optional(),hints:a.optional()}).strict().optional()}).strict(),c=t.object({as:t.string().trim().min(1,"must not be empty; remove the key to use the instance name").optional(),description:t.string().optional(),allowDryRuns:t.boolean().optional()}).strict(),p=e=>t.object({hints:a.optional(),groups:t.record(t.string(),c.nullable(),{required_error:`is required: the recipe groups of the type go under "groups", keyed by the instance name of a "${e}" component`})}).strict(),m=t.object({description:t.string().optional(),as:n.optional()}).strict(),d=`is required: declare provider (${e.join(", ")}), model (${o.join(", ")}) and reasoning (${i.join(", ")})`,u="must be a positive whole number of input tokens; remove the key to use the default of the server",b=t.object({provider:r(e),model:r(o),reasoning:r(i),maxThreadSize:t.number({invalid_type_error:u}).int(u).positive(u).optional()},{required_error:d,invalid_type_error:d}).strict();export const rawAssistantSchema=t.object({entities:t.record(t.string(),t.record(t.string(),s.nullable())).optional(),deletable:t.record(t.string(),t.object({}).strict().nullable()).optional(),writable:t.record(t.string(),t.record(t.string(),l.nullable())).optional(),childRecipes:t.record(t.string(),p("childEntityRecipes")).optional(),replaceRecipes:t.record(t.string(),p("replaceEntityRecipes")).optional(),actions:t.record(t.string(),m.nullable()).optional(),instructions:t.string().trim().min(1,"must not be empty; remove the key when there are no instructions").optional(),ai:b}).strict();
|
|
@@ -201,10 +201,10 @@ export interface paths {
|
|
|
201
201
|
readonly path?: never
|
|
202
202
|
readonly cookie?: never
|
|
203
203
|
}
|
|
204
|
-
/** @description Load the caller's latest assistant thread on the configuration with its active run, newest window first. The `x-assistant-thread-continuable` response header is `false` when the thread cannot be continued with this setup or is closed; a chat request then answers 409, and the thread continues once it is compacted. The `x-assistant-previous-thread` response header carries the id of the closed thread this one continues, when there is one. */
|
|
204
|
+
/** @description Load the caller's latest assistant thread on the configuration with its active run, newest window first. The `x-assistant-thread-continuable` response header is `false` when the thread cannot be continued with this setup, has reached its size limit or is closed; a chat request then answers 409, and the thread continues once it is compacted. The `x-assistant-previous-thread` response header carries the id of the closed thread this one continues, when there is one. */
|
|
205
205
|
readonly get: operations['getAssistantThread']
|
|
206
206
|
readonly put?: never
|
|
207
|
-
/** @description Run the experimental assistant on the caller's thread of the configuration, or replay the run when the request carries `Last-Event-ID`. Answers 409 when the thread cannot be continued
|
|
207
|
+
/** @description Run the experimental assistant on the caller's thread of the configuration, or replay the run when the request carries `Last-Event-ID`. Answers 409 when the thread cannot be continued or another run is still answering, and 503 while the server shuts down; sent again, the message reaches another instance. A 409 for a thread that cannot be continued, because it is closed, was started with another setup or has reached its size limit, carries the `x-assistant-thread-continuable: false` response header; the thread continues once it is compacted. Answers 403 unless the customer has the AI Assistant feature flag on. */
|
|
208
208
|
readonly post: operations['assistantChat']
|
|
209
209
|
readonly delete?: never
|
|
210
210
|
readonly options?: never
|
|
@@ -221,7 +221,7 @@ export interface paths {
|
|
|
221
221
|
}
|
|
222
222
|
readonly get?: never
|
|
223
223
|
readonly put?: never
|
|
224
|
-
/** @description
|
|
224
|
+
/** @description Compact the caller's latest assistant thread on the configuration, which cannot be continued as it is: the thread is summarized and closed, and a new, empty thread is opened that links to it and keeps the summary. Nothing is run; the next chat request runs on the new thread, with the summary in its system prompt. Answers 409 when there is no thread, the latest one can be continued as it is, or one of its runs is still running, and 502 when the summary could not be made; nothing is closed then and the request can be sent again. */
|
|
225
225
|
readonly post: operations['compactAssistantThread']
|
|
226
226
|
readonly delete?: never
|
|
227
227
|
readonly options?: never
|
|
@@ -5610,6 +5610,7 @@ export interface operations {
|
|
|
5610
5610
|
| boolean
|
|
5611
5611
|
| {
|
|
5612
5612
|
readonly ai: {
|
|
5613
|
+
readonly maxThreadSize?: number
|
|
5613
5614
|
/** @enum {string} */
|
|
5614
5615
|
readonly model: 'frontier' | 'advanced' | 'balanced' | 'fast'
|
|
5615
5616
|
/** @enum {string} */
|
|
@@ -5832,6 +5833,7 @@ export interface operations {
|
|
|
5832
5833
|
| boolean
|
|
5833
5834
|
| {
|
|
5834
5835
|
readonly ai: {
|
|
5836
|
+
readonly maxThreadSize?: number
|
|
5835
5837
|
/** @enum {string} */
|
|
5836
5838
|
readonly model: 'frontier' | 'advanced' | 'balanced' | 'fast'
|
|
5837
5839
|
/** @enum {string} */
|
|
@@ -103,6 +103,18 @@ export declare class AssistantSystem implements InitializableSystem {
|
|
|
103
103
|
private _olderThreadId;
|
|
104
104
|
/** The resume that is running, so two sends that find the thread locked share one. */
|
|
105
105
|
private _resuming;
|
|
106
|
+
/**
|
|
107
|
+
* The message the server refused on a chat because its thread cannot be continued, by where it
|
|
108
|
+
* starts in the chat's messages: it is painted, but it is not part of the thread, so a resume
|
|
109
|
+
* leaves it out of the closed thread it paints above the new one.
|
|
110
|
+
*/
|
|
111
|
+
private _refused;
|
|
112
|
+
/**
|
|
113
|
+
* The chat a message is being sent on for the first time. When the server refuses that message
|
|
114
|
+
* because the thread cannot be continued, the refusal is not a failure of the chat: the thread is
|
|
115
|
+
* resumed and the message is sent again; see `send`.
|
|
116
|
+
*/
|
|
117
|
+
private _firstAttempt;
|
|
106
118
|
/**
|
|
107
119
|
* Identity of the chain of threads that is painted. A resumed chat keeps it, because its new
|
|
108
120
|
* thread continues the same chain; any other new chat starts another one.
|
|
@@ -168,12 +180,26 @@ export declare class AssistantSystem implements InitializableSystem {
|
|
|
168
180
|
* A thread this session may not continue is resumed first, in a new thread; see `resume`. When
|
|
169
181
|
* that fails the message is not sent.
|
|
170
182
|
*
|
|
183
|
+
* The chat can be out of date about that: the thread reached its size limit with the reply before
|
|
184
|
+
* this message, or the assistant's setup changed while the configurator stayed open. The server
|
|
185
|
+
* then refuses the message and says that the thread cannot be continued. The message stays
|
|
186
|
+
* painted while the thread is resumed, and is then sent again on the new thread, with the change
|
|
187
|
+
* record it was first sent with. This happens once: a refusal of the second attempt is the
|
|
188
|
+
* failure it is. When resuming fails, the message is left as any message that failed to send:
|
|
189
|
+
* painted, with the failure as the error of the chat.
|
|
190
|
+
*
|
|
191
|
+
* Accepted: when the server refuses the compaction itself with a conflict, because another tab
|
|
192
|
+
* compacted the thread or a run is still running, the thread is read again (see `resume`) and
|
|
193
|
+
* the refused message is gone without a notice. It takes two tabs or a run racing the send, and
|
|
194
|
+
* the user writes the message again, as after any reload of a thread that failed.
|
|
195
|
+
*
|
|
171
196
|
* It never rejects: a message the client could not send is reported as the error of the chat.
|
|
172
197
|
*/
|
|
173
198
|
send: (content: string) => Promise<void>;
|
|
174
199
|
/**
|
|
175
200
|
* Makes the chat ready for the next message. A thread the server no longer lets this session
|
|
176
|
-
* continue, because the assistant's setup changed since it was started
|
|
201
|
+
* continue, because the assistant's setup changed since it was started or the thread reached
|
|
202
|
+
* its size limit, is compacted there: the
|
|
177
203
|
* server closes it and opens a thread that starts from its summary. The messages painted so far
|
|
178
204
|
* move to `earlierThreads`, and a new client takes the new thread over.
|
|
179
205
|
*
|
|
@@ -201,6 +227,13 @@ export declare class AssistantSystem implements InitializableSystem {
|
|
|
201
227
|
reloadThread: () => void;
|
|
202
228
|
setOpen: (open: boolean) => void;
|
|
203
229
|
setInputFocused: (isInputFocused: boolean) => void;
|
|
230
|
+
/**
|
|
231
|
+
* Where the message starts that is being sent on the chat for the first time and that the server
|
|
232
|
+
* refused because its thread cannot be continued, so it never reached the thread; `-1` when there
|
|
233
|
+
* is no such message. A refused continuation of a reply is not one: its message is stored.
|
|
234
|
+
*/
|
|
235
|
+
private _findRefusedMessageIndex;
|
|
236
|
+
private _sendOn;
|
|
204
237
|
private _resume;
|
|
205
238
|
private _committedState;
|
|
206
239
|
private _patchPanel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ChatClient as e,fetchServerSentEvents as t}from"@tanstack/ai-client";import{BehaviorSubject as i}from"rxjs";import{skip as
|
|
1
|
+
import{ChatClient as e,fetchServerSentEvents as t}from"@tanstack/ai-client";import{BehaviorSubject as i}from"rxjs";import{skip as s}from"rxjs/operators";import{isShallowEqual as a}from"@salsita-ai/common/utils";import{AssistantRequestError as r}from"#package/client";import{createBrowserDetailsClientTool as n}from"./browser-details.js";import{ChangeRecorder as h}from"./change-recorder.js";import{createApplyClientTool as o,createQueryClientTool as l}from"./graphql-tools.js";let c=Object.freeze({messages:Object.freeze([]),isLoading:!1,error:void 0,failedStatus:void 0,isContinuable:!0,isLoaded:!1,isResuming:!1,earlierThreads:Object.freeze([]),hasEarlierThread:!1,isLoadingEarlierThread:!1}),d=Object.freeze({open:!1,hasUnreadReply:!1,isInputFocused:!1}),u=Object.freeze({}),_=Object.freeze({results:Object.freeze([]),committed:!1,sideEffects:Object.freeze([]),message:"Nothing was run, because the user closed the configurator while this reply was being written. Do not call any more tools in this reply and end it now. The tools work again as soon as the user writes the next message."}),g=e=>{if(e.getError())return!0;let t=e.getMessages().at(-1)?.parts.at(-1);return t?.type!=="tool-call"&&t?.type!=="tool-result"};export class AssistantSystem{_context;_tools;_chat=new i(c);_panel=new i(d);_activation=null;_current=null;_finishing=new Set;_olderThreadId=null;_resuming=null;_refused=null;_firstAttempt=null;_chain={};_loadingThreadId=null;_recorder;_recording=null;constructor(e){this._context=e;let{configuratorExtensionSelectors:t,domainContextSelectors:i,domainServicesSelectors:s,specSelectors:a}=e;this._recorder=new h({maxEntities:30},{readers:{selectedEntityId:e=>i.getNavigationSystem().getActiveSelectionState(e),navigationState:e=>i.getNavigationSystem().getActiveNavigationState(e),viewState:e=>i.getNavigationSystem().getActiveViewState(e),toolState:e=>i.getNavigationSystem().getActiveToolState(e)},getExposedFields:()=>a.getAssistant()?.fields??u,resolveLocalizable:e=>s.getResolveLocalizable()(e)});let r={getGraphQLSystem:()=>t.getGraphQLSystem(),getStoreSystem:()=>i.getStoreSystem(),getActiveMode:()=>i.getActiveMode(),clearChanges:()=>this._recorder.clear(this._committedState())};this._tools=[n(),l(r),o(r)]}chat$=this._chat;panel$=this._panel;initialize=()=>{let{committed:e}=this._context.domainContextSelectors.getStoreSystem().stateSlices;this._recording??=e.pipe(s(1)).subscribe(this._recorder.record),this._recorder.reset(e.value.state),this._endChat(),this._patchPanel(d),this._activation&&this._startClient()};activate=()=>{this._endChat();let e={};return this._activation=e,this._startClient(),()=>{this._activation===e&&(this._activation=null,this._endChat())}};send=async e=>{if(!this._chat.value.isContinuable&&!await this.resume())return;let t=this._current;if(!t)return;let i={changes:this._recorder.take(this._committedState())};this._firstAttempt=t;try{await this._sendOn(t,e,i);let s=this._findRefusedMessageIndex(t);if(-1===s)return;this._refused={chat:t,index:s}}finally{this._firstAttempt===t&&(this._firstAttempt=null)}if(!await this.resume())return;let s=this._current;s&&await this._sendOn(s,e,i)};resume=async()=>{let e=this._current;if(!e)return!1;if(this._chat.value.isContinuable)return!0;if(this._resuming)return this._resuming;let t=this._resume(e).finally(()=>{this._resuming===t&&(this._resuming=null)});return this._resuming=t,t};loadEarlierThread=async()=>{let e=this._current,t=this._olderThreadId;if(!e||null===t||this._loadingThreadId===t)return;let i=this._chain;this._loadingThreadId=t,this._chat.next({...this._chat.value,isLoadingEarlierThread:!0});try{let s=await e.authorizedClient.getAssistantEarlierThread(e.configurationId,t,50);if(this._chain!==i||this._olderThreadId!==t)return;this._olderThreadId=s.previousThreadId,this._chat.next({...this._chat.value,earlierThreads:[{threadId:t,messages:s.messages},...this._chat.value.earlierThreads],hasEarlierThread:null!==s.previousThreadId})}catch(e){this._context.domainServicesSelectors.getLogger().warn("Could not load the earlier assistant conversation",{error:e})}finally{this._chain===i&&this._loadingThreadId===t&&(this._loadingThreadId=null,this._chat.next({...this._chat.value,isLoadingEarlierThread:!1}))}};stop=async()=>{let e=this._current;if(!e)return;let t=e.client.getCurrentRunId();if(e.client.stop(),null!=t)try{let i=e.getConnection();await fetch(`${i.url}/runs/${encodeURIComponent(t)}/cancel`,{method:"POST",headers:await i.headers()})}catch(e){this._context.domainServicesSelectors.getLogger().warn("Could not cancel the assistant run on the server",{error:e})}};reloadThread=()=>{if(!this._activation)return;let e=this._retireChat();e?.client.stop(),e?.dispose(),this._recorder.reset(this._committedState()),this._startClient()};setOpen=e=>{this._patchPanel(e?{open:e,hasUnreadReply:!1}:{open:e,isInputFocused:!1})};setInputFocused=e=>{this._patchPanel({isInputFocused:e})};_findRefusedMessageIndex=e=>{var t;let i,{failedStatus:s,isContinuable:a}=this._chat.value;return this._firstAttempt===e&&e.generation.active&&409===s&&!a?(i=(t=e.client.getMessages()).map(e=>e.role).lastIndexOf("user"),t.slice(i+1).every(e=>0===e.parts.length)?i:-1):-1};_sendOn=async(e,t,i)=>{let{client:s}=e;s.getIsLoading()||null===s.getResumeState()||s.stop();try{await s.sendMessage({content:t,metadata:i})}catch(i){let t=i instanceof Error?i:Error(String(i));e.generation.active&&this._chat.next({...this._chat.value,error:t}),this._context.domainServicesSelectors.getLogger().error("The assistant message could not be sent",{},t)}};_resume=async e=>{this._chat.next({...this._chat.value,isResuming:!0,error:void 0,failedStatus:void 0});try{let{previousThreadId:t}=await e.authorizedClient.compactAssistantThread(e.configurationId);if(!e.generation.active)return!1;let{messages:i,earlierThreads:s,hasEarlierThread:a}=this._chat.value,r=this._refused?.chat===e?i.slice(0,this._refused.index):i;return e.generation.active=!1,this._current=null,e.client.stop(),e.dispose(),this._chat.next({...c,earlierThreads:[...s,{threadId:t,messages:r}],hasEarlierThread:a,isLoadingEarlierThread:null!==this._loadingThreadId}),this._startClient(),!0}catch(i){let t=i instanceof Error?i:Error(String(i));if(t instanceof r&&409===t.status)return e.generation.active&&this.reloadThread(),!1;return e.generation.active&&this._chat.next({...this._chat.value,isResuming:!1,error:t,failedStatus:t instanceof r?t.status:void 0}),this._context.domainServicesSelectors.getLogger().error("The assistant thread could not be resumed",{},t),!1}};_committedState=()=>this._context.domainContextSelectors.getStoreSystem().stateSlices.committed.value.state;_patchPanel=e=>{let t={...this._panel.value,...e};a(t,this._panel.value)||this._panel.next(t)};_takeOverThread=e=>{for(let t of this._finishing)t.configurationId===e&&(t.client.stop(),t.dispose())};_startClient=()=>{let i={active:!0},s=this._context.getConfigurationId();this._takeOverThread(s);let r=this._context.getAuthorizedClient(),n=()=>r.getAssistantChatConnection(s),h=e=>{if(!i.active)return;let t={...this._chat.value,...e};a(t,this._chat.value)||this._chat.next(t)},o=async(e,t)=>{let s=await fetch(e,t).finally(()=>h({isLoaded:!0})),a=s.headers.get("x-assistant-thread-continuable");return null!=a&&s.ok&&i.active&&0===this._chat.value.earlierThreads.length&&(this._olderThreadId=s.headers.get("x-assistant-previous-thread")),h({failedStatus:s.ok?void 0:s.status,...null!=a&&{isContinuable:"false"!==a,hasEarlierThread:null!==this._olderThreadId}}),s},l=async()=>({headers:await n().headers(),fetchClient:o}),c=t(()=>n().url,l),d=t(()=>`${n().url}/runs`,l),u=this._tools.map(e=>({...e,execute:(t,s)=>i.active?e.execute?.(t,s):_})),m=new e({threadId:`assistant:${s}`,connection:{...c,joinRun:d.joinRun},persistence:!0,history:{pageSize:50},tools:u,onMessagesChange:e=>h({messages:e}),onLoadingChange:e=>{let t=i.active&&this._chat.value.isLoading;h({isLoading:e}),!e&&g(m)&&(i.active?t&&!this._panel.value.open&&this._patchPanel({hasUnreadReply:!0}):v())},onErrorChange:e=>{i.active&&e!==this._chat.value.error&&(!e||-1===this._findRefusedMessageIndex(p))&&(h({error:e}),e&&this._context.domainServicesSelectors.getLogger().error("The assistant request failed",{status:this._chat.value.failedStatus},e))}}),f=!1,v=()=>{f||(f=!0,this._finishing.delete(p),m.dispose())},p={client:m,configurationId:s,generation:i,authorizedClient:r,getConnection:n,dispose:v};this._current=p,m.attach()};_retireChat=()=>{let e=this._current;return this._current=null,e&&(e.generation.active=!1),this._chain={},this._olderThreadId=null,this._resuming=null,this._refused=null,this._loadingThreadId=null,this._chat.value!==c&&this._chat.next(c),e};_endChat=()=>{let e=this._retireChat();if(e){if(!e.client.getIsLoading()&&g(e.client))return void e.dispose();this._finishing.add(e)}}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in UI of a loaded configuration: the layout the spec's `layout` selects, in owner or
|
|
3
|
+
* read mode. `LoadedConfiguratorDetailPage` renders it when the project registers no layout element
|
|
4
|
+
* named `ConfiguratorLayout`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DefaultConfiguratorLayout: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as r,jsxs as e,Fragment as t}from"react/jsx-runtime";import{useMemo as a}from"react";import{match as o}from"ts-pattern";import{useAccessService as i}from"#package/react-sdk";import{useLayoutSystemConfig as m}from"../configurator-context/index.js";import{Layout as n,LayoutCustomOwnerUi as u,LayoutCustomReadUi as s,LayoutDialog as d,Legacy as w}from"../ui/index.js";export const DefaultConfiguratorLayout=()=>{let{accessInfo:p}=i(),y=m(),{ownerUi:l,readUi:c}=a(()=>o(y.layout).with("simple",()=>({ownerUi:r(w.SimpleOwnerLayout,{}),readUi:r(w.SummaryReadLayout,{})})).with("tabs",()=>({ownerUi:r(w.TabsOwnerLayout,{}),readUi:r(w.SummaryReadLayout,{})})).with("steps",()=>({ownerUi:r(w.StepsOwnerLayout,{}),readUi:r(w.SummaryReadLayout,{})})).with("universal",()=>({ownerUi:r(n,{}),readUi:r(n,{})})).with("custom",()=>({ownerUi:r(u,{}),readUi:r(s,{})})).exhaustive(),[y.layout]);return e(t,{children:[p.viewMode?c:l,"universal"===y.layout&&e(t,{children:[r(w.CommonFinalizePage,{}),r(d,{})]})]})};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as
|
|
1
|
+
import{jsx as o,jsxs as r}from"react/jsx-runtime";import e from"mui-modal-provider";import{memo as i,useCallback as n,useEffect as t}from"react";import{InvalidAccessKeyError as a}from"#package/client";import{BuiltInLoadingPhase as d,CommonAnalyticsFactory as s,DomainLifecycleBoundary as l,LoadedDataProvider as u,PhoenixLayoutElement as c,useAccessService as f,useAnalyticsService as m,useAuthorizedClient as g,useConfiguratorDomainFactory as p,useConfiguratorLoadStatus as v,useConfiguratorRawLoadedData as I,useExpectPhase as C,useLoadedDataActions as h,useLoadingError as j,useLoadingPhase as k,useLogger as w,useRoutingService as y,useSpecAssistant as b}from"#package/react-sdk";import{useConfigurator as x}from"../application-runtime/index.js";import{IntegrationSubscription as E,OnConfiguratorReadyProvider as L,ViewportStateSync as D}from"../configurator-context/index.js";import{ConfiguratorNotFoundPage as M}from"./ConfiguratorNotFoundPage.js";import{DefaultConfiguratorLayout as F}from"./DefaultConfiguratorLayout.js";import{DevContextFallback as S}from"./error-boundaries/DevContextFallback.js";import{DevLoadingFallback as T}from"./error-boundaries/DevLoadingFallback.js";import{LoadingErrorBoundary as A}from"./error-boundaries/LoadingErrorBoundary.js";import{useLoad as P}from"./use-load.js";let R=i(()=>((()=>{let{assistantSystem:o}=x(),r=null!=b();t(()=>{if(r)return o.activate()},[o,r])})(),o(e,{children:o(L,{children:o(c,{name:"ConfiguratorLayout",defaultSlot:F})})})));export const LoadedConfiguratorDetailPage=()=>{C(d.SceneAssets);let{configurationId:e,accessInfo:i}=f(),t=g(),c=y(),b=w(),x=m(),{startConfiguratorLoad:L,completeConfiguratorLoad:F,failConfiguratorLoad:V,resetConfiguratorLoad:q}=h(),{isLoading:B,isError:G}=v(),N=I(),z=p(),H=n(()=>i.viewMode&&void 0!==i.revisionId?t.getConfigurationRevision(e,i.revisionId):t.getConfiguration(e),[i.revisionId,i.viewMode,t,e]),J=n(()=>{L()},[L]),K=n(o=>{F({initialRevisionId:o.revisionId,initialEntities:o.data,modelVersion:o.modelVersion,rootEntityType:o.rootEntityType,schemaIdentity:z}),x.push(s.ConfigurationLoaded(o.configurationId,o.revisionId,i.viewMode?"read":"owner")),b.appendToGlobalContext({configurationId:o.configurationId,revisionId:o.revisionId,mode:i.viewMode}),b.info("Configuration loaded.")},[i.viewMode,x,F,z,b]),O=n(o=>{if(V(o),o instanceof a)return void c.rejectAccessToken(e,i.raw);let r=o instanceof Error?o:Error("Configuration loading failed with an unknown error.",{cause:o});b.error("Configuration loading failed because the API request did not succeed. Verify the configuration ID and access token.",{configurationId:e},r)},[i.raw,e,V,b,c]),Q=n(()=>{q()},[q]);return(P({key:[t,e,i.revisionId,i.viewMode],load:H,onStart:J,onSuccess:K,onError:O,onCleanup:Q}),k({id:d.DataFetch,label:"Loading configuration…"},B),j(G),G)?o(M,{}):null===N?null:o(u,{value:N,children:o(A,{fallbackRender:r=>o(T,{...r}),children:o(l,{type:"configurator",children:o(A,{FallbackComponent:S,children:r(E,{children:[o(D,{}),o(R,{})]})})})})})};
|
package/dist/docs/index.json
CHANGED
|
@@ -133,7 +133,7 @@ The value of `WOOCOMMERCE_API_KEY` must correspond to the value of the 'API key'
|
|
|
133
133
|
settings page and `WOOCOMMERCE_PRODUCT_NAME` must correspond to the name of the product template you created.
|
|
134
134
|
|
|
135
135
|
- run development server (from this repo): `pnpm dev:server`
|
|
136
|
-
- prepare local deployment (from the implementation project): `pnpm deployment:prepare-local`
|
|
136
|
+
- prepare local deployment (from the implementation project): `pnpm phoenix deployment:prepare-local`
|
|
137
137
|
- start the dev process (from the implementation project): `pnpm dev`
|
|
138
138
|
- run bundle build (from the implementation project): `pnpm build`
|
|
139
139
|
|
|
@@ -354,19 +354,19 @@ const onGoToIndex = phx.frontend.createOnGoToIndexHandler(() => {'{'}
|
|
|
354
354
|
|
|
355
355
|
```typescript
|
|
356
356
|
// Custom Phoenix layout element
|
|
357
|
-
const MyLayoutElement = phx.frontend.createCustomPhoenixLayoutElement(() => {
|
|
357
|
+
const MyLayoutElement = phx.frontend.createCustomPhoenixLayoutElement('MyLayoutElement', () => {
|
|
358
358
|
return <div>Custom Layout</div>
|
|
359
|
-
|
|
359
|
+
})
|
|
360
360
|
|
|
361
361
|
// Custom Phoenix entity element
|
|
362
|
-
const MyEntityElement = phx.frontend.createCustomPhoenixEntityElement(() => {
|
|
362
|
+
const MyEntityElement = phx.frontend.createCustomPhoenixEntityElement('MyEntityElement', () => {
|
|
363
363
|
return <mesh>...</mesh>
|
|
364
|
-
|
|
364
|
+
})
|
|
365
365
|
|
|
366
366
|
// Custom page when auth fails
|
|
367
|
-
const NotAuthenticatedPage = phx.frontend.createNotAuthenticatedPage(() => {
|
|
367
|
+
const NotAuthenticatedPage = phx.frontend.createNotAuthenticatedPage(() => {
|
|
368
368
|
return <div>Please log in</div>
|
|
369
|
-
|
|
369
|
+
})
|
|
370
370
|
```
|
|
371
371
|
|
|
372
372
|
### Preset UI
|
|
@@ -383,6 +383,33 @@ const PresetAction = phx.frontend.createPresetPageAction(PresetHelpAction)
|
|
|
383
383
|
const PresetOverlay = phx.frontend.createPresetDeviceNotSupported(SmallScreenNotice)
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
+
### Loaded Configuration UI
|
|
387
|
+
|
|
388
|
+
A layout element registered under the name `ConfiguratorLayout` replaces the whole UI of a loaded configuration, in owner and in read mode, whatever `layout` the spec sets. Without it, Phoenix renders the layout the spec selects.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// src/elements/ConfiguratorLayout.tsx
|
|
392
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
393
|
+
|
|
394
|
+
export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', () => {
|
|
395
|
+
return (
|
|
396
|
+
<>
|
|
397
|
+
<LayoutSlots.Services />
|
|
398
|
+
<LayoutSlots.SharedCanvas />
|
|
399
|
+
<aside style={{ position: 'absolute', insetBlock: 0, insetInlineStart: 0, inlineSize: 320 }}>
|
|
400
|
+
Project sidebar
|
|
401
|
+
</aside>
|
|
402
|
+
</>
|
|
403
|
+
)
|
|
404
|
+
})
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
- `LayoutSlots.SharedCanvas` renders the scene and fills the page. The layout positions its own UI above it and after it in the markup, as the `aside` does; unpositioned content is painted under the canvas.
|
|
408
|
+
- The layout has to mount `LayoutSlots.SharedCanvas`, or the project sets `appLoader: legacy`. Otherwise the app loader stays on screen.
|
|
409
|
+
- The layout composes the rest from what `@salsita-ai/sdk/configurator` exports: `LayoutSlots.Services`, the other members of `LayoutSlots`, and `useSceneSafeArea`.
|
|
410
|
+
- Nothing of the built-in layouts is rendered for it. The finalize page is `Legacy.CommonFinalizePage`, the dialog is `LayoutDialog`, and the preview start screen is `DesktopPreview` or `MobilePreview`, which the layout shows while `usePreview().isPreview` is set. The assistant panel is not available to a project layout.
|
|
411
|
+
- Any project layout element named `ConfiguratorLayout` takes over the page.
|
|
412
|
+
|
|
386
413
|
## TransformerBuilder
|
|
387
414
|
|
|
388
415
|
Use `transformerBuilder` to create entity transformers:
|
|
@@ -35,15 +35,23 @@ interface ExpressionContext<
|
|
|
35
35
|
|
|
36
36
|
// System services
|
|
37
37
|
readonly domain: Domain // Access to domain model and other entities
|
|
38
|
-
readonly componentReader: ComponentReader // Read component states across all entities
|
|
39
38
|
readonly migrate: MigrateFunction // Data migration utilities
|
|
40
|
-
readonly
|
|
41
|
-
|
|
39
|
+
readonly componentReader: ComponentReader // Read component states across all entities
|
|
40
|
+
|
|
41
|
+
// Projections
|
|
42
|
+
readonly positionProjections: PositionProjections // World matrices of scene entities
|
|
43
|
+
readonly sketchingProjections: SketchingProjections // Shapes and connections of a sketch
|
|
44
|
+
readonly collisionProjections: CollisionProjections // Collision meshes and collisions between entities
|
|
45
|
+
readonly constraintProjections: ConstraintProjections // Spatial constraints an entity violates
|
|
46
|
+
readonly snappingProjections: SnappingProjections // Snap points, edges, planes and matches
|
|
47
|
+
readonly projectedEntitiesProjections: ProjectedEntitiesProjections // Data of projected entities
|
|
42
48
|
}
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
The context includes data sources (session, published, frontend, backend), system services (domain access, component reader, projections), and application state (mode, role). Each part serves a different purpose in expression calculations.
|
|
46
52
|
|
|
53
|
+
The SDK declares the context in two parts. `CoreExpressionContext` holds the mode, the role, the data sources, `domain` and `migrate`. `RuntimeExpressionContext` holds the component reader and the projections. An editor hover or a type error therefore shows `ctx` as `OpenExpressionContext<..., RuntimeExpressionContext<AnyDomainEntity>>`, which has the members listed above. A helper of your own that takes the context declares its parameter as `ExpressionContext` with the five types of your project.
|
|
54
|
+
|
|
47
55
|
### Configurator Modes
|
|
48
56
|
|
|
49
57
|
The `mode` property indicates the current operational context of the configurator. Different modes enable different functionality and access levels. Expressions can check the mode to change their behavior.
|
|
@@ -86,7 +86,7 @@ For inspiration and real-world implementation patterns, examine deployed Salsita
|
|
|
86
86
|
|
|
87
87
|
When you encounter challenges or need to implement specific features, use these resources in order:
|
|
88
88
|
|
|
89
|
-
**AI-Powered Assistance**: Every Phoenix project ships
|
|
89
|
+
**AI-Powered Assistance**: Every Phoenix project ships Phoenix agent skills, so coding agents in your project already know the SDK conventions and CLI. Start by using AI coding assistants in the `configurator-phoenix` project. While they may not solve complex problems directly, they can point you toward relevant examples or search terms that will help you find the right solution.
|
|
90
90
|
|
|
91
91
|
**Storybook Documentation**: Search the Phoenix Storybook for functionality similar to what you need. When you find relevant examples, examine the corresponding code in the configurator-phoenix stories to understand the implementation details.
|
|
92
92
|
|
|
@@ -29,10 +29,11 @@ pnpm phoenix dev
|
|
|
29
29
|
|
|
30
30
|
1. Refuses to run when a `spec/` folder already exists, so it never touches an existing project.
|
|
31
31
|
2. Writes a `package.json` that depends on the CLI's SDK version when the folder has none. An existing `package.json` is kept, as long as it depends on the SDK.
|
|
32
|
-
3.
|
|
33
|
-
4. Writes `
|
|
32
|
+
3. Adds the `dev`, `build`, `deploy`, `check` and `fix` scripts to `package.json` (see [Project Scripts](#project-scripts)). A script the project already has under one of those names keeps its command.
|
|
33
|
+
4. Writes a starter spec (`spec/application.yaml`, `spec/configurator.yaml`) with one resizable, recolorable box, and switches `package.json` to the application-spec pipeline.
|
|
34
|
+
5. Writes `pnpm-workspace.yaml` with the pnpm settings the first install needs: the build scripts of the SDK's dependencies are allowed or denied, and `@salsita-ai/*` packages are exempt from pnpm's minimum release age. A project inside a pnpm monorepo gets no file; the workspace root holds these settings.
|
|
34
35
|
|
|
35
|
-
It asks nothing, writes no `.env` and contacts no server. `phoenix sync` then generates every other project file, including
|
|
36
|
+
It asks nothing, writes no `.env` and contacts no server. `phoenix sync` then generates every other project file, including the agent skills described in [Project Structure](?path=/docs/phoenix-project-structure--docs).
|
|
36
37
|
|
|
37
38
|
### Which project `phoenix dev` develops in
|
|
38
39
|
|
|
@@ -114,8 +115,6 @@ For quick local development without a server, use:
|
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
117
|
phoenix dev --persistence local-storage
|
|
117
|
-
# or use the shortcut script:
|
|
118
|
-
pnpm dev:local
|
|
119
118
|
```
|
|
120
119
|
|
|
121
120
|
**Important:** The server must be running before `deployment:prepare-local` when using `server` persistence mode.
|
|
@@ -158,27 +157,19 @@ spec/application.yaml → Server prepares environment → CLI writes .env.genera
|
|
|
158
157
|
|
|
159
158
|
### Project Scripts
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
| Script
|
|
164
|
-
|
|
|
165
|
-
| `dev`
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
| `prettify:fix` | `prettier --write .` | Prettier fix only |
|
|
175
|
-
| `verify:fix` | `phoenix build && phoenix fix && phoenix check --types` | Build, fix, then type-check |
|
|
176
|
-
| `test` | `vitest --passWithNoTests run` | Run tests |
|
|
177
|
-
| `test:watch` | `vitest` | Tests in watch mode |
|
|
178
|
-
| `prepare:ci` | `phoenix prepare:ci` | CI environment setup |
|
|
179
|
-
| `deployment:prepare-local` | `phoenix deployment:prepare-local` | Prepare local environment |
|
|
180
|
-
|
|
181
|
-
Custom scripts you add to `package.json` are preserved during sync. Deprecated scripts (like `environment:create`, `deployment:register`) are automatically removed.
|
|
160
|
+
`phoenix init` writes five scripts into `package.json`, each a shortcut for the CLI command of the same name:
|
|
161
|
+
|
|
162
|
+
| Script | Command | Purpose |
|
|
163
|
+
| -------- | ---------------- | --------------------------------------------- |
|
|
164
|
+
| `dev` | `phoenix dev` | Dev server with hot reload |
|
|
165
|
+
| `build` | `phoenix build` | Production build |
|
|
166
|
+
| `deploy` | `phoenix deploy` | Build and publish the project |
|
|
167
|
+
| `check` | `phoenix check` | Run all checks (TypeScript, ESLint, Prettier) |
|
|
168
|
+
| `fix` | `phoenix fix` | Auto-fix lint and formatting issues |
|
|
169
|
+
|
|
170
|
+
The scripts are yours: change, rename or delete them, and add your own. `phoenix sync` and `phoenix clean` leave the `scripts` section of `package.json` as it is, so they add no script, restore none you removed and remove none. Every CLI command also runs without a script, as `pnpm phoenix <command>`.
|
|
171
|
+
|
|
172
|
+
A project may have other scripts than these five, such as `sync`, `test` or `verify:fix`; they are its own and stay as they are. All of this holds for a project on the application-spec pipeline, which `phoenix init` selects. In a project on the legacy pipeline (see [Current and legacy project tooling](#current-and-legacy-project-tooling)), `phoenix sync` and `phoenix clean` write their own set of scripts into `package.json` on every run, over scripts of the same name.
|
|
182
173
|
|
|
183
174
|
### Server-Generated Variables
|
|
184
175
|
|
|
@@ -178,7 +178,7 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
178
178
|
|
|
179
179
|
| File | Auto-Generated? | Notes |
|
|
180
180
|
| ------------------------------------------------------ | --------------- | -------------------------------------- |
|
|
181
|
-
| `package.json` | Partially | Dependencies merged, scripts
|
|
181
|
+
| `package.json` | Partially | Dependencies merged, scripts untouched |
|
|
182
182
|
| `tsconfig.json` | Yes | Managed by Phoenix |
|
|
183
183
|
| `eslint.config.js` | Yes | Managed by Phoenix |
|
|
184
184
|
| `prettier.config.js` | Yes | Managed by Phoenix |
|
|
@@ -190,9 +190,11 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
190
190
|
| `.env.example` | No | Template for env vars |
|
|
191
191
|
| `.env.generated` | Yes | Auto-generated IDs, git-ignored |
|
|
192
192
|
| `README.md` | No | Project documentation |
|
|
193
|
-
| `AGENTS.md` |
|
|
193
|
+
| `AGENTS.md` | No | Your own coding-agent instructions |
|
|
194
194
|
| `.claude/skills/phoenix*/`, `.agents/skills/phoenix*/` | Yes | Phoenix agent skills |
|
|
195
195
|
|
|
196
|
+
Sync does not write `README.md`, `AGENTS.md`, `.env.example`, `.env.ci` or the `README.md` files in `assets/public/` and `assets/private/`. Earlier SDK versions wrote them once; a project that has them keeps them, and they are yours to edit.
|
|
197
|
+
|
|
196
198
|
### DO NOT EDIT (Fully Auto-Generated)
|
|
197
199
|
|
|
198
200
|
- **Everything in `.phoenix/`** - Completely regenerated on each build. The one exception is `.phoenix/variants/`, where a coding agent writes the option files for `phoenix variants`; a rebuild keeps it
|
|
@@ -204,7 +206,7 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
204
206
|
|
|
205
207
|
### Agent Guidance
|
|
206
208
|
|
|
207
|
-
Coding agents (Claude Code, Codex, Cursor) read
|
|
209
|
+
Coding agents (Claude Code, Codex, Cursor) read the Phoenix agent skills. An `AGENTS.md` is yours to add for your own instructions; sync does not write one. The `phoenix`, `phoenix-cli`, `phoenix-feedback` and `phoenix-review` skills carry the SDK conventions, CLI tooling and review checklist, and the `phoenix-create-configurator` skill walks the agent from your product documents and assets to a reviewed configurator in five stages. The `phoenix-variants` skill has the agent show you several options over the dev preview when a decision is a matter of taste, and build the one you pick. Phoenix keeps them in step with the installed SDK, including the reference files inside a skill folder. During the internal beta, a project created from a canary build of the SDK gets one more section in the `phoenix` skill: once per conversation the agent checks for a newer canary and, after telling you, updates the project's SDK and runs `phoenix sync`. Other skill folders are never touched.
|
|
208
210
|
|
|
209
211
|
Projects created by earlier SDK versions had this guidance in `CLAUDE.md`, `.cursor/rules/phoenix.mdc` and `commands/review.md`. Sync removes the SDK block from the first two, keeps your own text around it, deletes a file that held nothing else, and deletes the review commands; use the `phoenix-review` skill instead of `/review`.
|
|
210
212
|
|
|
@@ -104,10 +104,10 @@ pnpm install
|
|
|
104
104
|
pnpm phoenix sync
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
`phoenix sync` keeps `spec/` and `src/` as your authored inputs and generates the application shell,
|
|
107
|
+
`phoenix sync` keeps `spec/` and `src/` as your authored inputs and generates the application shell, types, and `.phoenix/` implementation. Start development without a server:
|
|
108
108
|
|
|
109
109
|
```sh
|
|
110
|
-
pnpm dev
|
|
110
|
+
pnpm phoenix dev --persistence local-storage
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Open the URL printed by the command. The application shell loads with the default `<salsita-configurator>` element and an empty 3D scene.
|