@salsita-ai/sdk 0.50.0-develop.da8d27f5d7 → 0.50.0-develop.eb204e0da4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/application-spec/generate-application/code/templates/index.d.ts +6 -6
  2. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  3. package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +2 -0
  4. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -0
  5. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.d.ts → skills/phoenix-create-configurator-skill.d.ts} +1 -1
  6. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.js → skills/phoenix-create-configurator-skill.js} +1 -1
  7. package/dist/application-spec/generate-application/code/templates/{phoenix-feedback-skill.js → skills/phoenix-feedback-skill.js} +1 -1
  8. package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.js → skills/phoenix-review-skill.js} +2 -2
  9. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -0
  10. package/dist/application-spec/generate-application/code/templates/{common → skills}/sdk-content.d.ts +21 -0
  11. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +67 -0
  12. package/dist/client/phoenix-api.d.ts +5 -1
  13. package/dist/docs/index.json +1 -1
  14. package/dist/docs/phoenix/migrations.mdx +45 -5
  15. package/dist/docs/phoenix/phoenix-cli.mdx +1 -1
  16. package/dist/docs/phoenix/sdk-builder-workflow.mdx +4 -2
  17. package/dist/phoenix/commands/deploy.js +2 -2
  18. package/package.json +3 -3
  19. package/dist/application-spec/generate-application/code/templates/agents-md.js +0 -2
  20. package/dist/application-spec/generate-application/code/templates/common/sdk-content.js +0 -67
  21. package/dist/application-spec/generate-application/code/templates/phoenix-cli-skill.js +0 -1
  22. package/dist/application-spec/generate-application/code/templates/phoenix-skill.js +0 -1
  23. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.d.ts +0 -0
  24. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.js +0 -0
  25. /package/dist/application-spec/generate-application/code/templates/{agents-md.d.ts → skills/agents-md.d.ts} +0 -0
  26. /package/dist/application-spec/generate-application/code/templates/{phoenix-cli-skill.d.ts → skills/phoenix-cli-skill.d.ts} +0 -0
  27. /package/dist/application-spec/generate-application/code/templates/{phoenix-feedback-skill.d.ts → skills/phoenix-feedback-skill.d.ts} +0 -0
  28. /package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.d.ts → skills/phoenix-review-skill.d.ts} +0 -0
  29. /package/dist/application-spec/generate-application/code/templates/{phoenix-skill.d.ts → skills/phoenix-skill.d.ts} +0 -0
@@ -12,7 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  export { stripSdkMarkerContent } from './common/strip-sdk-marker-content.js';
15
- export { generateAgentsMd } from './agents-md.js';
15
+ export { generateAgentsMd } from './skills/agents-md.js';
16
16
  export { generateApplicationSpecAdminConfig } from './admin-config.js';
17
17
  export { generateApplicationSpecAdminCode } from './admin-domain-code.js';
18
18
  export { generateApplicationSpecAdminFrontendCode } from './admin-frontend-code.js';
@@ -42,11 +42,11 @@ export { generateApplicationSpecHtmlIndex } from './html-index.js';
42
42
  export { generateLinguiRcFile } from './lingui-rc.js';
43
43
  export { generateApplicationSpecNvmrc } from './nvmrc.js';
44
44
  export { generateApplicationSpecPackageJson } from './package-json.js';
45
- export { generatePhoenixCliSkill } from './phoenix-cli-skill.js';
46
- export { generatePhoenixCreateConfiguratorSkill, generatePhoenixCreateConfiguratorSkillReferences, } from './phoenix-create-configurator-skill.js';
47
- export { generatePhoenixFeedbackSkill } from './phoenix-feedback-skill.js';
48
- export { generatePhoenixReviewSkill } from './phoenix-review-skill.js';
49
- export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } from './phoenix-skill.js';
45
+ export { generatePhoenixCliSkill } from './skills/phoenix-cli-skill.js';
46
+ export { generatePhoenixCreateConfiguratorSkill, generatePhoenixCreateConfiguratorSkillReferences, } from './skills/phoenix-create-configurator-skill.js';
47
+ export { generatePhoenixFeedbackSkill } from './skills/phoenix-feedback-skill.js';
48
+ export { generatePhoenixReviewSkill } from './skills/phoenix-review-skill.js';
49
+ export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } from './skills/phoenix-skill.js';
50
50
  export { generateApplicationSpecPrettierConfig } from './prettier-config.js';
51
51
  export { generateApplicationSpecPrettierIgnore } from './prettier-ignore.js';
52
52
  export { generateApplicationSpecPrivateAssetsReadme } from './private-assets-readme.js';
@@ -1 +1 @@
1
- export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./phoenix-review-skill.js";export{followsCanaryBuilds,generatePhoenixSkill}from"./phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
1
+ export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./skills/agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./skills/phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./skills/phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./skills/phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./skills/phoenix-review-skill.js";export{followsCanaryBuilds,generatePhoenixSkill}from"./skills/phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
@@ -0,0 +1,2 @@
1
+ import{sdkGuidanceSections as e}from"./sdk-content.js";export const generateAgentsMd=()=>`${[e.projectIntro,e.buildCommands,e.doNotEdit,"Phoenix SDK guidance lives in the `phoenix`, `phoenix-cli`, `phoenix-create-configurator`, `phoenix-feedback` and `phoenix-review` skills under `.claude/skills/` and `.agents/skills/`. Phoenix rewrites them on every sync, removes anything else inside their folders and removes other `phoenix`-prefixed skill folders, so to customise a skill, copy it to a folder whose name does not start with `phoenix`."].join("\n\n")}
2
+ `;
@@ -0,0 +1 @@
1
+ import{renderAgentSkill as n}from"./agent-skill.js";import{sdkGuidanceSections as e}from"./sdk-content.js";export const generatePhoenixCliSkill=()=>n({name:"phoenix-cli",description:"Phoenix CLI tooling for building and inspecting this configurator, covering build, dev and sync commands, dev logs, glTF asset inspection, screenshots, runtime state, data and scene inspection, and Playwright MCP setup. Use when building, debugging build or runtime errors, checking 3D assets, or inspecting the running configurator.",sections:[e.buildCommands,e.devLogs,e.assetInspection,e.screenshotCli,e.inspectState,e.inspectData,e.inspectScene,e.playwrightMcpSetup]});
@@ -1,3 +1,3 @@
1
- import { type AgentSkillReference } from './common/agent-skill.js';
1
+ import { type AgentSkillReference } from './agent-skill.js';
2
2
  export declare const generatePhoenixCreateConfiguratorSkill: () => string;
