@salsita-ai/sdk 0.50.0-develop.abef6d86a1 → 0.50.0-develop.ae52227c7e

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 (74) hide show
  1. package/README.md +2 -2
  2. package/dist/application-spec/errors/diagnostic-codes.d.ts +1 -0
  3. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  4. package/dist/application-spec/errors/diagnostic-messages.d.ts +1 -0
  5. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  6. package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +7 -0
  7. package/dist/application-spec/generate-application/code/project-scaffolding.js +1 -1
  8. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +25 -23
  9. package/dist/application-spec/generate-application/code/templates/domain.js +3 -3
  10. package/dist/application-spec/generate-application/code/templates/package-json.js +1 -1
  11. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +1 -0
  12. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +1 -1
  13. package/dist/application-spec/parse-application-files/spec/model/defaults.d.ts +5 -0
  14. package/dist/application-spec/parse-application-files/spec/model/defaults.js +1 -1
  15. package/dist/configurator/pages/DefaultConfiguratorLayout.d.ts +6 -0
  16. package/dist/configurator/pages/DefaultConfiguratorLayout.js +1 -0
  17. package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
  18. package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.js +4 -4
  19. package/dist/configurator/ui/configurator-layout/universal/DesktopPreview.js +1 -1
  20. package/dist/configurator/ui/configurator-layout/universal/MobileLayout.js +4 -4
  21. package/dist/configurator/ui/configurator-layout/universal/MobilePreview.js +1 -1
  22. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.d.ts +47 -0
  23. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -0
  24. package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.d.ts +11 -26
  25. package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.js +1 -1
  26. package/dist/docs/complex-use-cases/filtered-controls-with-fix-dialog.mdx +16 -19
  27. package/dist/docs/complex-use-cases/sketching.mdx +0 -38
  28. package/dist/docs/controls/select-attribute/cardselect.mdx +9 -13
  29. package/dist/docs/index.json +1 -1
  30. package/dist/docs/integrations/woocommerce/local-development.mdx +1 -1
  31. package/dist/docs/other-features/side-effects.mdx +8 -11
  32. package/dist/docs/other-features/violation-messages.mdx +52 -56
  33. package/dist/docs/phoenix/configurator-interface.mdx +100 -65
  34. package/dist/docs/phoenix/phoenix-cli.mdx +19 -26
  35. package/dist/docs/phoenix/project-structure.mdx +8 -6
  36. package/dist/docs/phoenix/quickstart.mdx +2 -2
  37. package/dist/docs/phoenix/sdk-builder-workflow.mdx +2 -2
  38. package/dist/docs/phoenix/webhooks.mdx +1 -1
  39. package/dist/domain/core/component/domain-component-optic.d.ts +10 -17
  40. package/dist/domain/core/component/domain-component-optic.js +2 -2
  41. package/dist/domain/core/component/legacy-filter.d.ts +2 -17
  42. package/dist/domain/core/component/system-component-filter.d.ts +21 -16
  43. package/dist/domain/core/invariant/custom-invariants.d.ts +6 -14
  44. package/dist/domain/core/invariant/invariants.d.ts +1 -4
  45. package/dist/domain/core/state/migrations.d.ts +14 -45
  46. package/dist/domain/core/state/migrations.js +1 -1
  47. package/dist/domain/domain-builder.js +1 -1
  48. package/dist/domain/domain-runtime/spec/create-violation-expressions.d.ts +24 -0
  49. package/dist/domain/domain-runtime/spec/create-violation-expressions.js +1 -0
  50. package/dist/domain/domain-runtime/spec/index.d.ts +1 -1
  51. package/dist/domain/domain-runtime/spec/index.js +1 -1
  52. package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +1 -1
  53. package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.d.ts +2 -2
  54. package/dist/legacy/domain-cli/gen/invariants.js +3 -4
  55. package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
  56. package/dist/legacy/domain-cli/utils/cache.js +1 -1
  57. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
  58. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
  59. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
  60. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +1 -0
  61. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/domain-index.js +17 -15
  62. package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
  63. package/dist/phoenix/commands/init.d.ts +4 -4
  64. package/dist/phoenix/commands/init.js +1 -1
  65. package/dist/phoenix/commands/prettier.js +1 -1
  66. package/dist/phoenix/utils/git/index.d.ts +1 -0
  67. package/dist/phoenix/utils/git/index.js +1 -0
  68. package/dist/phoenix/utils/git/is-inside-git-repository.d.ts +6 -0
  69. package/dist/phoenix/utils/git/is-inside-git-repository.js +1 -0
  70. package/dist/phoenix/utils/index.d.ts +1 -0
  71. package/dist/phoenix/utils/index.js +1 -1
  72. package/package.json +3 -3
  73. package/dist/domain/domain-runtime/spec/invariant-builder.d.ts +0 -26
  74. package/dist/domain/domain-runtime/spec/invariant-builder.js +0 -1
@@ -22,6 +22,12 @@ interface WithDomainEntityId {
22
22
  id: DomainEntityId<string>;
23
23
  }
24
24
  export type IdOrWithId = DomainEntityId<string> | WithDomainEntityId;
