@salsita-ai/sdk 0.50.0-develop.c28cfb7ded → 0.50.0-develop.ca72986c39

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 (53) hide show
  1. package/README.md +13 -12
  2. package/dist/application-spec/generate-application/code/generate-application-code.d.ts +3 -2
  3. package/dist/application-spec/generate-application/code/generate-application-code.js +1 -1
  4. package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +9 -4
  5. package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
  6. package/dist/application-spec/generate-application/code/templates/frontend-config.js +3 -5
  7. package/dist/application-spec/generate-application/code/templates/gitignore.js +1 -2
  8. package/dist/application-spec/generate-application/code/templates/globals.js +3 -4
  9. package/dist/application-spec/generate-application/code/templates/index.d.ts +1 -0
  10. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  11. package/dist/application-spec/generate-application/code/templates/root-tsconfig.d.ts +6 -0
  12. package/dist/application-spec/generate-application/code/templates/root-tsconfig.js +2 -0
  13. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -1
  14. package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.js +1 -1
  15. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +5 -4
  16. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +2 -2
  17. package/dist/application-spec/generate-application/code/templates/tsconfig.d.ts +5 -0
  18. package/dist/application-spec/generate-application/code/templates/tsconfig.js +1 -1
  19. package/dist/application-spec/generate-application/code/templates/webhooks-config.js +2 -3
  20. package/dist/application-spec/parse-application-files/categorize/categorize-files.d.ts +10 -0
  21. package/dist/application-spec/parse-application-files/categorize/categorize-files.js +1 -1
  22. package/dist/application-spec/parse-application-files/categorize/earlier-generated-tsconfig.d.ts +9 -0
  23. package/dist/application-spec/parse-application-files/categorize/earlier-generated-tsconfig.js +1 -0
  24. package/dist/client/phoenix-api.d.ts +367 -0
  25. package/dist/docs/index.json +1 -1
  26. package/dist/docs/phoenix/deployments.mdx +2 -6
  27. package/dist/docs/phoenix/getting-started.mdx +1 -0
  28. package/dist/docs/phoenix/localization.mdx +4 -4
  29. package/dist/docs/phoenix/migration-guide.mdx +19 -8
  30. package/dist/docs/phoenix/phoenix-cli.mdx +36 -6
  31. package/dist/docs/phoenix/project-structure.mdx +17 -5
  32. package/dist/docs/phoenix/sdk-builder-workflow.mdx +1 -1
  33. package/dist/docs/phoenix/webhooks.mdx +4 -4
  34. package/dist/globals/index.d.ts +2 -0
  35. package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
  36. package/dist/phoenix/application-spec/project-files.js +1 -1
  37. package/dist/phoenix/cli.js +1 -1
  38. package/dist/phoenix/commands/check.js +1 -1
  39. package/dist/phoenix/commands/deps/deps.d.ts +52 -0
  40. package/dist/phoenix/commands/deps/deps.js +3 -0
  41. package/dist/phoenix/commands/deps/index.d.ts +1 -0
  42. package/dist/phoenix/commands/deps/index.js +1 -0
  43. package/dist/phoenix/commands/deps/sdk-manifest.d.ts +12 -0
  44. package/dist/phoenix/commands/deps/sdk-manifest.js +1 -0
  45. package/dist/phoenix/commands/dev.js +5 -5
  46. package/dist/phoenix/commands/index.d.ts +1 -0
  47. package/dist/phoenix/commands/index.js +1 -1
  48. package/dist/phoenix/services/application-spec-project-watcher.d.ts +2 -0
  49. package/dist/phoenix/services/application-spec-project-watcher.js +2 -2
  50. package/dist/phoenix/services/swc.js +1 -1
  51. package/dist/phoenix/utils/index.d.ts +1 -0
  52. package/dist/phoenix/utils/index.js +1 -1
  53. package/package.json +3 -6
package/README.md CHANGED
@@ -42,17 +42,18 @@ Generated code exposes the `phx` global in project files: `phx.domain` for the m
42
42
 
43
43
  Run the CLI through the project's package manager so its version matches the installed SDK.
44
44
 
45
- | Command | What it does |
46
- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
47
- | `phoenix init [folder]` | Starts a project: `package.json` when missing, the five project scripts it lacks, and a starter spec. |
48
- | `phoenix sync` | Regenerates project files and aligns dependencies. |
49
- | `phoenix dev` | Development server with hot reload, webhook tunnel and the editor. |
50
- | `phoenix build` | Production build of the frontend and the webhooks. |
51
- | `phoenix check` / `fix` | Type check, lint and format; `fix` applies the automatic fixes. |
52
- | `phoenix login <workspace>` | Signs in through Studio; the token is kept in `~/.phoenix/credentials.json`. |
53
- | `phoenix deploy` | Uploads the project sources for a platform build. |
54
- | `phoenix docs` | Searches and reads the SDK documentation bundled with the package. |
55
- | `phoenix feedback "<message>"` | Sends feedback about the SDK, CLI, documentation or Studio to Salsita; `--file <path>` sends a text file instead. |
45
+ | Command | What it does |
46
+ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
47
+ | `phoenix init [folder]` | Starts a project: `package.json` when missing, the five project scripts it lacks, and a starter spec. |
48
+ | `phoenix sync` | Regenerates project files and aligns dependencies. |
49
+ | `phoenix deps` | Adds the SDK's missing peer dependencies to `package.json` and moves out-of-range ones into range; installs nothing. |
50
+ | `phoenix dev` | Development server with hot reload, webhook tunnel and the editor. |
51
+ | `phoenix build` | Production build of the frontend and the webhooks. |
52
+ | `phoenix check` / `fix` | Type check, lint and format; `fix` applies the automatic fixes. |
53
+ | `phoenix login <workspace>` | Signs in through Studio; the token is kept in `~/.phoenix/credentials.json`. |
54
+ | `phoenix deploy` | Uploads the project sources for a platform build. |
55
+ | `phoenix docs` | Searches and reads the SDK documentation bundled with the package. |
56
+ | `phoenix feedback "<message>"` | Sends feedback about the SDK, CLI, documentation or Studio to Salsita; `--file <path>` sends a text file instead. |
56
57
 
57
58
  Every frontend `.js` file `phoenix build` emits starts with a comment naming the application version from `spec/application.yaml`, the git commit when the build runs in a checkout, and the SDK version. `--banner <text>` replaces the application line with your own text. The line is public in every bundle:
58
59
 
@@ -88,7 +89,7 @@ import { getPhoenixThemes } from '@salsita-ai/sdk/ui'
88
89
  | `webhook` | Webhook builders, finalization task factories, templates, and backend handlers. |
89
90
  | `application-spec` | Application-spec parsing, validation, code generation, and runtime generation. |
90
91
  | `i18n` | SDK catalogs, locale fallback, and catalog-merging utilities. |
91
- | `globals` | Global runtime and asset-module TypeScript declarations. |
92
+ | `globals` | Global runtime and asset-module TypeScript declarations, including Vite's client types. |
92
93
  | `watch` | File watching and glob matching used by the toolchain. |
93
94
  | `common/*` | `utils`, `env`, `eslint` and `prettier` from `@salsita-ai/common`. |
94
95
  | `contracts/*` | `api`, `clerk`, `domain`, `chat`, `assistant` and `webhook` from `@salsita-ai/contracts`. |
@@ -10,8 +10,9 @@
10
10
  *
11
11
  * Each group module documents the criterion that decides its membership, so a new template
12
12
  * self-classifies into exactly one of them. This module owns only the aggregate and adds no files of
13
- * its own: it concatenates the two application-file groups, in that order, into the `.phoenix`
14
- * output, and spreads the scaffolding group's project-root buckets alongside them.
13
+ * its own: it concatenates the two application-file groups and the scaffolding group's `.phoenix`
14
+ * files, in that order, into the `.phoenix` output, and spreads the scaffolding group's project-root
15
+ * buckets alongside them.
15
16
  *
16
17
  * Generated projects always bind phx globally, so the aggregate selects `globalPhxBindingStrategy`.