3
3
  export declare const generatePhoenixCreateConfiguratorSkillReferences: () => readonly AgentSkillReference[];
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let t=`Guide the client through five stages: a representative first slice, the overall configuration flow, iterative options and features, finalization, and a complete review. Make the client comfortable, informed, and in control while taking responsibility for platform research, implementation, and verification.
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let t=`Guide the client through five stages: a representative first slice, the overall configuration flow, iterative options and features, finalization, and a complete review. Make the client comfortable, informed, and in control while taking responsibility for platform research, implementation, and verification.
2
2
 
3
3
  “Client” means the person creating the configurator. “Configurator user” means its intended user, such as a buyer, salesperson, or designer. Tailor the experience to that audience; do not assume a purchasing journey.`,n=`## Foundations
4
4
 
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let t=`## When to Raise Feedback
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let t=`## When to Raise Feedback
2
2
  Draft feedback for the Phoenix team when one of these happens while you work in this project:
3
3
  - **Bug** — the SDK, the \`phoenix\` CLI or generated code crashes, produces wrong output or behaves differently from its documentation, and the cause is not in this project's own code.
4
4
  - **Misleading guidance** — a doc page, a Phoenix skill or an error message sent you the wrong way, or did not say what you needed.
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let i=`Review all current git changes for correctness, Phoenix pattern violations, and security issues.
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let i=`Review all current git changes for correctness, Phoenix pattern violations, and security issues.
2
2
 
3
3
  ## Phoenix-Specific Review
4
4
 
@@ -9,7 +9,7 @@ import{renderAgentSkill as e}from"./common/agent-skill.js";let i=`Review all cur
9
9
  - [ ] Invariants have matching implementations in src/invariants/
10
10
  - [ ] Component bindings use correct entity types or aliases
11
11
  - [ ] $read/$write/$bind reference valid properties or expressions
12
- - [ ] Model version incremented when schema changes (requires new v\\*.ts migration)
12
+ - [ ] A model that has been deployed is unchanged under its version: the change is in \`model.latest\`, the deployed model is kept as \`model.v<N>\`, and \`src/migrations/v<N+1>.ts\` migrates saved configurations
13
13
 
14
14
  ### Expressions & Invariants (src/expressions/, src/invariants/)
15
15
 
@@ -0,0 +1 @@
1
+ import{renderAgentSkill as e}from"./agent-skill.js";import{sdkGuidanceSections as t}from"./sdk-content.js";export const followsCanaryBuilds=e=>!e.useWorkspace&&e.sdkVersion.includes("-develop.");export const generatePhoenixSkill=({checkLatestCanary:n})=>e({name:"phoenix",description:`Phoenix SDK project conventions, covering project structure, architecture principles, key patterns, file and export rules, webhook membership, versioning the domain model once it is deployed, showing the user the dev preview and the build of a deploy, and how to search the SDK documentation${n?", and the internal-beta check for the latest canary SDK build":""}. Use before writing or changing specs or source code in this Phoenix configurator project.`,sections:[...n?["## Stay on the Latest Canary\nDuring the internal beta this project uses the `canary` build of the SDK, which is published from every change to the platform. Once per conversation, before you change anything, compare the installed version with the newest canary:\n```bash\npnpm list @salsita-ai/sdk --depth 0\nnpm view @salsita-ai/sdk@canary version\n```\nWhen they differ, tell the user which version you are moving to, then update the project and regenerate its files:\n```bash\npnpm add @salsita-ai/sdk@canary\npnpm phoenix sync\n```\n`sync` rewrites the Phoenix skills, so read the ones you use again after it. When the user runs a globally installed `phoenix`, update that too with `npm install -g @salsita-ai/sdk@canary`. If the project no longer builds after the update, stop and tell the user; the `phoenix-feedback` skill covers reporting it."]:[],t.projectStructure,t.architecturePrinciples,t.keyPatterns,t.fileConventions,t.exportRules,t.webhookMembership,t.doNotEdit,t.modelVersioning,"## Show the Preview While You Work\nThe user should see the configurator change as you build it. Before your first change in a conversation, make sure `pnpm phoenix dev` is running (start it in the background when it is not) and open the address it prints as `Preview: <url>` (also written to `.phoenix/phoenix-dev.log`) in the user's browser: `open '<url>'` on macOS, `xdg-open '<url>'` on Linux, `Start-Process '<url>'` in PowerShell, `start \"\" \"<url>\"` in cmd. Keep the quotes, the shell would otherwise read a `?` in the address as a pattern. The log keeps the lines of earlier runs, so when you have just started dev, wait for the `Preview:` line written after your start and use the last one. Open it once; the preview reloads on every change, so tell the user to keep that tab open instead of opening it again.","## Show the Build After a Deploy\n`pnpm phoenix deploy` starts a build on the server and waits for it to go live. Right after `Build <id> started` it prints `Follow the build in Studio: <url>`. Run the deploy so that you can read its output while it runs, and open that link in the user's browser, quoted as above, as soon as it appears, so they can follow the build they just triggered. When the line is missing, the sign-in is older than the link: tell the user the build is listed under Deployments in Studio, and that `pnpm phoenix login <workspace>` brings the link back.","## Code Review\nUse the `phoenix-review` skill to review code changes before committing.",t.sdkDocumentation]});
@@ -76,6 +76,27 @@ All files in src/expressions/, src/invariants/, src/transformers/, src/component
76
76
  - \`.phoenix/\` directory — completely regenerated on each build
77
77
  - \`tsconfig.json\`, \`eslint.config.js\`, \`prettier.config.js\`, \`.prettierignore\`, \`.linguirc\`, \`.nvmrc\`
78
78
  - \`src/locales/\` .ts files — compiled from .po files`;