25
+ /**
26
+ * The criteria a component filter can state, each with the value it is compared with. This is the
27
+ * one place that lists them: the filter an optic carries (`GradualComponentSystemFilter`), the
28
+ * filter `withGradualFilter` takes and the legacy `DeclarativeComponentSystemFilter` take their
29
+ * keys and value types from it.
30
+ */
25
31
  export type ComponentSystemFilterProps = {
26
32
  navigationStates?: string[];
27
33
  selectedEntityIds?: readonly IdOrWithId[] | IdOrWithId;
@@ -38,22 +44,21 @@ export type ComponentSystemFilterProps = {
38
44
  debug?: boolean;
39
45
  viewports?: readonly Viewport[];
40
46
  };
41
- export interface GradualComponentSystemFilter<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, CONTEXT_EXTENSION = unknown> {
42
- navigationStates: undefined | readonly string[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly string[] | undefined, CONTEXT_EXTENSION>;
43
- selectedEntityIds: undefined | readonly IdOrWithId[] | IdOrWithId | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
44
- hoveredEntityIds: undefined | readonly IdOrWithId[] | IdOrWithId | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
45
- draggedEntityIds: undefined | readonly IdOrWithId[] | IdOrWithId | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
46
- exportedEntityIds: undefined | readonly IdOrWithId[] | IdOrWithId | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
47
- viewStates: undefined | readonly string[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly string[] | undefined, CONTEXT_EXTENSION>;
48
- toolStates: undefined | readonly string[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly string[] | undefined, CONTEXT_EXTENSION>;
49
- exportStates: undefined | readonly string[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly string[] | undefined, CONTEXT_EXTENSION>;
50
- modes: undefined | ReadonlyArray<'owner' | 'read' | 'finalize'> | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, ReadonlyArray<'owner' | 'read' | 'finalize'> | undefined, CONTEXT_EXTENSION>;
51
- roles: undefined | readonly SessionRole[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly SessionRole[] | undefined, CONTEXT_EXTENSION>;
52
- active: undefined | boolean | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, boolean | undefined, CONTEXT_EXTENSION>;
53
- fullscreen: undefined | boolean | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, boolean | undefined, CONTEXT_EXTENSION>;
54
- debug: undefined | boolean | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, boolean | undefined, CONTEXT_EXTENSION>;
55
- viewports: undefined | readonly Viewport[] | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, readonly Viewport[] | undefined, CONTEXT_EXTENSION>;
56
- }
47
+ /**
48
+ * The value an optic's filter holds for one criterion: the value `ComponentSystemFilterProps`
49
+ * states for it, with an array read-only.
50
+ */
51
+ type FilterCriterion<KEY extends keyof ComponentSystemFilterProps> = [
52
+ Required<ComponentSystemFilterProps>[KEY]
53
+ ] extends [ReadonlyArray<infer ITEM>] ? readonly ITEM[] : Required<ComponentSystemFilterProps>[KEY];
54
+ /**
55
+ * The filter a component optic carries: every criterion of `ComponentSystemFilterProps`, each
56
+ * unset, a value, or an expression that yields the value for one entity. The criteria are
57
+ * evaluated one by one, and the first one that does not match ends the evaluation.
58
+ */
59
+ export type GradualComponentSystemFilter<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, CONTEXT_EXTENSION = unknown> = {
60
+ [KEY in keyof Required<ComponentSystemFilterProps>]: undefined | FilterCriterion<KEY> | AnonymousExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, FilterCriterion<KEY> | undefined, CONTEXT_EXTENSION>;
61
+ };
57
62
  export interface FilterMismatchReason {
58
63
  readonly modes?: ReadonlyArray<'owner' | 'read' | 'finalize'>;
59
64
  readonly roles?: readonly SessionRole[];
@@ -1,21 +1,13 @@
1
- import type { OmitEmpty, OmitNever } from '@salsita-ai/common/utils';
2
1
  import type { CustomExpression } from '../expression/index.js';
3
- import type { $DomainEntity, DomainEntity, ExpandAlias, TypeOrAlias } from '../model/index.js';
4
- import type { Invariant } from './invariants.js';
2
+ import type { $DomainEntity, DomainEntity, TypeOrAlias } from '../model/index.js';
3
+ import type { AliasedInvariants } from './invariants.js';
5
4
  import type { Violation } from './violation.js';
6
5
  export type CustomInvariantsFromBlueprints<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, $MAP extends Record<ENTITIES['type'], $DomainEntity<ENTITIES>>, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, BLUEPRINTS extends Partial<Record<keyof $MAP & string, Record<string, boolean | string>>>, CONTEXT_EXTENSION = unknown> = {
7
6
  [TYPE_OR_ALIAS in keyof BLUEPRINTS]: {
8
7
  [NAME in keyof BLUEPRINTS[TYPE_OR_ALIAS]]: TYPE_OR_ALIAS extends keyof $MAP ? CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $MAP[TYPE_OR_ALIAS], BLUEPRINTS[TYPE_OR_ALIAS][NAME] extends string ? ReadonlyArray<Violation<BLUEPRINTS[TYPE_OR_ALIAS][NAME]>> : BLUEPRINTS[TYPE_OR_ALIAS][NAME], CONTEXT_EXTENSION> : never;
9
8
  };
10
9
  };
11
- export type StaticallyDefinedViolations<ENTITIES extends DomainEntity<string>, ALIASES extends Record<string, ENTITIES>, BLUEPRINTS extends Partial<Record<TypeOrAlias<ENTITIES, ALIASES>, Record<string, boolean | string>>>> = OmitEmpty<{
12
- [TYPE_OR_ALIAS in keyof BLUEPRINTS]: OmitNever<{
13
- [NAME in keyof BLUEPRINTS[TYPE_OR_ALIAS]]: BLUEPRINTS[TYPE_OR_ALIAS][NAME] extends boolean ? Violation<string> : never;
14
- }>;
15
- }>;
16
- export type InvariantsFromBlueprints<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ALIASES extends Record<string, ENTITIES>, BLUEPRINTS extends Partial<Record<TypeOrAlias<ENTITIES, ALIASES>, Record<string, boolean | string>>>, STATIC_VIOLATIONS extends StaticallyDefinedViolations<ENTITIES, ALIASES, BLUEPRINTS>, PICK extends keyof BLUEPRINTS = keyof BLUEPRINTS, CONTEXT_EXTENSION = unknown> = {
17
- readonly [TYPE_OR_ALIAS in keyof BLUEPRINTS & string & PICK]: ReadonlyArray<{
18
- [NAME in keyof BLUEPRINTS[TYPE_OR_ALIAS]]: Invariant<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ExpandAlias<ENTITIES, ALIASES, TYPE_OR_ALIAS>, BLUEPRINTS[TYPE_OR_ALIAS][NAME] extends boolean ? TYPE_OR_ALIAS extends keyof STATIC_VIOLATIONS ? NAME extends keyof STATIC_VIOLATIONS[TYPE_OR_ALIAS] ? STATIC_VIOLATIONS[TYPE_OR_ALIAS][NAME]['type'] : never : never : BLUEPRINTS[TYPE_OR_ALIAS][NAME] extends string ? BLUEPRINTS[TYPE_OR_ALIAS][NAME] : never, CONTEXT_EXTENSION>;
19
- }[keyof BLUEPRINTS[TYPE_OR_ALIAS]]>;
20
- };
21
- export declare const createCustomInvariantFactory: <ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, $MAP extends Record<TypeOrAlias<ENTITIES, ALIASES>, $DomainEntity<ENTITIES>>, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ALIASES extends Record<string, ENTITIES>, BLUEPRINTS extends Partial<Record<TypeOrAlias<ENTITIES, ALIASES>, Record<string, boolean | string>>>, CONTEXT_EXTENSION = unknown>() => <STATIC_VIOLATIONS extends StaticallyDefinedViolations<ENTITIES, ALIASES, BLUEPRINTS>>(staticViolations: STATIC_VIOLATIONS) => <TYPES_OR_ALIASES extends keyof BLUEPRINTS & string>(implementations: Pick<CustomInvariantsFromBlueprints<ENTITIES, ALLOWED_ROOT_ENTITIES, $MAP, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, BLUEPRINTS, CONTEXT_EXTENSION>, TYPES_OR_ALIASES>) => InvariantsFromBlueprints<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ALIASES, BLUEPRINTS, STATIC_VIOLATIONS, TYPES_OR_ALIASES, CONTEXT_EXTENSION>;
10
+ /** The violation a static invariant reports when it does not hold, by entity type or alias and by invariant name. */
11
+ type StaticViolations = Readonly<Record<string, Readonly<Record<string, Violation<string>>>>>;
12
+ export declare const createCustomInvariantFactory: <ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, $MAP extends Record<TypeOrAlias<ENTITIES, ALIASES>, $DomainEntity<ENTITIES>>, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ALIASES extends Record<string, ENTITIES>, BLUEPRINTS extends Partial<Record<TypeOrAlias<ENTITIES, ALIASES>, Record<string, boolean | string>>>, CONTEXT_EXTENSION = unknown>() => (staticViolations: StaticViolations) => <TYPES_OR_ALIASES extends keyof BLUEPRINTS & string>(implementations: Pick<CustomInvariantsFromBlueprints<ENTITIES, ALLOWED_ROOT_ENTITIES, $MAP, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, BLUEPRINTS, CONTEXT_EXTENSION>, TYPES_OR_ALIASES>) => AliasedInvariants<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ALIASES, string>;
13
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { OpenExpressionContext } from '../expression/index.js';
2
- import type { $DomainEntity, DomainEntity, ExpandAlias, TypeOrAlias } from '../model/index.js';
2
+ import type { DomainEntity, ExpandAlias, TypeOrAlias } from '../model/index.js';
3
3
  import type { DomainState } from '../state/index.js';
4
4
  import { type Violation } from './violation.js';
5
5
  export interface Invariant<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ENTITY extends ENTITIES, VIOLATION_TYPE extends string, CONTEXT_EXTENSION = unknown> {
@@ -8,6 +8,3 @@ export interface Invariant<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_E
8
8
  export type AliasedInvariants<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ALIASES extends Record<string, ENTITIES>, VIOLATION_TYPES extends string> = {
9
9
  [K in TypeOrAlias<ENTITIES, ALIASES>]?: ReadonlyArray<Invariant<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ExpandAlias<ENTITIES, ALIASES, K>, VIOLATION_TYPES>>;
10
10
  };
11
- export type DeclaredInvariants<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, $MAP extends Record<ENTITIES['type'], $DomainEntity<ENTITIES>>, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>> = {
12
- readonly [TYPE in ENTITIES['type']]: '$violations' extends keyof $MAP[TYPE] ? $MAP[TYPE]['$violations'] extends () => ReadonlyArray<Violation<infer VIOLATION_TYPE>> ? ReadonlyArray<Invariant<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITIES, VIOLATION_TYPE>> : never : never;
13
- };
@@ -48,51 +48,20 @@ export type MigrateFunction<LATEST_ENTITIES extends DomainEntity<string>> = (ver
48
48
  state: ReadonlyArray<DomainEntity<string>>;
49
49
  }, options: MigrateFunctionOptions) => readonly LATEST_ENTITIES[];
50
50
  /**
51
- * Builder for the state migration function.
51
+ * Creates the function that migrates a state of any registered version of the model to the latest
52
+ * one. The state is validated against the reflections of its own version, and again after every
53
+ * migration against the reflections of the version that migration yields.
54
+ *
55
+ * @param modelVersions the entity reflections of every version of the model, keyed by version
56
+ * @param latestVersion the version the state is migrated to
57
+ * @param migrations the migration to each version above the first: the one under key `n` takes a
58
+ * state of version `n - 1` to version `n`
52
59
  * @example
53
-
54
- export const migrateState = MigrateFunctionBuilder
55
-
56
- // the reflections are used for validation and sanitization
57
- .fromVersions({
58
- 1: v1Reflections,
59
- 2: v2Reflections,
60
- 3: v3Reflections,
61
- })
62
-
63
- // the latest version of the model
64
- .to(2)
65
-
66
- // the migrations between the versions
67
- .using({
68
- 2: migrationToV2,
69
- 3: migrationToV3,
70
- })
71
-
72
- .build()
73
-
60
+ * export const migrate = createMigrateFunction(
61
+ * { 1: v1Reflections, 2: v2Reflections, 3: v3Reflections },
62
+ * 3,
63
+ * { 2: migrationToV2, 3: migrationToV3 }
64
+ * )
74
65
  */
75
- export declare const MigrateFunctionBuilder: {
76
- /**
77
- * Provide the entity reflections for all versions of the model.
78
- */
79
- fromVersions: <VERSIONS extends Record<number, EntityReflections<any>>>(modelVersions: VERSIONS) => {
80
- /**
81
- * Specify the latest version of the model. I.e. the version to which the state will be migrated.
82
- */
83
- to: <LATEST extends keyof VERSIONS & number>(latestVersion: LATEST) => {
84
- /**
85
- * Provide the migrations between each consecutive version of the model.
86
- *
87
- * Migration with index `i` should migrate the state from version `i - 1` to version `i`.
88
- */
89
- using: (migrations: Record<Exclude<keyof VERSIONS, 1>, Migration<any, any>>) => {
90
- /**
91
- * Finally, build the migration function.
92
- */
93
- build: () => MigrateFunction<EntitiesOfReflections<VERSIONS[LATEST]>>;
94
- };
95
- };
96
- };
97
- };
66
+ export declare const createMigrateFunction: <VERSIONS extends Record<number, EntityReflections<any>>, LATEST extends keyof VERSIONS & number>(modelVersions: VERSIONS, latestVersion: LATEST, migrations: Record<Exclude<keyof VERSIONS, 1>, Migration<any, any>>) => MigrateFunction<EntitiesOfReflections<VERSIONS[LATEST]>>;
98
67
  export {};
@@ -1 +1 @@
1
- import e from"lodash-es/pick.js";export class StateMigrationError extends Error{constructor(e){super(e),this.name="StateMigrationError"}}export const isStateMigrationError=e=>e instanceof StateMigrationError;let t=(t,r,i,o)=>t.map(t=>{let n=r[t.type];if(!n)throw new StateMigrationError(`Unknown entity type "${t.type}". The state contains an entity whose type is not registered in the domain model for this version. Fix: register the entity type in the domain spec, or remove/migrate the stale entity from the preset or configuration data.`);if(n._meta.validator(t))return t;if(i){let r=e(t,Object.keys(n._meta.schema.properties));if(n._meta.validator(t))return o?.error(`Entity ${t.type}#${t.id} had extra fields that had to be trimmed: ${Object.keys(r)}`),r}throw new StateMigrationError(`Entity "${t.type}#${t.id}" does not match its schema in the current domain model. Validation errors: ${JSON.stringify(n._meta.validator.errors)}. Fix: update the entity data to match the schema, add a migration that transforms older data into the current shape, or re-register the schema if it changed.`)});export const MigrateFunctionBuilder={fromVersions:e=>({to:r=>({using:i=>({build:()=>(o,{logger:n,trimExtraProperties:a})=>{let s=e[o.version];if(!s)throw new StateMigrationError(`Unknown model version ${o.version}. The state declares a version that has no registered reflections. Fix: register the version in MigrateFunctionBuilder.fromVersions({ ... }), or correct the version stored on the state.`);let d=t(o.state,s,a,n);o.version===r?n?.debug(`State is already in the latest version ${r}`):n?.debug(`Migrating state from version ${o.version} to ${r}`);for(let s=o.version;s<r;s++){n?.debug(`Applying intermediate migration to version ${s+1}`);let h=e[s+1];if(!h)throw new StateMigrationError(`Unknown model version ${s+1} while migrating from ${o.version} to ${r}. An intermediate version has no registered reflections. Fix: register version ${s+1} in MigrateFunctionBuilder.fromVersions({ ... }).`);let g=i[s+1];if(!g)throw new StateMigrationError(`Missing migration from version ${s} to ${s+1}. No migration is registered for this step while migrating to ${r}. Fix: add the migration in MigrateFunctionBuilder.using({ ${s+1}: ... }).`);d=t(g(d),h,a,n)}if(!e[o.version])throw new StateMigrationError(`Unknown model version ${o.version}. The state declares a version that has no registered reflections. Fix: register the version in MigrateFunctionBuilder.fromVersions({ ... }), or correct the version stored on the state.`);return d}})})})};
1
+ import t from"lodash-es/pick.js";export class StateMigrationError extends Error{constructor(t){super(t),this.name="StateMigrationError"}}export const isStateMigrationError=t=>t instanceof StateMigrationError;let e=(e,r,i,o)=>e.map(e=>{let n=r[e.type];if(!n)throw new StateMigrationError(`Unknown entity type "${e.type}". The state contains an entity whose type is not registered in the domain model for this version. Fix: register the entity type in the domain spec, or remove/migrate the stale entity from the preset or configuration data.`);if(n._meta.validator(e))return e;if(i){let r=t(e,Object.keys(n._meta.schema.properties));if(n._meta.validator(e))return o?.error(`Entity ${e.type}#${e.id} had extra fields that had to be trimmed: ${Object.keys(r)}`),r}throw new StateMigrationError(`Entity "${e.type}#${e.id}" does not match its schema in the current domain model. Validation errors: ${JSON.stringify(n._meta.validator.errors)}. Fix: update the entity data to match the schema, add a migration that transforms older data into the current shape, or re-register the schema if it changed.`)});export const createMigrateFunction=(t,r,i)=>(o,{logger:n,trimExtraProperties:a})=>{let s=t[o.version];if(!s)throw new StateMigrationError(`Unknown model version ${o.version}. The state declares a version that has no registered reflections. Fix: register the version in the model versions passed to createMigrateFunction, or correct the version stored on the state.`);let d=e(o.state,s,a,n);o.version===r?n?.debug(`State is already in the latest version ${r}`):n?.debug(`Migrating state from version ${o.version} to ${r}`);for(let s=o.version;s<r;s++){n?.debug(`Applying intermediate migration to version ${s+1}`);let h=t[s+1];if(!h)throw new StateMigrationError(`Unknown model version ${s+1} while migrating from ${o.version} to ${r}. An intermediate version has no registered reflections. Fix: register version ${s+1} in the model versions passed to createMigrateFunction.`);let g=i[s+1];if(!g)throw new StateMigrationError(`Missing migration from version ${s} to ${s+1}. No migration is registered for this step while migrating to ${r}. Fix: add the migration under key ${s+1} in the migrations passed to createMigrateFunction.`);d=e(g(d),h,a,n)}return d};
@@ -1 +1 @@
1
- import{DomainFactory as t,MigrateFunctionBuilder as i,expandAlias as e}from"./core/index.js";import{resolveActionModules as s,resolveExpressionModules as n}from"./domain-runtime/index.js";import{withProfilerLabel as a}from"./utils/profiler-label.js";export class DomainBuilder{entities;aliasMap;$entityReflections;allowedRootEntityTypes;implemented;modelVersion;publishedData;sessionData;frontendData;backendData;migrate;isAdminDomain;actions;actionImplementations;constructor(t,i,e,s,n,a,o,h,l,r,m,d,p,c){this.entities=t,this.aliasMap=i,this.$entityReflections=e,this.allowedRootEntityTypes=s,this.implemented=n,this.modelVersion=a,this.publishedData=o,this.sessionData=h,this.frontendData=l,this.backendData=r,this.migrate=m,this.isAdminDomain=d,this.actions=p,this.actionImplementations=c}static from(t,e,s,n,a,o,h,l,r,m){let d=i.fromVersions({1:t}).to(1).using({}).build();return new DomainBuilder(t,e,s,n,{},a,o,h,l,r,d,m,{},{})}withMigrate(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,t,this.isAdminDomain,this.actions,this.actionImplementations)}withExpressions(t){let i={...this.implemented};return Object.entries(t).forEach(([t,s])=>{e(this.aliasMap,t).forEach(e=>{let n={...i[e]};i[e]=n,Object.entries(s).forEach(([i,s])=>{let o=n[i];if(o)throw Error(`Duplicate expression ${e}.${i}: The expression is defined on both ${t} and ${o.origin.typeOrAlias}, which would lead to ambiguous usage. Please ensure that expressions are only defined once.`);n[i]={...s,evaluate:a(`${e}_${i}`,s.evaluate),origin:{name:i,typeOrAlias:t}}})})}),new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,i,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,this.actions,this.actionImplementations)}withExpressionModules(t){return n(t,this.aliasMap).reduce((t,i)=>t.withExpressions(i.expressions),this)}withActions(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,Object.fromEntries(t.map(t=>[t.name,t])),this.actionImplementations)}withActionModules(t){return this.withActionImplementations(s(t))}withActionImplementations(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,this.actions,{...this.actionImplementations,...t})}build=()=>{let e=this.implemented,s=this.migrate??i.fromVersions({1:this.entities}).to(1).using({}).build();return new t({aliasMap:this.aliasMap,entityReflections:this.entities,$entityReflections:this.$entityReflections,expressions:e,allowedRootEntityTypes:this.allowedRootEntityTypes,modelVersion:this.modelVersion,migrate:s,publishedData:this.publishedData,sessionData:this.sessionData,frontendData:this.frontendData,backendData:this.backendData,isAdminDomain:this.isAdminDomain,actions:this.actions,actionImplementations:this.actionImplementations})}}
1
+ import{DomainFactory as t,createMigrateFunction as i,expandAlias as e}from"./core/index.js";import{resolveActionModules as s,resolveExpressionModules as n}from"./domain-runtime/index.js";import{withProfilerLabel as a}from"./utils/profiler-label.js";export class DomainBuilder{entities;aliasMap;$entityReflections;allowedRootEntityTypes;implemented;modelVersion;publishedData;sessionData;frontendData;backendData;migrate;isAdminDomain;actions;actionImplementations;constructor(t,i,e,s,n,a,o,h,l,m,r,d,p,c){this.entities=t,this.aliasMap=i,this.$entityReflections=e,this.allowedRootEntityTypes=s,this.implemented=n,this.modelVersion=a,this.publishedData=o,this.sessionData=h,this.frontendData=l,this.backendData=m,this.migrate=r,this.isAdminDomain=d,this.actions=p,this.actionImplementations=c}static from(t,e,s,n,a,o,h,l,m,r){let d=i({1:t},1,{});return new DomainBuilder(t,e,s,n,{},a,o,h,l,m,d,r,{},{})}withMigrate(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,t,this.isAdminDomain,this.actions,this.actionImplementations)}withExpressions(t){let i={...this.implemented};return Object.entries(t).forEach(([t,s])=>{e(this.aliasMap,t).forEach(e=>{let n={...i[e]};i[e]=n,Object.entries(s).forEach(([i,s])=>{let o=n[i];if(o)throw Error(`Duplicate expression ${e}.${i}: The expression is defined on both ${t} and ${o.origin.typeOrAlias}, which would lead to ambiguous usage. Please ensure that expressions are only defined once.`);n[i]={...s,evaluate:a(`${e}_${i}`,s.evaluate),origin:{name:i,typeOrAlias:t}}})})}),new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,i,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,this.actions,this.actionImplementations)}withExpressionModules(t){return n(t,this.aliasMap).reduce((t,i)=>t.withExpressions(i.expressions),this)}withActions(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,Object.fromEntries(t.map(t=>[t.name,t])),this.actionImplementations)}withActionModules(t){return this.withActionImplementations(s(t))}withActionImplementations(t){return new DomainBuilder(this.entities,this.aliasMap,this.$entityReflections,this.allowedRootEntityTypes,this.implemented,this.modelVersion,this.publishedData,this.sessionData,this.frontendData,this.backendData,this.migrate,this.isAdminDomain,this.actions,{...this.actionImplementations,...t})}build=()=>{let e=this.implemented,s=this.migrate??i({1:this.entities},1,{});return new t({aliasMap:this.aliasMap,entityReflections:this.entities,$entityReflections:this.$entityReflections,expressions:e,allowedRootEntityTypes:this.allowedRootEntityTypes,modelVersion:this.modelVersion,migrate:s,publishedData:this.publishedData,sessionData:this.sessionData,frontendData:this.frontendData,backendData:this.backendData,isAdminDomain:this.isAdminDomain,actions:this.actions,actionImplementations:this.actionImplementations})}}
@@ -0,0 +1,24 @@
1
+ import { type AnonymousExpression, type AnyAliasMap, type Violation } from '../../core/index.js';
2
+ import { type AnyInvariant, type ProjectModule } from './module-checks/index.js';
3
+ /**
4
+ * The `$violations` expression of every entity type that has invariants, keyed by entity type.
5
+ *
6
+ * The entity and data types are erased: the expression only passes its arguments on to the invariants,
7
+ * and `DomainBuilder.withExpressions` accepts it for any project.
8
+ */
9
+ type ViolationExpressions = Readonly<Record<string, {
10
+ readonly $violations: AnonymousExpression<any, any, any, any, any, any, ReadonlyArray<Violation<string>>>;
11
+ }>>;
12
+ /**
13
+ * Creates the `$violations` expressions of a domain from its invariants.
14
+ *
15
+ * Takes the invariants of the `src/invariants/` files, as the generated barrel lists them, after
16
+ * checking each file against the folder's contract. An invariant declared for an alias applies to
17
+ * every entity type of the alias. The `$violations` expression of an entity type returns the
18
+ * violations of all its invariants: those of `commonInvariants` first, then those of the files in
19
+ * the order of the barrel.
20
+ *
21
+ * @param commonInvariants Invariants the SDK adds to the domain, such as the ones every admin domain has.
22
+ */
23
+ export declare const createViolationExpressions: (aliasMap: AnyAliasMap, modules: readonly ProjectModule[], commonInvariants?: Readonly<Record<string, readonly AnyInvariant[]>>) => ViolationExpressions;
24
+ export {};
@@ -0,0 +1 @@
1
+ import{expandAlias as e}from"../../core/index.js";import{resolveInvariantModules as t}from"./module-checks/index.js";export const createViolationExpressions=(o,r,l={})=>{let f={};for(let i of[l,t(r)])for(let[t,r]of Object.entries(i))for(let l of e(o,t))f[l]=[...f[l]??[],...r];let i={};for(let[e,t]of Object.entries(f))i[e]={$violations:{evaluate:(e,o,r)=>t.flatMap(t=>t.evaluate(e,o,r)),meta:{}}};return i};
@@ -1,8 +1,8 @@
1
1
  export * from './selectors.js';