17
18
  * A module-scoped binding is only meaningful to a consumer calling the domain-runtime group on its
@@ -1 +1 @@
1
- import{GeneratedApplication as o}from"../../core/index.js";import{generateDomainRuntimeFiles as e,globalPhxBindingStrategy as i}from"./domain-runtime-files.js";import{generateProjectScaffolding as r}from"./project-scaffolding.js";import{generateStandaloneAppFiles as t}from"./standalone-app-files.js";export const generateApplicationCode=m=>o({files:[...e(m,i),...t(m)],...r(m)});
1
+ import{GeneratedApplication as o}from"../../core/index.js";import{generateDomainRuntimeFiles as e,globalPhxBindingStrategy as r}from"./domain-runtime-files.js";import{generateProjectScaffolding as i}from"./project-scaffolding.js";import{generateStandaloneAppFiles as t}from"./standalone-app-files.js";export const generateApplicationCode=m=>{let{phoenixFiles:n,...p}=i(m);return o({files:[...e(m,r),...t(m),...n],...p})};
@@ -27,8 +27,9 @@ export declare const agentSkillRoots: readonly ['.claude/skills', '.agents/skill
27
27
  * The SDK version pins the generated `package.json` dependency, `cliUseWorkspace` lets an explicit
28
28
  * CLI flag override the spec's workspace mode, and the optional name and description populate the
29
29
  * project metadata. The categorized files carry the project's existing `package.json` and `.gitignore`
30
- * so those templates can merge rather than clobber, and the legacy cursor rules and `CLAUDE.md` so the
31
- * SDK block can be stripped from them.
30
+ * so those templates can merge rather than clobber, the legacy cursor rules and `CLAUDE.md` so the
31
+ * SDK block can be stripped from them, and whether the root `tsconfig.json` is the one earlier SDK
32
+ * versions generated, so it can be replaced.
32
33
  *
33
34
  * `projectInGitRepository` says whether the project folder is inside a git work tree. `.gitignore` is
34
35
  * generated only then: a project that is not under git has no use for the file, and one it already
@@ -46,10 +47,13 @@ export interface GenerateProjectScaffoldingOptions {
46
47
  readonly description?: string;
47
48
  }
48
49
  /**
49
- * Project-root file buckets produced independently from the application runtime files.
50
+ * Project-root file buckets produced independently from the application runtime files, and the
51
+ * project configuration that lives in `.phoenix/`.
50
52
  *
51
53
  * The buckets are kept apart because they carry different write intent, which the caching layer
52
- * acts on: `generatedProjectFiles` are content-diffed and rewritten whenever they differ,
54
+ * acts on: `phoenixFiles` are paths relative to `.phoenix/` and join the application files there,
55
+ * rewritten whenever they differ and removed with the rest of the folder's stale output;
56
+ * `generatedProjectFiles` are content-diffed and rewritten whenever they differ,
53
57
  * `initialProjectFiles` are written only when absent and are the project's to edit afterwards, and
54
58
  * `prettierFiles` are separated so a writer can emit the formatting configuration ahead of the
55
59
  * output it formats. `removedProjectFiles` lists project-relative paths of files earlier SDK versions
@@ -57,6 +61,7 @@ export interface GenerateProjectScaffoldingOptions {
57
61
  * into, they express that intent without implementing the write behavior themselves.
58
62
  */
59
63
  export interface GeneratedProjectScaffolding {
64
+ readonly phoenixFiles: readonly GeneratedApplicationFile[];
60
65
  readonly generatedProjectFiles: readonly GeneratedApplicationFile[];
61
66
  readonly initialProjectFiles: readonly GeneratedApplicationFile[];
62
67
  readonly prettierFiles: readonly GeneratedApplicationFile[];
@@ -1,6 +1,6 @@
1
- import{GeneratedApplicationFile as e}from"../../core/index.js";import{followsCanaryBuilds as t,generateApplicationSpecEslintConfig as n,generateApplicationSpecFavicon as r,generateApplicationSpecGitignore as o,generateApplicationSpecNvmrc as i,generateApplicationSpecPackageJson as a,generateApplicationSpecPrettierConfig as c,generateApplicationSpecRobotsTxt as s,generateApplicationSpecTsconfig as l,generateLinguiRcFile as p,generatePhoenixCliSkill as f,generatePhoenixCreateConfiguratorSkill as m,generatePhoenixCreateConfiguratorSkillReferences as d,generatePhoenixFeedbackSkill as g,generatePhoenixReviewSkill as u,generatePhoenixSkill as x,generatePhoenixVariantsSkill as h,stripSdkMarkerContent as j}from"./templates/index.js";export const agentSkillRoots=[".claude/skills",".agents/skills"];let v=[{path:"CLAUDE.md",generatedPrefix:""},{path:".cursor/rules/phoenix.mdc",generatedPrefix:`---
1
+ import{GeneratedApplicationFile as e}from"../../core/index.js";import{followsCanaryBuilds as t,generateApplicationSpecEslintConfig as n,generateApplicationSpecFavicon as i,generateApplicationSpecGitignore as r,generateApplicationSpecNvmrc as o,generateApplicationSpecPackageJson as a,generateApplicationSpecPrettierConfig as s,generateApplicationSpecRobotsTxt as c,generateApplicationSpecRootTsconfig as l,generateApplicationSpecTsconfig as p,generateLinguiRcFile as f,generatePhoenixCliSkill as m,generatePhoenixCreateConfiguratorSkill as d,generatePhoenixCreateConfiguratorSkillReferences as g,generatePhoenixFeedbackSkill as j,generatePhoenixReviewSkill as u,generatePhoenixSkill as x,generatePhoenixVariantsSkill as h,stripSdkMarkerContent as F}from"./templates/index.js";export const agentSkillRoots=[".claude/skills",".agents/skills"];let P=[{path:"CLAUDE.md",generatedPrefix:""},{path:".cursor/rules/phoenix.mdc",generatedPrefix:`---
2
2
  description: Phoenix SDK project rules
3
3
  globs:
4
4
  alwaysApply: true
5
5
  ---
6
- `}],k=[".claude/commands/review.md",".cursor/commands/review.md"],b=(e,t)=>e.projectFiles.find(e=>e.file.name===t)?.file.content;export const generateProjectScaffolding=F=>{let P,{applicationSpec:w,categorizedFiles:C,sdkVersion:L,projectInGitRepository:S=!0,cliUseWorkspace:$,name:y,description:M}=F,A=b(C,"package.json"),D=b(C,".gitignore"),K=void 0!==$?$:w.appMetadata.workspace,R=(t=>{let n=[],r=[...k];for(let{path:o,generatedPrefix:i}of v){let a=b(t,o);if(void 0===a)continue;let c=j(a,i);void 0===c?r.push(o):c!==a&&n.push(e(o,c,{format:!1}))}return{strippedFiles:n,removedFiles:r}})(C);return{generatedProjectFiles:[e("package.json",a({sdkVersion:L,useWorkspace:K,useClerk:w.appMetadata.useClerk??!1,name:y,description:M,existingContent:A})),e(".nvmrc",i(),{format:!1}),e(".linguirc",p(w.configurator.ui.availableLocales),{format:!1}),e("tsconfig.json",l()),e("eslint.config.js",n()),...S?[e(".gitignore",o(D))]:[],...(P=[{name:"phoenix",content:x({checkLatestCanary:t({useWorkspace:K,sdkVersion:L})}),references:[]},{name:"phoenix-cli",content:f(),references:[]},{name:"phoenix-create-configurator",content:m(),references:d()},{name:"phoenix-feedback",content:g(),references:[]},{name:"phoenix-review",content:u(),references:[]},{name:"phoenix-variants",content:h(),references:[]}],agentSkillRoots.flatMap(t=>P.flatMap(n=>[e(`${t}/${n.name}/SKILL.md`,n.content),...n.references.map(r=>e(`${t}/${n.name}/${r.path}`,r.content))]))),...R.strippedFiles],initialProjectFiles:[e("public/favicon.ico",r(),{binary:!0}),e("public/robots.txt",s(),{format:!1})],prettierFiles:[e("prettier.config.js",c())],removedProjectFiles:R.removedFiles}};
6
+ `}],v=[".claude/commands/review.md",".cursor/commands/review.md"],k=(e,t)=>e.projectFiles.find(e=>e.file.name===t)?.file.content;export const generateProjectScaffolding=b=>{let w,C,{applicationSpec:L,categorizedFiles:S,sdkVersion:$,projectInGitRepository:y=!0,cliUseWorkspace:M,name:A,description:D}=b,E=k(S,"package.json"),K=k(S,".gitignore"),R=void 0!==M?M:L.appMetadata.workspace,G=(t=>{let n=[],i=[...v];for(let{path:r,generatedPrefix:o}of P){let a=k(t,r);if(void 0===a)continue;let s=F(a,o);void 0===s?i.push(r):s!==a&&n.push(e(r,s,{format:!1}))}return{strippedFiles:n,removedFiles:i}})(S),I=(w=e("tsconfig.json",l()),S.hasEarlierGeneratedTsconfig?{generatedProjectFiles:[w],initialProjectFiles:[]}:{generatedProjectFiles:[],initialProjectFiles:[w]});return{phoenixFiles:[e("tsconfig.json",p())],generatedProjectFiles:[e("package.json",a({sdkVersion:$,useWorkspace:R,useClerk:L.appMetadata.useClerk??!1,name:A,description:D,existingContent:E})),e(".nvmrc",o(),{format:!1}),e(".linguirc",f(L.configurator.ui.availableLocales),{format:!1}),...I.generatedProjectFiles,e("eslint.config.js",n()),...y?[e(".gitignore",r(K))]:[],...(C=[{name:"phoenix",content:x({checkLatestCanary:t({useWorkspace:R,sdkVersion:$})}),references:[]},{name:"phoenix-cli",content:m(),references:[]},{name:"phoenix-create-configurator",content:d(),references:g()},{name:"phoenix-feedback",content:j(),references:[]},{name:"phoenix-review",content:u(),references:[]},{name:"phoenix-variants",content:h(),references:[]}],agentSkillRoots.flatMap(t=>C.flatMap(n=>[e(`${t}/${n.name}/SKILL.md`,n.content),...n.references.map(i=>e(`${t}/${n.name}/${i.path}`,i.content))]))),...G.strippedFiles],initialProjectFiles:[...I.initialProjectFiles,e("public/favicon.ico",i(),{binary:!0}),e("public/robots.txt",c(),{format:!1})],prettierFiles:[e("prettier.config.js",s())],removedProjectFiles:G.removedFiles}};
@@ -1,9 +1,7 @@
1
- import{generateEnvReader as e}from"../common/env-reader.js";import{autoHeader as t}from"../common/file-headers.js";let o={IS_DEV:{type:"bool",default:!1,devDefault:!0},SERVER_URL:{type:"str",devDefault:"http://localhost:3000"},ENVIRONMENT_ID:{type:"str"},DEPLOYMENT_ID:{type:"str"},GTM_ID:{type:"str"},GTM_DEBUG:{type:"bool",default:!1,devDefault:!0},PERSISTENCE_MODE:{type:"str",default:"server",devDefault:"server",choices:["memory","local-storage","server"]},DATADOG_SITE:{type:"str"},DATADOG_CLIENT_TOKEN:{type:"str"},DATADOG_ENABLED:{type:"bool",default:!0,devDefault:!1},SERVICE_NAME:{type:"str",devDefault:"local"},RESOURCES_URL:{type:"str",devDefault:"http://localhost:5173"}},r={CLERK_PUBLISHABLE_KEY:{type:"str",required:!0}};export const generateApplicationSpecFrontendConfig=a=>{let n=a.infra.env?.frontend??{},d=a.appMetadata.useClerk?r:{},{imports:l,config:i}=e({...o,...d,...n}),p=l.length>0?`import { ${l.join(", ")} } from 'envalid'
1
+ import{generateEnvReader as e}from"../common/env-reader.js";import{autoHeader as t}from"../common/file-headers.js";let o={IS_DEV:{type:"bool",default:!1,devDefault:!0},SERVER_URL:{type:"str",devDefault:"http://localhost:3000"},ENVIRONMENT_ID:{type:"str"},DEPLOYMENT_ID:{type:"str"},GTM_ID:{type:"str"},GTM_DEBUG:{type:"bool",default:!1,devDefault:!0},PERSISTENCE_MODE:{type:"str",default:"server",devDefault:"server",choices:["memory","local-storage","server"]},DATADOG_SITE:{type:"str"},DATADOG_CLIENT_TOKEN:{type:"str"},DATADOG_ENABLED:{type:"bool",default:!0,devDefault:!1},SERVICE_NAME:{type:"str",devDefault:"local"},RESOURCES_URL:{type:"str",devDefault:"http://localhost:5173"}},r={CLERK_PUBLISHABLE_KEY:{type:"str",required:!0}};export const generateApplicationSpecFrontendConfig=a=>{let n=a.infra.env?.frontend??{},d=a.appMetadata.useClerk?r:{},{imports:l,config:p}=e({...o,...d,...n}),i=[...l,"createEnvReader"].sort();return`${t("frontend.config.ts","Runtime environment config for the frontend (Vite-injected env vars).")}
2
+ import { ${i.join(", ")} } from '@salsita-ai/sdk/common/env'
2
3
 
3
- `:"";return`${t("frontend.config.ts","Runtime environment config for the frontend (Vite-injected env vars).")}
4
- ${p}import { createEnvReader } from '@salsita-ai/sdk/common/env'
5
-
6
- export const readRuntimeConfig = createEnvReader(${i})
4
+ export const readRuntimeConfig = createEnvReader(${p})
7
5
 
8
6
  export type RuntimeConfig = ReturnType<typeof readRuntimeConfig>
9
7
 
@@ -53,9 +53,8 @@ phoenix-api.yaml
53
53
  phoenix.config.ts
54
54
  prettier.config.js
55
55
  eslint.config.js
56
- tsconfig.json
57
56
  .prettierignore
58
57
  .linguirc
59
58
  public/robots.txt
60
59
  ${gitignoreEndMarker}
61
- `;export const generateApplicationSpecGitignore=t=>{if(!t)return e;let n=t.includes("\r\n")?"\r\n":"\n",i=t.replace(/\r?\n/g,n),r=e.replace(/\r?\n/g,n),o=i.indexOf(gitignoreStartMarker),s=i.indexOf(gitignoreEndMarker);if(-1!==o&&-1!==s&&s>o){let e=i.substring(0,o),t=i.substring(s+gitignoreEndMarker.length);return(t=t.replace(/^(?:\r?\n)+/,"")).trim().length>0?e+r+n+t:e+r}return i.trim()?r+`${n}# Custom entries${n}`+i:r};
60
+ `;export const generateApplicationSpecGitignore=t=>{if(!t)return e;let i=t.includes("\r\n")?"\r\n":"\n",n=t.replace(/\r?\n/g,i),r=e.replace(/\r?\n/g,i),o=n.indexOf(gitignoreStartMarker),s=n.indexOf(gitignoreEndMarker);if(-1!==o&&-1!==s&&s>o){let e=n.substring(0,o),t=n.substring(s+gitignoreEndMarker.length);return(t=t.replace(/^(?:\r?\n)+/,"")).trim().length>0?e+r+i+t:e+r}return n.trim()?r+`${i}# Custom entries${i}`+n:r};
@@ -1,9 +1,9 @@
1
- import{autoHeader as e}from"../common/file-headers.js";import{generateApplicationSpecTypeDeclarations as a}from"./application-types.js";export const generateApplicationSpecGlobals=(o,t)=>{let n,i,r,l,d,s,p,m,y,{declarations:c,imports:f}=a(o,t,2);f.addTypeImport("@salsita-ai/sdk/domain","EnumLike");let u=[c,(n=o.configurator.latestModel,i=o.admin.latestModel,r=n.entityTypes.map(String),l=[...n.aliases].map(([e])=>String(e)),d=[...n.commonTypes].map(([e])=>String(e)),s=i.entityTypes.map(String),p=[...i.aliases].map(([e])=>String(e)),m=[...i.commonTypes].map(([e])=>String(e)),y=(e,a)=>e.map(e=>` ${a(e)}`).join("\n"),` interface Model {
2
- ${y([...r,...d,...l],e=>`readonly ${e}: ${e}`)}
1
+ import{autoHeader as e}from"../common/file-headers.js";import{generateApplicationSpecTypeDeclarations as a}from"./application-types.js";export const generateApplicationSpecGlobals=(o,n)=>{let t,i,l,r,d,s,p,m,y,{declarations:c,imports:f}=a(o,n,2);f.addTypeImport("@salsita-ai/sdk/domain","EnumLike");let u=[c,(t=o.configurator.latestModel,i=o.admin.latestModel,l=t.entityTypes.map(String),r=[...t.aliases].map(([e])=>String(e)),d=[...t.commonTypes].map(([e])=>String(e)),s=i.entityTypes.map(String),p=[...i.aliases].map(([e])=>String(e)),m=[...i.commonTypes].map(([e])=>String(e)),y=(e,a)=>e.map(e=>` ${a(e)}`).join("\n"),` interface Model {
2
+ ${y([...l,...d,...r],e=>`readonly ${e}: ${e}`)}
3
3
  }
4
4
 
5
5
  interface EnhancedModel {
6
- ${y([...r,...l],e=>`readonly $${e}: $${e}`)}
6
+ ${y([...l,...r],e=>`readonly $${e}: $${e}`)}
7
7
  }
8
8
 
9
9
  interface AdminModel {
@@ -38,7 +38,6 @@ ${y([...s,...p],e=>`readonly $${e}: Admin.$${e}`)}
38
38
  readonly ApplicationTypes: ApplicationTypes
39
39
  }`)].filter(Boolean).join("\n\n");return`${e("globals.d.ts","Global type declarations for the phx namespace, env configs and domain types.")}
40
40
  /* eslint-disable @typescript-eslint/triple-slash-reference, @typescript-eslint/consistent-type-imports */
41
- /// <reference types="vite/client" />
42
41
  /// <reference types="@salsita-ai/sdk/globals" />
43
42
 
44
43
  ${f.toTypeOnlyString(".phoenix/globals.d.ts")}
@@ -47,6 +47,7 @@ export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } f
47
47
  export { generatePhoenixVariantsSkill } from './skills/phoenix-variants-skill.js';
48
48
  export { generateApplicationSpecPrettierConfig } from './prettier-config.js';
49
49
  export { generateApplicationSpecRobotsTxt } from './robots-txt.js';
50
+ export { generateApplicationSpecRootTsconfig } from './root-tsconfig.js';
50
51
  export { generateApplicationSpecStoryDevDeclaration, generateApplicationSpecStoryPhxModule, generateApplicationSpecStoryRawProjectFiles, generateApplicationSpecStoryWrapper, } from './story/index.js';
51
52
  export { generateApplicationSpecTsconfig } from './tsconfig.js';
52
53
  export { generateApplicationSpecWebhookBuilders } from './webhook-builders.js';
@@ -1 +1 @@
1
- export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.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{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{generatePhoenixVariantsSkill}from"./skills/phoenix-variants-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.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{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
1
+ export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.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{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{generatePhoenixVariantsSkill}from"./skills/phoenix-variants-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecRootTsconfig}from"./root-tsconfig.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The project's root `tsconfig.json`, written once when the project has none: it extends the
3
+ * generated settings in `.phoenix/tsconfig.json`, so editors and tools that look for a
4
+ * `tsconfig.json` in the project root find them. It is the project's afterwards, to add to.
5
+ */
6
+ export declare const generateApplicationSpecRootTsconfig: () => string;
@@ -0,0 +1,2 @@
1
+ export const generateApplicationSpecRootTsconfig=()=>`{ "extends": "./.phoenix/tsconfig.json" }
2
+ `;
@@ -1 +1 @@
1
- import{renderAgentSkill as e}from"./agent-skill.js";import{sdkGuidanceSections as n}from"./sdk-content.js";export const generatePhoenixCliSkill=()=>e({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, variant previews, and Playwright MCP setup. Use when building, debugging build or runtime errors, checking 3D assets, or inspecting the running configurator.",sections:[n.buildCommands,n.devLogs,n.assetInspection,n.screenshotCli,n.inspectState,n.inspectData,n.inspectScene,n.variantPreviews,n.playwrightMcpSetup]});
1
+ import{renderAgentSkill as e}from"./agent-skill.js";import{sdkGuidanceSections as n}from"./sdk-content.js";export const generatePhoenixCliSkill=()=>e({name:"phoenix-cli",description:"Phoenix CLI tooling for building and inspecting this configurator, covering build, dev, sync and deps commands, dev logs, glTF asset inspection, screenshots, runtime state, data and scene inspection, variant previews, and Playwright MCP setup. Use when building, debugging build or runtime errors, checking 3D assets, or inspecting the running configurator.",sections:[n.buildCommands,n.devLogs,n.assetInspection,n.screenshotCli,n.inspectState,n.inspectData,n.inspectScene,n.variantPreviews,n.playwrightMcpSetup]});
@@ -66,7 +66,7 @@ import{renderAgentSkill as e}from"./agent-skill.js";let s=`Review all current gi
66
66
 
67
67
  ## Auto-Generated Files — DO NOT modify
68
68
 
69
- - .phoenix/ directory (except the option files in .phoenix/variants/, which the phoenix-variants skill writes), tsconfig.json, eslint.config.js, prettier.config.js
69
+ - .phoenix/ directory (except the option files in .phoenix/variants/, which the phoenix-variants skill writes), eslint.config.js, prettier.config.js
70
70
  - src/locales/\\*.ts files
71
71
 
72
72
  ### Runtime Verification
@@ -6,11 +6,12 @@
6
6
  * only in which sections they carry.
7
7
  */
8
8
  export declare const sdkGuidanceSections: {
9
- /** The three everyday `phoenix` package scripts. */
9
+ /** The everyday `phoenix` commands, and the one that brings `package.json` in line with the SDK. */
10
10
  readonly buildCommands: `## Build Commands
11
11
  - \`pnpm phoenix build\` — Full production build
12
12
  - \`pnpm phoenix dev\` — Development mode with hot reload
13
- - \`pnpm phoenix sync\` — Sync generated files without full build`;
13
+ - \`pnpm phoenix sync\` — Sync generated files without full build
14
+ - \`pnpm phoenix deps\` — Add the SDK's missing peer dependencies to \`package.json\` and move out-of-range ones into the SDK's range; it installs nothing, so run \`pnpm install\` after it`;
14
15
  /** Where `phoenix dev` writes its log and how to read it. */
15
16
  readonly devLogs: `## Dev Logs
16
17
  - \`pnpm phoenix dev\` writes all logs to \`.phoenix/phoenix-dev.log\`
@@ -80,8 +81,8 @@ The default export must be what the folder's phx factory returns (for example \`
80
81
  - Keep browser rendering in an unpinned file. Move shared server-safe logic into a separate module when both frontend and webhook code need it.`;
81
82
  /** Generated files a project must not edit. */
82
83
  readonly doNotEdit: `## Do Not Edit
83
- - \`.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
84
- - \`tsconfig.json\`, \`eslint.config.js\`, \`prettier.config.js\`, \`.linguirc\`, \`.nvmrc\`
84
+ - \`.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. The project's TypeScript settings come from \`.phoenix/tsconfig.json\`; the root \`tsconfig.json\` is the project's and extends it. \`phoenix dev\`, \`phoenix build\` and the webhook bundle do not resolve \`paths\` aliases, so an alias added to the root file passes \`phoenix check\` and fails the build; use relative imports
85
+ - \`eslint.config.js\`, \`prettier.config.js\`, \`.linguirc\`, \`.nvmrc\`
85
86
  - \`src/locales/\` .ts files — compiled from .po files
86
87
  - \`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\``;
87
88
  /** When and how a deployed domain model gets a new version and a migration, with the recipe for replacing the starter. */
@@ -1,10 +1,10 @@
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 - `ConfiguratorLayout`: a layout element with this name replaces everything the built-in layout renders for a loaded configuration, in owner and read mode, finalization included. Prefer arranging the spec's layout components; write one for shell-level changes, such as moving the sidebar or adding an area the built-in layout has no place for\n - Mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in a positioned root covering the page, or the app loader stays on screen; put the UI above it, positioned and after it in the markup. Call `useSceneSafeArea()` (same import) and put `containerRef` on the element covering the whole canvas and `safeAreaRef` on the region the UI leaves free, with `pointerEvents: 'none'`, so the camera frames the scene there\n - Sidebar moved, contents as the spec configures them: mount `LayoutSlots.DesktopSidebar` in a positioned container that sets its size, with `side=\"start\"` on the left; `docs/layouts/sidebar/in-a-custom-layout.mdx` is the whole layout\n - Your own UI: \"Hooks for a Project Layout\" in `docs/phoenix/configurator-interface.mdx` lists the hooks with what each returns (`pnpm phoenix docs -r docs/phoenix/configurator-interface.mdx`), `docs/phoenix/layout-slots.mdx` the built-in pieces you can still mount, and `docs/layouts/custom-layout.mdx` is a worked example\n - Messages: mount `LayoutSlots.Services` for the built-in snackbars, or show violations, side-effect hints and errors yourself with `phx.frontend.useConfiguratorMessages(onMessage)`, not both. Either way mount a snackbar root (`LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot`): save and built-in action errors are always snackbars\n - Finalize over the spec's `finalizeAction` components, each naming a handler registered with `phx.frontend.createPhoenixHandler`: run each with `phx.frontend.useFinalizeAction(handler)` in a component of its own wrapped in `PhoenixHandlerBoundary` (from `@salsita-ai/sdk/react`), or mount `FinalizeActions` (from `@salsita-ai/sdk/configurator`) for the built-in buttons. `finalizePage` and `finalizeButton` are legacy\n - Read mode and fullscreen are the layout's job, no hook enforces them: hide what a viewer must not change while `phx.frontend.useAccessService().accessInfo.viewMode` is `true`, and remove panels while `phx.frontend.useIsFullscreenActive()` is set; `phx.frontend.useSetFullscreen()` returns its setter\n- `src/handlers/` — Event & lifecycle handlers\n- `src/webhooks/` — Server-side webhook handlers (`phx.webhooks.*Builder`); the webhook is the default export of `src/webhooks/index.ts`, which you create when the project needs its own handlers — without it the build uses a built-in inert webhook that answers only anonymous auth, so create the file before setting `useAuthHook` or `useClerk`\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\n- `pnpm phoenix deps` — Add the SDK's missing peer dependencies to `package.json` and move out-of-range ones into the SDK's range; it installs nothing, so run `pnpm install` after it",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 - `ConfiguratorLayout`: a layout element with this name replaces everything the built-in layout renders for a loaded configuration, in owner and read mode, finalization included. Prefer arranging the spec's layout components; write one for shell-level changes, such as moving the sidebar or adding an area the built-in layout has no place for\n - Mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in a positioned root covering the page, or the app loader stays on screen; put the UI above it, positioned and after it in the markup. Call `useSceneSafeArea()` (same import) and put `containerRef` on the element covering the whole canvas and `safeAreaRef` on the region the UI leaves free, with `pointerEvents: 'none'`, so the camera frames the scene there\n - Sidebar moved, contents as the spec configures them: mount `LayoutSlots.DesktopSidebar` in a positioned container that sets its size, with `side=\"start\"` on the left; `docs/layouts/sidebar/in-a-custom-layout.mdx` is the whole layout\n - Your own UI: \"Hooks for a Project Layout\" in `docs/phoenix/configurator-interface.mdx` lists the hooks with what each returns (`pnpm phoenix docs -r docs/phoenix/configurator-interface.mdx`), `docs/phoenix/layout-slots.mdx` the built-in pieces you can still mount, and `docs/layouts/custom-layout.mdx` is a worked example\n - Messages: mount `LayoutSlots.Services` for the built-in snackbars, or show violations, side-effect hints and errors yourself with `phx.frontend.useConfiguratorMessages(onMessage)`, not both. Either way mount a snackbar root (`LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot`): save and built-in action errors are always snackbars\n - Finalize over the spec's `finalizeAction` components, each naming a handler registered with `phx.frontend.createPhoenixHandler`: run each with `phx.frontend.useFinalizeAction(handler)` in a component of its own wrapped in `PhoenixHandlerBoundary` (from `@salsita-ai/sdk/react`), or mount `FinalizeActions` (from `@salsita-ai/sdk/configurator`) for the built-in buttons. `finalizePage` and `finalizeButton` are legacy\n - Read mode and fullscreen are the layout's job, no hook enforces them: hide what a viewer must not change while `phx.frontend.useAccessService().accessInfo.viewMode` is `true`, and remove panels while `phx.frontend.useIsFullscreenActive()` is set; `phx.frontend.useSetFullscreen()` returns its setter\n- `src/handlers/` — Event & lifecycle handlers\n- `src/webhooks/` — Server-side webhook handlers (`phx.webhooks.*Builder`); the webhook is the default export of `src/webhooks/index.ts`, which you create when the project needs its own handlers — without it the build uses a built-in inert webhook that answers only anonymous auth, so create the file before setting `useAuthHook` or `useClerk`\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
2
2
  - Components: PascalCase (.tsx)
3
3
  - Utilities: kebab-case (.ts)
4
4
  - Expressions: entity-name.ts
5
5
  - Migrations: v*.ts (sequential)
6
6
  - Specs: kebab-case.yaml
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 `phx.domain.createAssistantSetupHandler`, `createMapPublishedRevisionDataHandler`, `createMapPublishedChunkedDataHandler`, `createDataRowValidatorHandler`, or `createSheetValidatorHandler` is included in the webhook automatically.\n- An assistant setup handler receives the declaration-shaped whitelist and { role, sessionData, access }. Keep the exact entity/alias split from YAML; inherited fields and expanded member entries are absent. Narrow original declarations, then the SDK expands them. Use project-defined role/session data for per-user customization. Keep the handler synchronous, pure and deterministic. The handler runs in the browser and webhook. Return null to disable assistant execution, or omit whitelist entries to restrict them. Button and panel visibility use spec presence; project developers own dynamic UI filtering through normal layout mechanics such as $filter. Leaves expose optional descriptions; writable options allow only validate: true and allowDryRuns: false; recipe groups allow descriptions and allowDryRuns: false. Deletable entries are {}. Names, component bindings, hints and types are internal. Omitted overrides retain baseline values. Existing entity, enhanced-entity and action types constrain the handler; no separate generated manifest types. Browser GraphQL, tools and change recording use the session-derived manifest; rejected setup fails closed. UI reads the pure spec selector. Omitted server instructions or AI fields preserve the baseline. An empty server instructions string clears it.\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`, `.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
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 `phx.domain.createAssistantSetupHandler`, `createMapPublishedRevisionDataHandler`, `createMapPublishedChunkedDataHandler`, `createDataRowValidatorHandler`, or `createSheetValidatorHandler` is included in the webhook automatically.\n- An assistant setup handler receives the declaration-shaped whitelist and { role, sessionData, access }. Keep the exact entity/alias split from YAML; inherited fields and expanded member entries are absent. Narrow original declarations, then the SDK expands them. Use project-defined role/session data for per-user customization. Keep the handler synchronous, pure and deterministic. The handler runs in the browser and webhook. Return null to disable assistant execution, or omit whitelist entries to restrict them. Button and panel visibility use spec presence; project developers own dynamic UI filtering through normal layout mechanics such as $filter. Leaves expose optional descriptions; writable options allow only validate: true and allowDryRuns: false; recipe groups allow descriptions and allowDryRuns: false. Deletable entries are {}. Names, component bindings, hints and types are internal. Omitted overrides retain baseline values. Existing entity, enhanced-entity and action types constrain the handler; no separate generated manifest types. Browser GraphQL, tools and change recording use the session-derived manifest; rejected setup fails closed. UI reads the pure spec selector. Omitted server instructions or AI fields preserve the baseline. An empty server instructions string clears it.\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. The project's TypeScript settings come from `.phoenix/tsconfig.json`; the root `tsconfig.json` is the project's and extends it. `phoenix dev`, `phoenix build` and the webhook bundle do not resolve `paths` aliases, so an alias added to the root file passes `phoenix check` and fails the build; use relative imports\n- `eslint.config.js`, `prettier.config.js`, `.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
8
8
 
9
9
  Get the current editor context data as JSON via \`pnpm phoenix inspect:data\`.
10
10
 
@@ -1 +1,6 @@
1
+ /**
2
+ * The project's TypeScript settings, generated to `.phoenix/tsconfig.json` and rewritten whenever they
3
+ * differ. Paths are relative to `.phoenix/`. The project's root `tsconfig.json` extends this file, and
4
+ * `phoenix check` uses it directly when the project has no root `tsconfig.json`.
5
+ */
1
6
  export declare const generateApplicationSpecTsconfig: () => string;
@@ -31,6 +31,6 @@ export const generateApplicationSpecTsconfig=()=>`{
31
31
 
32
32
  "paths": {}
33
33
  },
34
- "include": ["src", ".phoenix", "prettier.config.js", "eslint.config.js", "./.phoenix/globals.d.ts"]
34
+ "include": ["../src", ".", "../prettier.config.js", "../eslint.config.js", "./globals.d.ts"]
35
35
  }
36
36
  `;
@@ -1,6 +1,5 @@
1
- import{generateEnvReader as e}from"../common/env-reader.js";import{autoHeader as r}from"../common/file-headers.js";let t={IS_DEV:{type:"bool",default:!1,devDefault:!0},ASSISTANT_STRICT_THREADS:{type:"bool",default:!1},AWS_S3_PUBLIC_RESOURCES_BUCKET:{type:"str",required:!0},AWS_S3_RESOURCES_REGION:{type:"str",required:!0},DEPLOYMENT_RESOURCES_PATH:{type:"str",default:"deployment-resources"}},o={CLERK_API_KEY:{type:"str",required:!0},FRONTEND_URL:{type:"str",required:!0},JWT_SECRET_BASE64:{type:"str",required:!0},OAUTH_PROVIDER_CLIENT_ID:{type:"str",required:!0},OAUTH_PROVIDER_CLIENT_SECRET:{type:"str",required:!0},OAUTH_PROVIDER_BASE_URL:{type:"str",required:!0},RESOURCES_URL:{type:"str",required:!0}};export const generateApplicationSpecWebhooksConfig=i=>{let n=i.infra.env?.webhook??{},R=i.appMetadata.useClerk?o:{},{imports:E,config:p}=e({...t,...R,...n}),_=E.length>0?`import { ${E.join(", ")} } from 'envalid'
2
- `:"";return`${r("webhooks.config.ts","Runtime environment config for the webhook server.")}
3
- ${_}import { createEnvReader } from '@salsita-ai/sdk/common/env'
1
+ import{generateEnvReader as e}from"../common/env-reader.js";import{autoHeader as r}from"../common/file-headers.js";let t={IS_DEV:{type:"bool",default:!1,devDefault:!0},ASSISTANT_STRICT_THREADS:{type:"bool",default:!1},AWS_S3_PUBLIC_RESOURCES_BUCKET:{type:"str",required:!0},AWS_S3_RESOURCES_REGION:{type:"str",required:!0},DEPLOYMENT_RESOURCES_PATH:{type:"str",default:"deployment-resources"}},o={CLERK_API_KEY:{type:"str",required:!0},FRONTEND_URL:{type:"str",required:!0},JWT_SECRET_BASE64:{type:"str",required:!0},OAUTH_PROVIDER_CLIENT_ID:{type:"str",required:!0},OAUTH_PROVIDER_CLIENT_SECRET:{type:"str",required:!0},OAUTH_PROVIDER_BASE_URL:{type:"str",required:!0},RESOURCES_URL:{type:"str",required:!0}};export const generateApplicationSpecWebhooksConfig=i=>{let n=i.infra.env?.webhook??{},R=i.appMetadata.useClerk?o:{},{imports:E,config:p}=e({...t,...R,...n}),_=[...E,"createEnvReader"].sort();return`${r("webhooks.config.ts","Runtime environment config for the webhook server.")}
2
+ import { ${_.join(", ")} } from '@salsita-ai/sdk/common/env'
4
3
 
5
4
  const readRuntimeConfig = createEnvReader(${p})
6
5
 
@@ -70,6 +70,16 @@ export type CategorizedFiles = Branded<'CategorizedFiles', {
70
70
  readonly mockTemplatesChanged: boolean;
71
71
  readonly projectFiles: readonly CategorizedFile[];
72
72
  readonly projectFilesChanged: boolean;
73
+ /**
74
+ * One-time migration: true only on a checkout's first sync with an SDK that contains this field;
75
+ * delete it once projects have synced with such an SDK.
76
+ *
77
+ * Whether the project's root `tsconfig.json` is the full file earlier SDK versions generated on
78
+ * every sync, before the TypeScript settings moved to `.phoenix/tsconfig.json`. Generation reads
79
+ * it to replace that file with the one-line `tsconfig.json` that extends `.phoenix/tsconfig.json`;
80
+ * any other root `tsconfig.json` is the project's own and is left as it is.
81
+ */
82
+ readonly hasEarlierGeneratedTsconfig: boolean;
73
83
  readonly customCode: readonly CategorizedFile[];
74
84
  readonly customCodeChanged: boolean;
75
85
  readonly envs: Record<string, unknown>;
@@ -1 +1 @@
1
- import e from"lodash-es/isEqual.js";import{Branded as n}from"@salsita-ai/common/utils";import{computeFileDepHashes as s}from"../../caching/index.js";import{DiagnosticCode as o,diagnosticMessages as a}from"../../errors/index.js";import{findWebhookHandlerRegistration as i}from"./module-analysis.js";import{findWebhookSafetyViolation as t}from"./webhook-safety.js";export const buildProjectFileIndex=e=>new Map([...e.spec,...e.expressions,...e.actions,...e.handlers,...e.elements,...e.transformers,...e.components,...e.invariants,...e.migrations,...e.adminMigrations,...e.adminExpressions,...e.adminInvariants,...e.adminComponents,...e.adminElements,...e.webhooks,...e.locales,...e.mockTemplates,...e.projectFiles,...e.customCode].map(e=>[e.file.name,e]));let r=e=>/\/_[^/]+\//.test(e),m=[{path:"spec/",key:"spec"},{path:"src/admin/expressions/",key:"adminExpressions"},{path:"src/admin/invariants/",key:"adminInvariants"},{path:"src/admin/components/",key:"adminComponents"},{path:"src/admin/elements/",key:"adminElements"},{path:"src/admin/migrations/",key:"adminMigrations"},{path:"src/expressions/",key:"expressions"},{path:"src/actions/",key:"actions"},{path:"src/handlers/",key:"handlers"},{path:"src/elements/",key:"elements"},{path:"src/transformers/",key:"transformers"},{path:"src/components/",key:"components"},{path:"src/invariants/",key:"invariants"},{path:"src/migrations/",key:"migrations"},{path:"src/webhooks/",key:"webhooks"},{path:"src/locales/",key:"locales"},{path:"mocks/",key:"mockTemplates"}],l=new Set(["package.json",".gitignore","CLAUDE.md",".cursor/rules/phoenix.mdc"]),d=new Set(["migrations","adminMigrations"]),c=m.filter(({path:e,key:n})=>e.startsWith("src/")&&d.has(n)),p=(e,n)=>{let s=e.slice(n.length).split("/");return!(s.length<=1)&&s.slice(0,-1).some(e=>!e.startsWith("_"))},h=/\.(ts|tsx|js|jsx)$/,f=(e,n)=>{if("components"===e||"adminComponents"===e)return n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (no 'use webhook' directive)"};if("handlers"===e){let e=i(n);return void 0!==e?{membership:"named-handler",logSuffix:`webhook (the ${e} registration)`}:n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (not a named handler or pin)"}}};export const CategorizedFiles=(i,d,g,u)=>{let k={spec:[],expressions:[],actions:[],handlers:[],elements:[],transformers:[],components:[],invariants:[],migrations:[],adminMigrations:[],adminExpressions:[],adminInvariants:[],adminComponents:[],adminElements:[],webhooks:[],locales:[],mockTemplates:[],projectFiles:[],customCode:[]},b=[];for(let e of i.files){if("text"!==e.kind)continue;if(r(e.name)){k.customCode.push({file:e,category:"customCode"});continue}if(l.has(e.name)){k.projectFiles.push({file:e,category:"projectFiles"});continue}let n=!1;for(let{path:s,key:o}of m)if(e.name.startsWith(s)){k[o].push({file:e,category:o}),n=!0;break}n||b.push(e.name)}for(let e of b)e.startsWith("src/")&&g.error(o.UNKNOWN_SRC_PATH,a.UNKNOWN_SRC_PATH(e));for(let{path:e,key:n}of c)for(let s of k[n])p(s.file.name,e)&&g.error(o.INVALID_SUBDIRECTORY,a.INVALID_SUBDIRECTORY(s.file.name,e));let C=k.webhooks.some(e=>"src/webhooks/index.ts"===e.file.name);if(k.webhooks.length>0&&!C&&g.error(o.MISSING_WEBHOOK_INDEX,a.MISSING_WEBHOOK_INDEX()),g.hasErrors)return;let v=[];for(let e of Object.values(k))for(let n of e)v.push(n.file.name);let x=s(i,v),{analyzer:y,logger:E}=u,I=d.createBuilder("categorizedFiles/moduleAnalysis",{build:e=>{try{return{analysis:y(e.name,e.content)}}catch(e){return{error:e instanceof Error?e.message:String(e)}}},getKey:e=>e.name,compare:(e,n)=>e.content===n.content}),w=new Set,S=e=>{if(!h.test(e.name))return;w.add(e.name);let n=I.build({name:e.name,content:e.content}).output;return void 0!==n.error?void g.error(o.MODULE_ANALYSIS_FAILED,a[o.MODULE_ANALYSIS_FAILED](e.name,n.error)):n.analysis},j={},_={};for(let[e,s]of Object.entries(k)){let o=d.createBuilder(`categorizedFiles/${e}`,{build:e=>e,getKey:e=>e.name,compare:(e,n)=>e.depHash===n.depHash}),a=new Set;j[e]=s.map(e=>{let s=x[e.file.name]??{deps:[],hash:""},{changed:i}=o.build({name:e.file.name,depHash:s.hash});a.add(e.file.name);let t=S(e.file),r=void 0===t?void 0:f(e.category,t);return void 0!==r&&E.debug(`Webhook membership: ${e.file.name} -> ${r.logSuffix}`),n({file:e.file,category:e.category,deps:s.deps,depHash:s.hash,changed:i,analysis:t,webhookMembership:r?.membership})}),_[e]=o.cleanup(a)}I.cleanup(w);let F=(e,n)=>n.some(e=>e.changed)||!0===_[e],M=d.createBuilder("categorizedFiles/envs",{build:e=>e.envs,getKey:()=>"singleton",compare:(n,s)=>e(n.envs,s.envs)}).build({envs:i.envs}),O=n({spec:j.spec,specChanged:F("spec",j.spec),expressions:j.expressions,expressionsChanged:F("expressions",j.expressions),actions:j.actions,actionsChanged:F("actions",j.actions),handlers:j.handlers,handlersChanged:F("handlers",j.handlers),elements:j.elements,elementsChanged:F("elements",j.elements),transformers:j.transformers,transformersChanged:F("transformers",j.transformers),components:j.components,componentsChanged:F("components",j.components),invariants:j.invariants,invariantsChanged:F("invariants",j.invariants),migrations:j.migrations,migrationsChanged:F("migrations",j.migrations),adminMigrations:j.adminMigrations,adminMigrationsChanged:F("adminMigrations",j.adminMigrations),adminExpressions:j.adminExpressions,adminExpressionsChanged:F("adminExpressions",j.adminExpressions),adminInvariants:j.adminInvariants,adminInvariantsChanged:F("adminInvariants",j.adminInvariants),adminComponents:j.adminComponents,adminComponentsChanged:F("adminComponents",j.adminComponents),adminElements:j.adminElements,adminElementsChanged:F("adminElements",j.adminElements),webhooks:j.webhooks,webhooksChanged:F("webhooks",j.webhooks),hasWebhookEntry:C,locales:j.locales,localesChanged:F("locales",j.locales),mockTemplates:j.mockTemplates,mockTemplatesChanged:F("mockTemplates",j.mockTemplates),projectFiles:j.projectFiles,projectFilesChanged:F("projectFiles",j.projectFiles),customCode:j.customCode,customCodeChanged:F("customCode",j.customCode),envs:M.output,envsChanged:M.changed}),N=buildProjectFileIndex(O);for(let e of N.values()){if("pinned"!==e.webhookMembership&&"named-handler"!==e.webhookMembership)continue;let n=t(e,N);void 0!==n&&g.error(o.WEBHOOK_UNSAFE_IMPORT,a[o.WEBHOOK_UNSAFE_IMPORT](n.fileName,n.inclusionReason,n.importer,n.specifier))}return O};
1
+ import e from"lodash-es/isEqual.js";import{Branded as n}from"@salsita-ai/common/utils";import{computeFileDepHashes as s}from"../../caching/index.js";import{DiagnosticCode as o,diagnosticMessages as i}from"../../errors/index.js";import{isEarlierGeneratedTsconfig as a}from"./earlier-generated-tsconfig.js";import{findWebhookHandlerRegistration as t}from"./module-analysis.js";import{findWebhookSafetyViolation as r}from"./webhook-safety.js";export const buildProjectFileIndex=e=>new Map([...e.spec,...e.expressions,...e.actions,...e.handlers,...e.elements,...e.transformers,...e.components,...e.invariants,...e.migrations,...e.adminMigrations,...e.adminExpressions,...e.adminInvariants,...e.adminComponents,...e.adminElements,...e.webhooks,...e.locales,...e.mockTemplates,...e.projectFiles,...e.customCode].map(e=>[e.file.name,e]));let m=e=>/\/_[^/]+\//.test(e),l=[{path:"spec/",key:"spec"},{path:"src/admin/expressions/",key:"adminExpressions"},{path:"src/admin/invariants/",key:"adminInvariants"},{path:"src/admin/components/",key:"adminComponents"},{path:"src/admin/elements/",key:"adminElements"},{path:"src/admin/migrations/",key:"adminMigrations"},{path:"src/expressions/",key:"expressions"},{path:"src/actions/",key:"actions"},{path:"src/handlers/",key:"handlers"},{path:"src/elements/",key:"elements"},{path:"src/transformers/",key:"transformers"},{path:"src/components/",key:"components"},{path:"src/invariants/",key:"invariants"},{path:"src/migrations/",key:"migrations"},{path:"src/webhooks/",key:"webhooks"},{path:"src/locales/",key:"locales"},{path:"mocks/",key:"mockTemplates"}],d="tsconfig.json",c=new Set(["package.json",".gitignore",d,"CLAUDE.md",".cursor/rules/phoenix.mdc"]),p=new Set(["migrations","adminMigrations"]),h=l.filter(({path:e,key:n})=>e.startsWith("src/")&&p.has(n)),f=(e,n)=>{let s=e.slice(n.length).split("/");return!(s.length<=1)&&s.slice(0,-1).some(e=>!e.startsWith("_"))},g=/\.(ts|tsx|js|jsx)$/,u=(e,n)=>{if("components"===e||"adminComponents"===e)return n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (no 'use webhook' directive)"};if("handlers"===e){let e=t(n);return void 0!==e?{membership:"named-handler",logSuffix:`webhook (the ${e} registration)`}:n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (not a named handler or pin)"}}};export const CategorizedFiles=(t,p,k,b)=>{let C={spec:[],expressions:[],actions:[],handlers:[],elements:[],transformers:[],components:[],invariants:[],migrations:[],adminMigrations:[],adminExpressions:[],adminInvariants:[],adminComponents:[],adminElements:[],webhooks:[],locales:[],mockTemplates:[],projectFiles:[],customCode:[]},v=[];for(let e of t.files){if("text"!==e.kind)continue;if(m(e.name)){C.customCode.push({file:e,category:"customCode"});continue}if(c.has(e.name)){C.projectFiles.push({file:e,category:"projectFiles"});continue}let n=!1;for(let{path:s,key:o}of l)if(e.name.startsWith(s)){C[o].push({file:e,category:o}),n=!0;break}n||v.push(e.name)}for(let e of v)e.startsWith("src/")&&k.error(o.UNKNOWN_SRC_PATH,i.UNKNOWN_SRC_PATH(e));for(let{path:e,key:n}of h)for(let s of C[n])f(s.file.name,e)&&k.error(o.INVALID_SUBDIRECTORY,i.INVALID_SUBDIRECTORY(s.file.name,e));let x=C.webhooks.some(e=>"src/webhooks/index.ts"===e.file.name);if(C.webhooks.length>0&&!x&&k.error(o.MISSING_WEBHOOK_INDEX,i.MISSING_WEBHOOK_INDEX()),k.hasErrors)return;let y=C.projectFiles.find(e=>e.file.name===d),E=void 0!==y&&a(y.file.content),I=[];for(let e of Object.values(C))for(let n of e)I.push(n.file.name);let w=s(t,I),{analyzer:S,logger:j}=b,F=p.createBuilder("categorizedFiles/moduleAnalysis",{build:e=>{try{return{analysis:S(e.name,e.content)}}catch(e){return{error:e instanceof Error?e.message:String(e)}}},getKey:e=>e.name,compare:(e,n)=>e.content===n.content}),_=new Set,M=e=>{if(!g.test(e.name))return;_.add(e.name);let n=F.build({name:e.name,content:e.content}).output;return void 0!==n.error?void k.error(o.MODULE_ANALYSIS_FAILED,i[o.MODULE_ANALYSIS_FAILED](e.name,n.error)):n.analysis},O={},N={};for(let[e,s]of Object.entries(C)){let o=p.createBuilder(`categorizedFiles/${e}`,{build:e=>e,getKey:e=>e.name,compare:(e,n)=>e.depHash===n.depHash}),i=new Set;O[e]=s.map(e=>{let s=w[e.file.name]??{deps:[],hash:""},{changed:a}=o.build({name:e.file.name,depHash:s.hash});i.add(e.file.name);let t=M(e.file),r=void 0===t?void 0:u(e.category,t);return void 0!==r&&j.debug(`Webhook membership: ${e.file.name} -> ${r.logSuffix}`),n({file:e.file,category:e.category,deps:s.deps,depHash:s.hash,changed:a,analysis:t,webhookMembership:r?.membership})}),N[e]=o.cleanup(i)}F.cleanup(_);let A=(e,n)=>n.some(e=>e.changed)||!0===N[e],T=p.createBuilder("categorizedFiles/envs",{build:e=>e.envs,getKey:()=>"singleton",compare:(n,s)=>e(n.envs,s.envs)}).build({envs:t.envs}),D=n({spec:O.spec,specChanged:A("spec",O.spec),expressions:O.expressions,expressionsChanged:A("expressions",O.expressions),actions:O.actions,actionsChanged:A("actions",O.actions),handlers:O.handlers,handlersChanged:A("handlers",O.handlers),elements:O.elements,elementsChanged:A("elements",O.elements),transformers:O.transformers,transformersChanged:A("transformers",O.transformers),components:O.components,componentsChanged:A("components",O.components),invariants:O.invariants,invariantsChanged:A("invariants",O.invariants),migrations:O.migrations,migrationsChanged:A("migrations",O.migrations),adminMigrations:O.adminMigrations,adminMigrationsChanged:A("adminMigrations",O.adminMigrations),adminExpressions:O.adminExpressions,adminExpressionsChanged:A("adminExpressions",O.adminExpressions),adminInvariants:O.adminInvariants,adminInvariantsChanged:A("adminInvariants",O.adminInvariants),adminComponents:O.adminComponents,adminComponentsChanged:A("adminComponents",O.adminComponents),adminElements:O.adminElements,adminElementsChanged:A("adminElements",O.adminElements),webhooks:O.webhooks,webhooksChanged:A("webhooks",O.webhooks),hasWebhookEntry:x,locales:O.locales,localesChanged:A("locales",O.locales),mockTemplates:O.mockTemplates,mockTemplatesChanged:A("mockTemplates",O.mockTemplates),projectFiles:O.projectFiles,projectFilesChanged:A("projectFiles",O.projectFiles),hasEarlierGeneratedTsconfig:E,customCode:O.customCode,customCodeChanged:A("customCode",O.customCode),envs:T.output,envsChanged:T.changed}),W=buildProjectFileIndex(D);for(let e of W.values()){if("pinned"!==e.webhookMembership&&"named-handler"!==e.webhookMembership)continue;let n=r(e,W);void 0!==n&&k.error(o.WEBHOOK_UNSAFE_IMPORT,i[o.WEBHOOK_UNSAFE_IMPORT](n.fileName,n.inclusionReason,n.importer,n.specifier))}return D};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * One-time migration, see the top of this module: true only before a checkout's first sync with this SDK.
3
+ *
4
+ * Whether the content of a project's root `tsconfig.json` is the file earlier SDK versions generated.
5
+ * The comparison is by meaning: comments, formatting and key order do not count, so a generated
6
+ * file that an editor or formatter rewrote is still recognised. Content that does not parse, or that
7
+ * differs in any setting, is the project's own.
8
+ */
9
+ export declare const isEarlierGeneratedTsconfig: (content: string) => boolean;
@@ -0,0 +1 @@
1
+ import e from"lodash-es/isEqual.js";let l={compilerOptions:{noEmit:!0,module:"ESNext",moduleResolution:"Bundler",lib:["ES2022","DOM"],esModuleInterop:!0,isolatedModules:!0,resolveJsonModule:!0,allowJs:!1,skipLibCheck:!0,strict:!0,noImplicitOverride:!0,noImplicitReturns:!0,noPropertyAccessFromIndexSignature:!0,noUncheckedIndexedAccess:!0,allowUnreachableCode:!1,allowUnusedLabels:!1,noFallthroughCasesInSwitch:!0,noUnusedLocals:!0,noUnusedParameters:!0,useDefineForClassFields:!0,jsx:"react-jsx",paths:{}},include:["src",".phoenix","prettier.config.js","eslint.config.js","./.phoenix/globals.d.ts"]},s=(e,l)=>{let s=l+1;for(;s<e.length&&'"'!==e[s];)s+="\\"===e[s]?2:1;return s+1};export const isEarlierGeneratedTsconfig=t=>e((e=>{try{return JSON.parse((e=>{let l="",t=0;for(;t<e.length;){let n=e[t];if('"'===n){let n=s(e,t);l+=e.slice(t,n),t=n;continue}","===n&&/^\s*[}\]]/.test(e.slice(t+1))||(l+=n),t++}return l})((e=>{let l="",t=0;for(;t<e.length;){let n=e[t],o=e[t+1];if('"'===n){let n=s(e,t);l+=e.slice(t,n),t=n}else if("/"===n&&"/"===o){let l=e.indexOf("\n",t);t=-1===l?e.length:l}else if("/"===n&&"*"===o){let l=e.indexOf("*/",t+2);t=-1===l?e.length:l+2}else l+=n,t++}return l})(e)))}catch{return}})(t),l);