79
+ /** When and how a deployed domain model gets a new version and a migration, with the recipe for replacing the starter. */
80
+ readonly modelVersioning: `## Version the Domain Model Once It Is Deployed
81
+ 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.
82
+
83
+ To change a model that has been deployed as v<N>:
84
+ 1. 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.
85
+ 2. Make the change in \`model.latest\`, which is now v<N+1>.
86
+ 3. 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.
87
+
88
+ The admin model is versioned on its own, under \`src/admin/migrations/\`, with its types in \`AdminV<N>\`.
89
+
90
+ **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:
91
+ \`\`\`typescript
92
+ // src/migrations/v2.ts
93
+ import type { Migration } from '@salsita-ai/sdk/domain'
94
+
95
+ export const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [
96
+ // the entities of the new model's preset
97
+ ]
98
+ \`\`\`
99
+ An 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.`;
79
100
  /** How to find and read SDK documentation from the CLI. */
80
101
  readonly sdkDocumentation: `## SDK Documentation
81
102
 
@@ -0,0 +1,67 @@
1
+ export const sdkGuidanceSections={projectIntro:`## Phoenix Configurator Project
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/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)\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- 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
+ - Components: PascalCase (.tsx)
5
+ - Utilities: kebab-case (.ts)
6
+ - Expressions: entity-name.ts
7
+ - Migrations: v*.ts (sequential)
8
+ - Specs: kebab-case.yaml
9
+ - Underscore folders (_shared/, _utils/) are NOT auto-imported`,exportRules:`## Export Rules
10
+ All files in src/expressions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.`,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\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
11
+
12
+ Get the current editor context data as JSON via \`pnpm phoenix inspect:data\`.
13
+
14
+ **Prerequisites:** same as screenshot (dev server running + browser open)
15
+
16
+ **Options:**
17
+ - \`--output, -o\` — Output file path (prints to stdout if omitted)
18
+ - \`--pretty\` — Pretty-print JSON (default: true)
19
+ - \`--port\` — WebSocket server port (default: 3020)
20
+
21
+ **Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties
22
+
23
+ **Example:** \`pnpm phoenix inspect:data -o ./data.json\`
24
+
25
+ **Tips:** Pipe through \`head\`/\`tail\` for partial output (e.g., \`pnpm phoenix inspect:data | head -30\`).
26
+
27
+ **Use cases:** AI agent context, debugging published/session/access data`,inspectScene:`## Inspect: Scene
28
+
29
+ Get the current Three.js scene graph as JSON via \`pnpm phoenix inspect:scene\`.
30
+
31
+ **Prerequisites:** same as screenshot (dev server running + browser open)
32
+
33
+ **Options:**
34
+ - \`--output, -o\` — Output file path (prints to stdout if omitted)
35
+ - \`--pretty\` — Pretty-print JSON (default: true)
36
+ - \`--port\` — WebSocket server port (default: 3020)
37
+
38
+ **Returns:** Recursive scene tree with per-node: name, type, visible, position, rotation, scale, userData (entityId), and for meshes: geometry name, bounding box, material properties (color, metalness, roughness, opacity, textures), morph target influences. Lights include color, intensity, and type-specific properties.
39
+
40
+ **Example:** \`pnpm phoenix inspect:scene -o ./scene.json\`
41
+
42
+ **Tips:** Scene graphs can be large. Pipe through \`head\`/\`tail\` for partial output.
43
+
44
+ **Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting`,playwrightMcpSetup:`## Playwright MCP Setup (for AI agents)
45
+
46
+ To interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to \`~/.claude/.mcp.json\`:
47
+
48
+ \`\`\`json
49
+ {
50
+ "mcpServers": {
51
+ "playwright": {
52
+ "command": "npx",
53
+ "args": [
54
+ "@playwright/mcp@latest",
55
+ "--headed",
56
+ "--launch-options",
57
+ "{\\"args\\":[\\"--use-gl=swiftshader\\",\\"--enable-webgl\\",\\"--ignore-gpu-blocklist\\"]}"
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ \`\`\`
63
+
64
+ Key flags:
65
+ - \`--headed\` — visible browser (required for GPU rendering)
66
+ - \`--use-gl=swiftshader\` — software GPU fallback for environments without a physical GPU
67
+ - \`--enable-webgl\` + \`--ignore-gpu-blocklist\` — ensure WebGL is available`};
@@ -618,7 +618,7 @@ export interface paths {
618
618
  }
619
619
  readonly get?: never
620
620
  readonly put?: never