2
2
  export type * from './types.js';
3
3
  export * from './actions.js';
4
+ export * from './create-violation-expressions.js';
4
5
  export * from './initialize.js';
5
- export * from './invariant-builder.js';
6
6
  export * from './memoize-expression.js';
7
7
  export { type PhoenixEntityElementProps, type PhoenixEntityElementRegistration, type PhoenixLayoutElementRegistration, type PhoenixIconRegistration, type ElementRegistration, type CustomPhoenixEntityElementProps, type PhoenixLayoutElementDefinition, type PhoenixEntityElementDefinition, type CreateCustomPhoenixLayoutElement, type CreateCustomPhoenixEntityElement, createPhoenixLayoutElement, createPhoenixEntityElement, createCustomPhoenixLayoutElement, createCustomPhoenixEntityElement, resolveElementModule, resolveElementModules, type PhoenixHandlerDefinition, type SystemPhoenixHandlerDefinition, type PhoenixHandlerRegistration, type SystemPhoenixHandlerRegistration, type SystemPhoenixAsyncHandlerDefinition, type SystemPhoenixAsyncHandlerRegistration, type HandlerRegistration, type CreatePhoenixHandler, createPhoenixHandler, createSystemPhoenixHandler, createSystemPhoenixAsyncHandler, resolveHandlerModule, resolveHandlerModules, type ProjectModule, resolveActionModules, resolveComponentOpticModules, type ExpressionModule, resolveExpressionModules, resolveImportedTransformers, resolveTransformerModule, resolveTransformerModules, resolveInvariantModules, } from './module-checks/index.js';