621
- /** @description Create a build for `phoenix deploy` and a presigned POST that uploads its source archive. S3 refuses uploads over `maxBytes`. */
621
+ /** @description Create a build for `phoenix deploy` and a presigned POST that uploads its source archive. S3 refuses uploads over `maxBytes`. A project whose domain model changed under an already deployed model version is refused before the upload. */
622
622
  readonly post: operations['createUploadBuild']
623
623
  readonly delete?: never
624
624
  readonly options?: never
@@ -4433,6 +4433,10 @@ export interface operations {
4433
4433
  readonly requestBody: {
4434
4434
  readonly content: {
4435
4435
  readonly 'application/json': {
4436
+ /** @description The admin model schema of the project, checked like `configurationSchema`. */
4437
+ readonly adminConfigurationSchema?: components['schemas']['ConfigurationSchema']
4438
+ /** @description The configurator model schema of the project. When it differs from the schema deployed under the same model version, no build is created and the response is 400. */
4439
+ readonly configurationSchema?: components['schemas']['ConfigurationSchema']
4436
4440
  /** @description Version of the CLI that uploads the sources. */
4437
4441
  readonly sdkVersion: string
4438
4442
  /** @description Size of the source archive the CLI will upload. */
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-08T09:21:48.882Z",
2
+ "generatedAt": "2026-10-08T13:07:36.446Z",
3
3
  "categories": [
4
4
  {
5
5
  "name": "Phoenix",
@@ -18,6 +18,8 @@ Each version defines its own:
18
18
 
19
19
  This versioning allows you to track changes over time and ensure proper migration paths.
20
20
 
21
+ The version number is not written anywhere: `latest` is the highest numbered version plus one, so a model with only `latest` is version 1. Once a version has been deployed it cannot change, because saved configurations depend on it. `phoenix deploy` refuses a model that differs from the one deployed under the same version, before anything is uploaded. To change a deployed model, keep it unchanged as `v<N>`, make the change in `latest` and add the migration to version N+1. Development environments replace the deployed model instead, so `phoenix dev` accepts every change.
22
+
21
23
  ## Model File Structure
22
24
 
23
25
  It's recommended to split your model versions into separate files for better maintainability:
@@ -39,9 +41,11 @@ The CLI generates separate TypeScript types for each version, which helps ensure
39
41
 
40
42
  ```typescript
41
43
  // The Migration type ensures you handle all entity types
42
- type Migration<FromEntity, ToEntity> = (state: FromEntity[]) => ToEntity[]
44
+ type Migration<FromEntity, ToEntity> = (state: readonly FromEntity[]) => readonly ToEntity[]
43
45
  ```
44
46
 
47
+ The types of each version are generated as global namespaces: `ModelV1`, `ModelV2`, and so on. `ModelV1.AnyDomainEntity` is the union of all entities of version 1, and `ModelV1.<EntityType>` is one of them. The admin model has its own namespaces, `AdminV1`, `AdminV2`, and so on. A migration needs no import for them.
48
+
45
49
  ## Migration File Location
46
50
 
47
51
  Migration files are organized in the `src/migrations/` directory with a specific naming convention:
@@ -63,11 +67,16 @@ Each migration file should export a function named `migrateToVN` where N is the
63
67
 
64
68
  ```typescript
65
69
  // src/migrations/v2.ts
66
- export const migrateToV2: Migration<V1Entity, V2Entity> = state => {
70
+ import type { Migration } from '@salsita-ai/sdk/domain'
71
+
72
+ export const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = state => {
67
73
  // Transform entities from v1 to v2
74
+ return state
68
75
  }
69
76
  ```
70
77
 
78
+ Keep the `Migration<...>` type on the export, with its `readonly` arrays: the generated domain file passes each migration to the migration builder, and a function typed with mutable arrays does not type-check there.
79
+
71
80
  **Note:** Migrations start from version 2. There is no migration TO v1 since v1 is the initial version. The first migration you write will be `migrateToV2`.
72
81
 
73
82
  The `ts-pattern` library is recommended for implementing migrations as it provides type-safe pattern matching.
@@ -77,11 +86,15 @@ The `ts-pattern` library is recommended for implementing migrations as it provid
77
86
  Migrations are implemented as functions that transform entities from one version to another. Each migration handles the transition between consecutive versions:
78
87
 
79
88
  ```typescript
80
- export const migrateToV4: Migration<V3Entity, V4Entity> = state => {
89
+ import { match } from 'ts-pattern'
90
+
91
+ import type { Migration } from '@salsita-ai/sdk/domain'
92
+
93
+ export const migrateToV4: Migration<ModelV3.AnyDomainEntity, ModelV4.AnyDomainEntity> = state => {
81
94
  return state.map(entity => {
82
95
  return (
83
96
  match(entity)
84
- .returnType<V4Entity>()
97
+ .returnType<ModelV4.AnyDomainEntity>()
85
98
  .with({ type: 'Burner' }, value => ({ ...value, y: 0 }))
86
99
  .with({ type: 'Cabinet' }, value => ({ ...value, y: 0 }))
87
100
  // ... handle other entities
@@ -91,6 +104,33 @@ export const migrateToV4: Migration<V3Entity, V4Entity> = state => {
91
104
  }
92
105
  ```
93
106
 
107
+ ### Renaming an Entity Type
108
+
109
+ An entity id starts with its entity type, such as `Box-1`, and is typed per entity type (`DomainEntityId<'Box'>`). The runtime validates that an entity's id matches its type, so an entity that keeps its data under a new type name needs a new id, and every relation that points at it must be rewritten to that id. The id is built from a string, which TypeScript cannot check, so this is one place where a cast is needed:
110
+
111
+ ```typescript
112
+ import { parseDomainEntityId } from '@salsita-ai/sdk/domain'
113
+
114
+ const toTableId = (id: ModelV1.Box['id']) =>
115
+ // The id is rebuilt for the new type name; nothing narrower than a cast can type a template string as an id
116
+ `Table-${parseDomainEntityId(id).sequenceNumber}` as ModelV2.Table['id']
117
+ ```
118
+
119
+ ### Replacing the Model Entirely
120
+
121
+ When the new model has nothing in common with the deployed one, there is nothing to convert. This is the case when a project that was first deployed as the starter gets its real model: the starter's `Box` model is version 1 in production, and a deployed version cannot change. Keep the deployed model as `v1`, define the real model in `latest`, and let the migration return the starting state of the new model, the `initialDomainEntities` of its preset:
122
+
123
+ ```typescript
124
+ // src/migrations/v2.ts
125
+ import type { Migration } from '@salsita-ai/sdk/domain'
126
+
127
+ export const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [
128
+ // the entities of the new model's preset
129
+ ]
130
+ ```
131
+
132
+ No entity of a type that the new version no longer has may stay in the returned state: the migrated state is validated against the latest model, and an unknown entity type fails the load.
133
+
94
134
  ## Migration Runtime
95
135
 
96
136
  Migrations are executed automatically every time the domain is loaded, both in the client application and in webhooks. During this process:
@@ -114,7 +154,7 @@ When implementing migrations:
114
154
 
115
155
  4. **Testing**: Thoroughly test migrations with real-world data to ensure they handle all edge cases, use existing configurations from previous versions.
116
156
 
117
- 5. **Version Bumping**: Consider bumping the model version for both breaking changes (removing fields, changing types) and additive changes where existing data needs proper defaults. If you add a new required field, existing configurations need a migration to set that field's default value.
157
+ 5. **Version Bumping**: Every change to a model that has been deployed needs a new version, whether it is breaking (removing fields, changing types) or additive. If you add a new required field, existing configurations need a migration to set that field's default value; a change that needs no conversion still needs a migration that returns the state unchanged.
118
158
 
119
159
  ## Admin Model Migrations
120
160
 
@@ -592,7 +592,7 @@ Revokes the token on the server and removes it from the credentials file. `--reg
592
592
 
593
593
  ### `phoenix deploy`
594
594
 
595
- Validates the spec, adds the pnpm settings `phoenix init` writes to `pnpm-workspace.yaml` when they are missing, packs and uploads the project, starts its build on the server and follows it until the configurator is live. Once the build starts it prints `Follow the build in Studio: <url>`, the build's page in the Studio you signed in through; a sign-in stored by an older CLI has no Studio URL, so sign in again to get the link. `--region` or `--serverUrl` selects the server, as for `login`; `--no-wait` returns as soon as the build starts.
595
+ Validates the spec, adds the pnpm settings `phoenix init` writes to `pnpm-workspace.yaml` when they are missing, packs and uploads the project, starts its build on the server and follows it until the configurator is live. Before the upload the server compares the domain model with the deployed one: a model that changed under an already deployed model version is refused with the reason, and needs a new model version and a [migration](?path=/docs/phoenix-migrations--docs). Once the build starts it prints `Follow the build in Studio: <url>`, the build's page in the Studio you signed in through; a sign-in stored by an older CLI has no Studio URL, so sign in again to get the link. `--region` or `--serverUrl` selects the server, as for `login`; `--no-wait` returns as soon as the build starts.
596
596
 
597
597
  ### `phoenix feedback <message>`
598
598
 
@@ -77,8 +77,9 @@ pnpm phoenix deploy
77
77
 
78
78
  1. It validates the application spec.
79
79
  2. It packs the project files: the `.yaml` files in `spec`, everything in `src`, `mocks`, `assets`, `public` and `templates`, and `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `.gitignore`, `CLAUDE.md` and `.cursor/rules/phoenix.mdc`. Every `node_modules`, `.phoenix` and `__generated` folder, every `.DS_Store` and `._*` file and every `.env` and `.env.*` file are left out, at any depth.
80
- 3. It uploads the archive, which may be up to 200 MB.
81
- 4. It starts the build, prints a link to the build's page in Studio and follows the build until your configurator is live, then prints its URL.
80
+ 3. It asks the server for a build. The server compares your domain model with the deployed one and refuses, before anything is uploaded, a model that changed under an already deployed model version; see [Migrations](?path=/docs/phoenix-migrations--docs).
81
+ 4. It uploads the archive, which may be up to 200 MB.
82
+ 5. It starts the build, prints a link to the build's page in Studio and follows the build until your configurator is live, then prints its URL.
82
83
 
83
84
  On the server, the install follows your `pnpm-lock.yaml` and `pnpm-workspace.yaml`, so it allows the same build scripts and package versions as `pnpm install` on your computer. The build runs without platform secrets. It sees only your own environment variables and the public platform values, the [build environment](?path=/docs/phoenix-phoenix-cli--docs) that `phoenix deployment:build-envs` writes. The trusted deploy step then publishes the frontend, webhooks and assets.
84
85
 
@@ -100,6 +101,7 @@ Coding agents working in your project use the same command, guided by the genera
100
101
  ## Known limits
101
102
 
102
103
  - You can deploy once the workspace's first deployment is live. Until then, `phoenix deploy` tells you the workspace is still being set up.
104
+ - The first deployment is the starter, so the starter's model is version 1 in production. Replacing it with your own model needs the starter's model kept as `v1` and a migration to version 2; [Migrations](?path=/docs/phoenix-migrations--docs) shows the migration for replacing a model entirely.
103
105
  - One workspace has one project, one `production` environment and one development environment per user.
104
106
  - `phoenix deploy` always deploys to the workspace's default project; it doesn't read `project` from the spec yet.
105
107
  - Only `phoenix dev` works from the sign-in. `phoenix deployment:prepare-dev` still needs the `.env` block of a Studio development environment.
@@ -1,4 +1,4 @@
1
- import{openAsBlob as e}from"node:fs";import{createApiBaseClient as t}from"#package/client";import{resolveApplicationSpecDeploymentData as i}from"../application-spec/index.js";import{createSourceArchive as r,getServerCredentials as o}from"../services/index.js";import{ensureWorkspaceInstallSettings as a}from"../utils/index.js";let s={queued:"Waiting for a build runner",preparing:"Preparing the deployment",building:"Building the project",deploying:"Deploying the frontend, webhooks and assets",live:"Live",failed:"Failed",cancelled:"Cancelled"},l=e=>new Promise(t=>setTimeout(t,e)),n=async(t,i)=>{let r=new FormData;for(let[e,i]of Object.entries(t.fields))r.append(e,i);r.append("file",await e(i,{type:"application/gzip"}),"sources.tgz");let o=await fetch(t.url,{method:"POST",body:r});if(!o.ok)throw Error(`Uploading the sources failed with HTTP ${o.status}: ${await o.text()}`)};export const runDeployCommand=async(e,{logger:d})=>{let u=await o(e.serverUrl),{pollIntervalMs:p}=e;if(!u)throw Error(`You are not signed in to ${e.serverUrl}. Run \`phoenix login <workspace>\` first.`);let c=t(e.serverUrl),f={authorization:`Bearer ${u.token}`};d.step("Validating the application spec");let h=await i({skipUpload:!0,logger:d});d.success(`The spec is valid (application version ${h.applicationVersion})`),d.step(`Packing the project for ${u.customerName}`),a(e.projectDir);let m=await r(e.projectDir);try{let t;d.info(` ${m.fileCount} files, ${(t=m.size)<1048576?`${(t/1024).toFixed(1)} KB`:`${(t/1024/1024).toFixed(1)} MB`}`);let i=await c.POST("/deployments/builds",{body:{sdkVersion:e.sdkVersion,sizeBytes:m.size},headers:f});if(i.error||!i.data)throw Error(`The server did not create a build: ${i.error?.message??"no response"}`);let{buildId:r,upload:o}=i.data;d.step("Uploading the sources"),await n(o,m.path);let a=await c.POST("/deployments/builds/{buildId}/start",{params:{path:{buildId:r}},headers:{...f,"content-type":null}});if(a.error)throw Error(`The server did not start the build: ${a.error.message}`);if(a.data?.status==="failed")throw Error(`Build ${r} could not be started: ${a.data.error??"the server gave no reason"}`);if(d.success(`Build ${r} started`),u.studioUrl){let e=new URL(`/builder/deployments/${r}`,u.studioUrl);e.searchParams.set("customer",u.customerSlug),d.info(`Follow the build in Studio: ${e.toString()}`)}if(!e.wait)return;let h=null,g=0;for(;;){let{data:e,error:t}=await c.GET("/deployments/builds/{buildId}",{params:{path:{buildId:r}},headers:f}).catch(e=>({data:void 0,error:{message:String(e)}}));if(t||!e){if((g+=1)>=5)throw Error(`Following build ${r} failed: ${t?.message??"no response"}. The build may still finish; check Deployments in Studio.`);await l(p);continue}if(g=0,e.status!==h&&(h=e.status,d.step(s[e.status])),"live"===e.status)return void d.success(`Your configurator is live at ${e.configuratorUrl}`);if("failed"===e.status||"cancelled"===e.status||e.timedOut){let t=await c.GET("/deployments/builds/{buildId}/log",{params:{path:{buildId:r}},headers:f}).catch(()=>({data:void 0})),i=(t.data?.log??"").trim().split("\n").slice(-40).join("\n");i&&d.info(`
1
+ import{openAsBlob as e}from"node:fs";import{createApiBaseClient as t}from"#package/client";import{resolveApplicationSpecDeploymentData as i}from"../application-spec/index.js";import{createSourceArchive as r,getServerCredentials as a}from"../services/index.js";import{ensureWorkspaceInstallSettings as o}from"../utils/index.js";let s={queued:"Waiting for a build runner",preparing:"Preparing the deployment",building:"Building the project",deploying:"Deploying the frontend, webhooks and assets",live:"Live",failed:"Failed",cancelled:"Cancelled"},l=e=>new Promise(t=>setTimeout(t,e)),n=async(t,i)=>{let r=new FormData;for(let[e,i]of Object.entries(t.fields))r.append(e,i);r.append("file",await e(i,{type:"application/gzip"}),"sources.tgz");let a=await fetch(t.url,{method:"POST",body:r});if(!a.ok)throw Error(`Uploading the sources failed with HTTP ${a.status}: ${await a.text()}`)};export const runDeployCommand=async(e,{logger:d})=>{let u=await a(e.serverUrl),{pollIntervalMs:p}=e;if(!u)throw Error(`You are not signed in to ${e.serverUrl}. Run \`phoenix login <workspace>\` first.`);let c=t(e.serverUrl),m={authorization:`Bearer ${u.token}`};d.step("Validating the application spec");let h=await i({skipUpload:!0,logger:d});d.success(`The spec is valid (application version ${h.applicationVersion})`),d.step(`Packing the project for ${u.customerName}`),o(e.projectDir);let f=await r(e.projectDir);try{let t;d.info(` ${f.fileCount} files, ${(t=f.size)<1048576?`${(t/1024).toFixed(1)} KB`:`${(t/1024/1024).toFixed(1)} MB`}`);let i=await c.POST("/deployments/builds",{body:{sdkVersion:e.sdkVersion,sizeBytes:f.size,configurationSchema:{version:h.schemaVersion,schema:h.schema},adminConfigurationSchema:h.adminSchema?{version:h.adminSchemaVersion,schema:h.adminSchema}:void 0},headers:m});if(i.error||!i.data)throw Error(`The server did not create a build: ${i.error?.message??"no response"}`);let{buildId:r,upload:a}=i.data;d.step("Uploading the sources"),await n(a,f.path);let o=await c.POST("/deployments/builds/{buildId}/start",{params:{path:{buildId:r}},headers:{...m,"content-type":null}});if(o.error)throw Error(`The server did not start the build: ${o.error.message}`);if(o.data?.status==="failed")throw Error(`Build ${r} could not be started: ${o.data.error??"the server gave no reason"}`);if(d.success(`Build ${r} started`),u.studioUrl){let e=new URL(`/builder/deployments/${r}`,u.studioUrl);e.searchParams.set("customer",u.customerSlug),d.info(`Follow the build in Studio: ${e.toString()}`)}if(!e.wait)return;let g=null,w=0;for(;;){let{data:e,error:t}=await c.GET("/deployments/builds/{buildId}",{params:{path:{buildId:r}},headers:m}).catch(e=>({data:void 0,error:{message:String(e)}}));if(t||!e){if((w+=1)>=5)throw Error(`Following build ${r} failed: ${t?.message??"no response"}. The build may still finish; check Deployments in Studio.`);await l(p);continue}if(w=0,e.status!==g&&(g=e.status,d.step(s[e.status])),"live"===e.status)return void d.success(`Your configurator is live at ${e.configuratorUrl}`);if("failed"===e.status||"cancelled"===e.status||e.timedOut){let t=await c.GET("/deployments/builds/{buildId}/log",{params:{path:{buildId:r}},headers:m}).catch(()=>({data:void 0})),i=(t.data?.log??"").trim().split("\n").slice(-40).join("\n");i&&d.info(`
2
2
  Last lines of the build log:
3
3
  ${i}
4
- `);let o=e.timedOut?"it took too long":e.error??"see the build log";throw Error(`Build ${r} did not go live: ${o}`)}await l(p)}}finally{await m.cleanup()}};
4
+ `);let a=e.timedOut?"it took too long":e.error??"see the build log";throw Error(`Build ${r} did not go live: ${a}`)}await l(p)}}finally{await f.cleanup()}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salsita-ai/sdk",
3
- "version": "0.50.0-develop.da8d27f5d7",
3
+ "version": "0.50.0-develop.eb204e0da4",
4
4
  "description": "TypeScript SDK and CLI for building, running, and deploying Phoenix configurator applications.",
5
5
  "keywords": [
6
6
  "salsita",
@@ -295,8 +295,8 @@
295
295
  "yaml": "2.9.0",
296
296
  "yargs": "17.7.2",
297
297
  "zod": "3.25.76",
298
- "@salsita-ai/common": "0.50.0-develop.da8d27f5d7",
299
- "@salsita-ai/contracts": "0.50.0-develop.da8d27f5d7"
298
+ "@salsita-ai/common": "0.50.0-develop.eb204e0da4",
299
+ "@salsita-ai/contracts": "0.50.0-develop.eb204e0da4"
300
300
  },
301
301
  "devDependencies": {
302
302
  "@clerk/clerk-react": "5.61.8",
@@ -1,2 +0,0 @@
1
- import{sdkGuidanceSections as e}from"./common/sdk-content.js";export const generateAgentsMd=()=>`${[e.projectIntro,e.buildCommands,e.doNotEdit,"Phoenix SDK guidance lives in the `phoenix`, `phoenix-cli`, `phoenix-create-configurator`, `phoenix-feedback` and `phoenix-review` skills under `.claude/skills/` and `.agents/skills/`. Phoenix rewrites them on every sync, removes anything else inside their folders and removes other `phoenix`-prefixed skill folders, so to customise a skill, copy it to a folder whose name does not start with `phoenix`."].join("\n\n")}
2
- `;
@@ -1,67 +0,0 @@
1
- export const sdkGuidanceSections={projectIntro:`## Phoenix Configurator Project
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/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)\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- 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
- - Components: PascalCase (.tsx)
5
- - Utilities: kebab-case (.ts)
6
- - Expressions: entity-name.ts
7
- - Migrations: v*.ts (sequential)
8
- - Specs: kebab-case.yaml
9
- - Underscore folders (_shared/, _utils/) are NOT auto-imported`,exportRules:`## Export Rules
10
- All files in src/expressions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.`,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\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files — compiled from .po files",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
11
-
12
- Get the current editor context data as JSON via \`pnpm phoenix inspect:data\`.
13
-
14
- **Prerequisites:** same as screenshot (dev server running + browser open)
15
-
16
- **Options:**
17
- - \`--output, -o\` — Output file path (prints to stdout if omitted)
18
- - \`--pretty\` — Pretty-print JSON (default: true)
19
- - \`--port\` — WebSocket server port (default: 3020)
20
-
21
- **Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties
22
-
23
- **Example:** \`pnpm phoenix inspect:data -o ./data.json\`
24
-
25
- **Tips:** Pipe through \`head\`/\`tail\` for partial output (e.g., \`pnpm phoenix inspect:data | head -30\`).
26
-
27
- **Use cases:** AI agent context, debugging published/session/access data`,inspectScene:`## Inspect: Scene
28
-
29
- Get the current Three.js scene graph as JSON via \`pnpm phoenix inspect:scene\`.
30
-
31
- **Prerequisites:** same as screenshot (dev server running + browser open)
32
-
33
- **Options:**
34
- - \`--output, -o\` — Output file path (prints to stdout if omitted)
35
- - \`--pretty\` — Pretty-print JSON (default: true)
36
- - \`--port\` — WebSocket server port (default: 3020)
37
-
38
- **Returns:** Recursive scene tree with per-node: name, type, visible, position, rotation, scale, userData (entityId), and for meshes: geometry name, bounding box, material properties (color, metalness, roughness, opacity, textures), morph target influences. Lights include color, intensity, and type-specific properties.
39
-
40
- **Example:** \`pnpm phoenix inspect:scene -o ./scene.json\`
41
-
42
- **Tips:** Scene graphs can be large. Pipe through \`head\`/\`tail\` for partial output.
43
-
44
- **Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting`,playwrightMcpSetup:`## Playwright MCP Setup (for AI agents)
45
-
46
- To interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to \`~/.claude/.mcp.json\`:
47
-
48
- \`\`\`json
49
- {
50
- "mcpServers": {
51
- "playwright": {
52
- "command": "npx",
53
- "args": [
54
- "@playwright/mcp@latest",
55
- "--headed",
56
- "--launch-options",
57
- "{\\"args\\":[\\"--use-gl=swiftshader\\",\\"--enable-webgl\\",\\"--ignore-gpu-blocklist\\"]}"
58
- ]
59
- }
60
- }
61
- }
62
- \`\`\`
63
-
64
- Key flags:
65
- - \`--headed\` — visible browser (required for GPU rendering)
66
- - \`--use-gl=swiftshader\` — software GPU fallback for environments without a physical GPU
67
- - \`--enable-webgl\` + \`--ignore-gpu-blocklist\` — ensure WebGL is available`};
@@ -1 +0,0 @@
1
- import{renderAgentSkill as n}from"./common/agent-skill.js";import{sdkGuidanceSections as e}from"./common/sdk-content.js";export const generatePhoenixCliSkill=()=>n({name:"phoenix-cli",description:"Phoenix CLI tooling for building and inspecting this configurator, covering build, dev and sync commands, dev logs, glTF asset inspection, screenshots, runtime state, data and scene inspection, and Playwright MCP setup. Use when building, debugging build or runtime errors, checking 3D assets, or inspecting the running configurator.",sections:[e.buildCommands,e.devLogs,e.assetInspection,e.screenshotCli,e.inspectState,e.inspectData,e.inspectScene,e.playwrightMcpSetup]});
@@ -1 +0,0 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";import{sdkGuidanceSections as t}from"./common/sdk-content.js";export const followsCanaryBuilds=e=>!e.useWorkspace&&e.sdkVersion.includes("-develop.");export const generatePhoenixSkill=({checkLatestCanary:n})=>e({name:"phoenix",description:`Phoenix SDK project conventions, covering project structure, architecture principles, key patterns, file and export rules, webhook membership, showing the user the dev preview and the build of a deploy, and how to search the SDK documentation${n?", and the internal-beta check for the latest canary SDK build":""}. Use before writing or changing specs or source code in this Phoenix configurator project.`,sections:[...n?["## Stay on the Latest Canary\nDuring the internal beta this project uses the `canary` build of the SDK, which is published from every change to the platform. Once per conversation, before you change anything, compare the installed version with the newest canary:\n```bash\npnpm list @salsita-ai/sdk --depth 0\nnpm view @salsita-ai/sdk@canary version\n```\nWhen they differ, tell the user which version you are moving to, then update the project and regenerate its files:\n```bash\npnpm add @salsita-ai/sdk@canary\npnpm phoenix sync\n```\n`sync` rewrites the Phoenix skills, so read the ones you use again after it. When the user runs a globally installed `phoenix`, update that too with `npm install -g @salsita-ai/sdk@canary`. If the project no longer builds after the update, stop and tell the user; the `phoenix-feedback` skill covers reporting it."]:[],t.projectStructure,t.architecturePrinciples,t.keyPatterns,t.fileConventions,t.exportRules,t.webhookMembership,t.doNotEdit,"## Show the Preview While You Work\nThe user should see the configurator change as you build it. Before your first change in a conversation, make sure `pnpm phoenix dev` is running (start it in the background when it is not) and open the address it prints as `Preview: <url>` (also written to `.phoenix/phoenix-dev.log`) in the user's browser: `open '<url>'` on macOS, `xdg-open '<url>'` on Linux, `Start-Process '<url>'` in PowerShell, `start \"\" \"<url>\"` in cmd. Keep the quotes, the shell would otherwise read a `?` in the address as a pattern. The log keeps the lines of earlier runs, so when you have just started dev, wait for the `Preview:` line written after your start and use the last one. Open it once; the preview reloads on every change, so tell the user to keep that tab open instead of opening it again.","## Show the Build After a Deploy\n`pnpm phoenix deploy` starts a build on the server and waits for it to go live. Right after `Build <id> started` it prints `Follow the build in Studio: <url>`. Run the deploy so that you can read its output while it runs, and open that link in the user's browser, quoted as above, as soon as it appears, so they can follow the build they just triggered. When the line is missing, the sign-in is older than the link: tell the user the build is listed under Deployments in Studio, and that `pnpm phoenix login <workspace>` brings the link back.","## Code Review\nUse the `phoenix-review` skill to review code changes before committing.",t.sdkDocumentation]});