8
8
  export * from './prepare-action-implementations.js';
@@ -1 +1 @@
1
- export*from"./selectors.js";export*from"./actions.js";export*from"./initialize.js";export*from"./invariant-builder.js";export*from"./memoize-expression.js";export{createPhoenixLayoutElement,createPhoenixEntityElement,createCustomPhoenixLayoutElement,createCustomPhoenixEntityElement,resolveElementModule,resolveElementModules,createPhoenixHandler,createSystemPhoenixHandler,createSystemPhoenixAsyncHandler,resolveHandlerModule,resolveHandlerModules,resolveActionModules,resolveComponentOpticModules,resolveExpressionModules,resolveImportedTransformers,resolveTransformerModule,resolveTransformerModules,resolveInvariantModules}from"./module-checks/index.js";export*from"./prepare-action-implementations.js";export*from"./prepare-domain-expressions.js";
1
+ export*from"./selectors.js";export*from"./actions.js";export*from"./create-violation-expressions.js";export*from"./initialize.js";export*from"./memoize-expression.js";export{createPhoenixLayoutElement,createPhoenixEntityElement,createCustomPhoenixLayoutElement,createCustomPhoenixEntityElement,resolveElementModule,resolveElementModules,createPhoenixHandler,createSystemPhoenixHandler,createSystemPhoenixAsyncHandler,resolveHandlerModule,resolveHandlerModules,resolveActionModules,resolveComponentOpticModules,resolveExpressionModules,resolveImportedTransformers,resolveTransformerModule,resolveTransformerModules,resolveInvariantModules}from"./module-checks/index.js";export*from"./prepare-action-implementations.js";export*from"./prepare-domain-expressions.js";
@@ -5,4 +5,4 @@ export { resolveActionModules } from './resolve-action-modules.js';
5
5
  export { resolveComponentOpticModules } from './resolve-component-optic-modules.js';
6
6
  export { type ExpressionModule, resolveExpressionModules } from './resolve-expression-modules.js';
7
7
  export { resolveImportedTransformers, resolveTransformerModule, resolveTransformerModules, } from './resolve-imported-transformers.js';
8
- export { resolveInvariantModules } from './resolve-invariant-modules.js';
8
+ export { type AnyInvariant, resolveInvariantModules } from './resolve-invariant-modules.js';
@@ -1,6 +1,6 @@
1
1
  import type { Invariant } from '../../../core/index.js';
2
2
  import { type ProjectModule } from './project-modules.js';
3
- type AnyInvariant = Invariant<any, any, any, any, any, any, string>;
3
+ /** An invariant of any project: a file's default export does not carry the project's entity and data types. */
4
+ export type AnyInvariant = Invariant<any, any, any, any, any, any, string>;
4
5
  /** Validates the `src/invariants/` files and concatenates their invariants per entity type or alias. */
5
6
  export declare const resolveInvariantModules: (modules: readonly ProjectModule[]) => Readonly<Record<string, readonly AnyInvariant[]>>;
6
- export {};
@@ -1,4 +1,4 @@
1
- import{DOMAIN_SDK_MODULE as t}from"./imports.js";import{stringifyWithCodeRefsAndI18n as n}from"./stringify-with-code-refs-and-i18n.js";export const generateInvariants=(i,a,e,s)=>{let o=[];for(let[t,n]of(o.push("interface CustomInvariantBlueprints {"),Object.entries(a))){for(let i of(o.push(` ${t}: {`),n))o.push(` ${i.name}: ${i.valueType}`);o.push(" }")}for(let[t,s]of(o.push("}"),o.push("const staticViolations = {"),Object.entries(a))){if(!s.some(t=>t.isStatic))continue;let a=i?.[t];if(a){for(let i of(o.push(` ${t}: {`),s))if(i.isStatic){let t=a[i.name];t&&o.push(` ${i.name}: ${n(t,e)},`)}o.push(" },")}}return o.push("} as const"),s?o.push(`export const implementCustomAdminInvariants = createCustomInvariantFactory<
1
+ import{DOMAIN_SDK_MODULE as t}from"./imports.js";import{stringifyWithCodeRefsAndI18n as n}from"./stringify-with-code-refs-and-i18n.js";export const generateInvariants=(i,o,e,s)=>{let a=[];for(let[t,n]of(a.push("interface CustomInvariantBlueprints {"),Object.entries(o))){for(let i of(a.push(` ${t}: {`),n))a.push(` ${i.name}: ${i.valueType}`);a.push(" }")}for(let[t,s]of(a.push("}"),a.push("const staticViolations = {"),Object.entries(o))){if(!s.some(t=>t.isStatic))continue;let o=i?.[t];if(o){for(let i of(a.push(` ${t}: {`),s))if(i.isStatic){let t=o[i.name];t&&a.push(` ${i.name}: ${n(t,e)},`)}a.push(" },")}}return a.push("} as const"),s?a.push(`export const implementCustomAdminInvariants = createCustomInvariantFactory<
2
2
  AnyDomainEntity,
3
3
  RootEntity,
4
4
  $Map,
@@ -9,7 +9,7 @@ import{DOMAIN_SDK_MODULE as t}from"./imports.js";import{stringifyWithCodeRefsAnd
9
9
  CustomInvariantBlueprints,
10
10
  RuntimeExpressionContext<AnyDomainEntity>
11
11
  >()(staticViolations)
12
- `):o.push(`export const implementCustomInvariants = createCustomInvariantFactory<
12
+ `):a.push(`export const implementCustomInvariants = createCustomInvariantFactory<
13
13
  AnyDomainEntity,
14
14
  RootEntity,
15
15
  $Map,
@@ -20,5 +20,4 @@ import{DOMAIN_SDK_MODULE as t}from"./imports.js";import{stringifyWithCodeRefsAnd
20
20
  CustomInvariantBlueprints,
21
21
  RuntimeExpressionContext<AnyDomainEntity>
22
22
  >()(staticViolations)
23
- `),e.importMembers(t,"createCustomInvariantFactory","RuntimeExpressionContext"),e.importMembers(s?"./admin-model/index.js":"./model/index.js","AnyDomainEntity","Aliases","RootEntity"),e.importMembers(s?"./admin-enhanced-model.js":"./enhanced-model.js","$Map"),s?(e.importMembers(t,"commonAdminViolationExpressions"),o.push(`export const adminInvariantBuilder = new InvariantBuilder<AnyDomainEntity, RootEntity, $Map, PublishedData, SessionData, FrontendData, Aliases>(aliases)
24
- .withInvariants(implementCustomAdminInvariants(commonAdminViolationExpressions))`)):o.push("export const invariantBuilder = new InvariantBuilder<AnyDomainEntity, RootEntity, $Map, PublishedData, SessionData, FrontendData, Aliases>(aliases)"),e.importMembers(t,"InvariantBuilder"),o.join("\n")};
23
+ `),e.importMembers(t,"createCustomInvariantFactory","RuntimeExpressionContext"),e.importMembers(s?"./admin-model/index.js":"./model/index.js","AnyDomainEntity","Aliases","RootEntity"),e.importMembers(s?"./admin-enhanced-model.js":"./enhanced-model.js","$Map"),s&&(e.importMembers(t,"commonAdminViolationExpressions"),a.push("export const commonAdminInvariants = implementCustomAdminInvariants(commonAdminViolationExpressions)")),a.join("\n")};
@@ -1,3 +1,3 @@
1
- export declare const cacheVersion = 760;
1
+ export declare const cacheVersion = 762;
2
2
  export declare const needsRebuild: (fileContents: string[], outputDirectory: string) => Promise<boolean>;
3
3
  export declare const writeCacheInfo: (fileContents: string[], outputDirectory: string) => Promise<void>;
@@ -1 +1 @@
1
- import{createHash as e}from"crypto";import{existsSync as r}from"fs";import{readFile as t}from"fs/promises";import{isEqualArray as o}from"@salsita-ai/common/utils";import{writeFormatted as i}from"../gen/write-formatted.js";export const cacheVersion=760;let s=r=>({version:760,files:r.map(r=>e("md5").update(r).digest("base64")).sort()});export const needsRebuild=async(e,i)=>{let a=`${i}/cache.json`;if(!r(a))return!0;try{let r=JSON.parse(await t(a,"utf-8")),i=s(e);return 760!==r.version||!o(r.files,i.files)}catch(e){return console.error("Failed to read cache file, forcing rebuild",e),!0}};export const writeCacheInfo=async(e,r)=>{let t=`${r}/cache.json`,o=s(e);await i(t,JSON.stringify(o,null,2))};
1
+ import{createHash as e}from"crypto";import{existsSync as r}from"fs";import{readFile as t}from"fs/promises";import{isEqualArray as o}from"@salsita-ai/common/utils";import{writeFormatted as i}from"../gen/write-formatted.js";export const cacheVersion=762;let s=r=>({version:762,files:r.map(r=>e("md5").update(r).digest("base64")).sort()});export const needsRebuild=async(e,i)=>{let a=`${i}/cache.json`;if(!r(a))return!0;try{let r=JSON.parse(await t(a,"utf-8")),i=s(e);return 762!==r.version||!o(r.files,i.files)}catch(e){return console.error("Failed to read cache file, forcing rebuild",e),!0}};export const writeCacheInfo=async(e,r)=>{let t=`${r}/cache.json`,o=s(e);await i(t,JSON.stringify(o,null,2))};
@@ -1,2 +1,2 @@
1
1
  export declare const mergeClaudeMdContent: (existingContent: string | undefined) => string;
2
- export declare const claudeMdFile = "[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]\n";
2
+ export declare const claudeMdFile = "[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n - A layout element named `ConfiguratorLayout` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in it, or the app loader stays on screen, and call `useSceneSafeArea()` (same import) with its `containerRef` on the element covering the whole canvas and its `safeAreaRef` on the region the layout's UI leaves free, so the camera frames the scene there\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]\n";
@@ -1,2 +1,2 @@
1
1
  export declare const mergeCursorRulesContent: (existingContent: string | undefined) => string;
2
- export declare const cursorRulesFile = "---\ndescription: Phoenix SDK project rules\nglobs:\nalwaysApply: true\n---\n\n[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]\n";
2
+ export declare const cursorRulesFile = "---\ndescription: Phoenix SDK project rules\nglobs:\nalwaysApply: true\n---\n\n[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n - A layout element named `ConfiguratorLayout` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in it, or the app loader stays on screen, and call `useSceneSafeArea()` (same import) with its `containerRef` on the element covering the whole canvas and its `safeAreaRef` on the region the layout's UI leaves free, so the camera frames the scene there\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]\n";
@@ -1 +1 @@
1
- export declare const sdkContent = "[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]";
1
+ export declare const sdkContent = "[PHOENIX-SDK-START]\n## Phoenix Configurator Project\n\nDeclarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.\n\n## Build Commands\n- `pnpm phoenix build` \u2014 Full production build\n- `pnpm phoenix dev` \u2014 Development mode with hot reload\n- `pnpm phoenix sync` \u2014 Sync generated files without full build\n\n## 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)\n\n## Project Structure\n- `spec/` \u2014 Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` \u2014 Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/invariants/` \u2014 Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` \u2014 Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` \u2014 ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` \u2014 Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n - A layout element named `ConfiguratorLayout` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount `LayoutSlots.SharedCanvas` (from `@salsita-ai/sdk/configurator`) in it, or the app loader stays on screen, and call `useSceneSafeArea()` (same import) with its `containerRef` on the element covering the whole canvas and its `safeAreaRef` on the region the layout's UI leaves free, so the camera frames the scene there\n- `src/handlers/` \u2014 Event & lifecycle handlers\n- `src/webhooks/` \u2014 Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` \u2014 Schema version migrations (v*.ts)\n- `src/locales/` \u2014 Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` \u2014 Admin interface (mirrors main src/ structure)\n- `src/_shared/` \u2014 Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` \u2014 Auto-generated files (DO NOT EDIT)\n- `public/` \u2014 Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` \u2014 Uploadable resources (PDF templates, email assets)\n\n## 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\n\n## 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\n\n## File Conventions\n- Components: PascalCase (.tsx)\n- Utilities: kebab-case (.ts)\n- Expressions: entity-name.ts\n- Migrations: v*.ts (sequential)\n- Specs: kebab-case.yaml\n- Underscore folders (_shared/, _utils/) are NOT auto-imported\n\n## Export Rules\nAll 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.\n\n## 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.\n\n## Do Not Edit\n- `.phoenix/` directory \u2014 completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files \u2014 compiled from .po files\n\n## Code Review\nUse `/review` command to review code changes before committing.\n\n## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` \u2014 Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` \u2014 Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` \u2014 Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` \u2014 Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` \u2014 List docs in a category\n- `pnpm phoenix docs -l` \u2014 List all doc paths with titles\n\n### 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`\n\n### 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` \u2014 Output file path (default: `./screenshot.png`)\n- `--width, -w` \u2014 Width in pixels (default: 1920)\n- `--height` \u2014 Height in pixels (default: 1080)\n- `--format, -f` \u2014 Image format: PNG or JPEG (default: PNG)\n- `--port` \u2014 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\n\n### 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` \u2014 Output file path (prints to stdout if omitted)\n- `--sections, -s` \u2014 Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 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` \u2014 domain data loaded\n- `domainContextReady` \u2014 domain context initialized (entities available)\n- `configuratorReady` \u2014 3D configurator initialized\n- `sessionAuthenticated` \u2014 session service active\n- `editorMode` \u2014 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\n\n### Inspect: Data\n\nGet the current editor context data as JSON via `pnpm phoenix inspect:data`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties\n\n**Example:** `pnpm phoenix inspect:data -o ./data.json`\n\n**Tips:** Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:data | head -30`).\n\n**Use cases:** AI agent context, debugging published/session/access data\n\n### Inspect: Scene\n\nGet the current Three.js scene graph as JSON via `pnpm phoenix inspect:scene`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` \u2014 Output file path (prints to stdout if omitted)\n- `--pretty` \u2014 Pretty-print JSON (default: true)\n- `--port` \u2014 WebSocket server port (default: 3020)\n\n**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.\n\n**Example:** `pnpm phoenix inspect:scene -o ./scene.json`\n\n**Tips:** Scene graphs can be large. Pipe through `head`/`tail` for partial output.\n\n**Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` \u2014 Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` \u2014 End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` \u2014 Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` \u2014 ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` \u2014 Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` \u2014 YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` \u2014 Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` \u2014 Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` \u2014 Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` \u2014 Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` \u2014 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\"`\n\n### Playwright MCP Setup (for AI agents)\n\nTo interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to `~/.claude/.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"playwright\": {\n \"command\": \"npx\",\n \"args\": [\n \"@playwright/mcp@latest\",\n \"--headed\",\n \"--launch-options\",\n \"{\\\"args\\\":[\\\"--use-gl=swiftshader\\\",\\\"--enable-webgl\\\",\\\"--ignore-gpu-blocklist\\\"]}\"\n ]\n }\n }\n}\n```\n\nKey flags:\n- `--headed` \u2014 visible browser (required for GPU rendering)\n- `--use-gl=swiftshader` \u2014 software GPU fallback for environments without a physical GPU\n- `--enable-webgl` + `--ignore-gpu-blocklist` \u2014 ensure WebGL is available\n[PHOENIX-SDK-END]";
@@ -21,6 +21,7 @@ Declarative 3D product configurator built with Phoenix SDK. Domain model defined
21
21
  - \`src/transformers/\` — Automatic state fixers (\`phx.frontend.transformerBuilder\`)
22
22
  - \`src/components/\` — ECS system components (\`phx.domain.attachComponent.withEntity()\`)
23
23
  - \`src/elements/\` — Page-level React components (\`phx.frontend.createCustomPhoenixLayoutElement()\`)
24
+ - A layout element named \`ConfiguratorLayout\` replaces everything the built-in layout renders for a loaded configuration, the finalize page included, so prefer arranging the layout in the spec; when you register it, mount \`LayoutSlots.SharedCanvas\` (from \`@salsita-ai/sdk/configurator\`) in it, or the app loader stays on screen, and call \`useSceneSafeArea()\` (same import) with its \`containerRef\` on the element covering the whole canvas and its \`safeAreaRef\` on the region the layout's UI leaves free, so the camera frames the scene there
24
25
  - \`src/handlers/\` — Event & lifecycle handlers
25
26
  - \`src/webhooks/\` — Server-side webhook handlers (\`phx.webhooks.*Builder\`)
26
27
  - \`src/migrations/\` — Schema version migrations (v*.ts)