@salsita-ai/sdk 0.49.0-develop.fc391028c9 → 0.50.0-develop.da56a0c6c3

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 (32) hide show
  1. package/CHANGELOG.md +479 -0
  2. package/dist/application-spec/generate-application/code/templates/index.d.ts +6 -6
  3. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  4. package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +2 -0
  5. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -0
  6. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.d.ts → skills/phoenix-create-configurator-skill.d.ts} +1 -1
  7. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.js → skills/phoenix-create-configurator-skill.js} +1 -1
  8. package/dist/application-spec/generate-application/code/templates/{phoenix-feedback-skill.js → skills/phoenix-feedback-skill.js} +1 -1
  9. package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.js → skills/phoenix-review-skill.js} +2 -2
  10. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -0
  11. package/dist/application-spec/generate-application/code/templates/{common → skills}/sdk-content.d.ts +21 -0
  12. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +67 -0
  13. package/dist/client/phoenix-api.d.ts +5 -1
  14. package/dist/docs/index.json +1 -1
  15. package/dist/docs/phoenix/migrations.mdx +45 -5
  16. package/dist/docs/phoenix/phoenix-cli.mdx +1 -1
  17. package/dist/docs/phoenix/sdk-builder-workflow.mdx +4 -2
  18. package/dist/docs/phoenix/state-management.mdx +1 -1
  19. package/dist/phoenix/cli.js +1 -1
  20. package/dist/phoenix/commands/deploy.js +2 -2
  21. package/package.json +3 -3
  22. package/dist/application-spec/generate-application/code/templates/agents-md.js +0 -2
  23. package/dist/application-spec/generate-application/code/templates/common/sdk-content.js +0 -67
  24. package/dist/application-spec/generate-application/code/templates/phoenix-cli-skill.js +0 -1
  25. package/dist/application-spec/generate-application/code/templates/phoenix-skill.js +0 -1
  26. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.d.ts +0 -0
  27. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.js +0 -0
  28. /package/dist/application-spec/generate-application/code/templates/{agents-md.d.ts → skills/agents-md.d.ts} +0 -0
  29. /package/dist/application-spec/generate-application/code/templates/{phoenix-cli-skill.d.ts → skills/phoenix-cli-skill.d.ts} +0 -0
  30. /package/dist/application-spec/generate-application/code/templates/{phoenix-feedback-skill.d.ts → skills/phoenix-feedback-skill.d.ts} +0 -0
  31. /package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.d.ts → skills/phoenix-review-skill.d.ts} +0 -0
  32. /package/dist/application-spec/generate-application/code/templates/{phoenix-skill.d.ts → skills/phoenix-skill.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,485 @@
2
2
 
3
3
  All notable changes to the @salsita-ai/sdk package will be documented in this file.
4
4
 
5
+ ## [0.49.0]
6
+
7
+ fix: configuration reopens properly from URL cid when `power` is `on` on initialization
8
+
9
+ feat: Postprocessing is applied to client-side images rendered with `imageRenderingSystem` `generateAndUploadDeclaredClientSideImages` and `generateDeclaredClientSideImages`
10
+
11
+ feat: add the experimental `extras/assistant` entry point (CON-2852)
12
+
13
+ `useAssistantChat` and the unstyled `AssistantChat` component talk to the server's experimental assistant routes for the current configuration: streamed replies, reasoning summaries, tool calls, stop, and reload that repaints the thread from the server and rejoins a reply still streaming. They need a customer with the AI Assistant feature flag on in Studio (CON-2960). `AuthorizedPhoenixApiClient` gains `getAssistantChatConnection`.
14
+
15
+ chore: bump `openai` to 7.23.0
16
+
17
+ feat: the experimental `assistant:` section declares the AI settings and instructions of the assistant, and preparing a deployment registers them (CON-2854)
18
+
19
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
20
+
21
+ ```yaml
22
+ assistant:
23
+ ai:
24
+ provider: openai
25
+ model: balanced
26
+ reasoning: low
27
+ instructions: |
28
+ Tables are sold in pairs.
29
+ entities:
30
+ Table:
31
+ length:
32
+ ```
33
+
34
+ - `assistant.ai` sets what the assistant runs on: `provider` (`openai`), `model` (a tier: `frontier`, `advanced`, `balanced` or `fast`; the server maps it to a concrete model) and `reasoning` (`low`, `medium` or `high`). All three are required.
35
+ - `assistant.instructions` is optional free text for the assistant about this configurator. It is added to the system prompt after the schema.
36
+ - `deployment:prepare`, `deployment:prepare-local` and the development prepare of `phoenix dev` send the GraphQL schema, a prompt generated by the SDK, the instructions and the AI settings to the server (API `1.41.0` or newer). `deployment:prepare` logs the schema and the character count of the deployment's part of the prompt. A project without an `assistant:` section sends nothing and has no assistant chat.
37
+ - `ai` and `instructions` are read at prepare time only: they are not in `.phoenix/domain-spec.ts` or the runtime `AssistantManifest`.
38
+
39
+ change!: `assistant.ai` is required in every `assistant:` section
40
+
41
+ A spec with an `assistant:` section and no `ai` block no longer parses; the error names the missing key and its allowed values.
42
+
43
+ Migration:
44
+
45
+ - Add an `ai` block with `provider`, `model` and `reasoning` to every `assistant:` section, as shown above.
46
+
47
+ change!: the experimental `extras/assistant` chat no longer registers the `getViewport` browser tool
48
+
49
+ Migration:
50
+
51
+ - None needed in a project: the tool was registered by `useAssistantChat` itself. Use a server that includes this change, which no longer offers `getViewport` to the model.
52
+
53
+ chore: bump `@tanstack/ai` to 0.64.0 and `@tanstack/ai-react` to 0.29.4
54
+
55
+ chore: bump `vitest` to 4.1.11, also in generated project `package.json` files
56
+
57
+ feat: experimental `assistant` spec section with a generated GraphQL schema and a GraphQL editor panel
58
+
59
+ **Experimental and unstable:** the `assistant` section is a proof of concept for the conversational configurator and will be replaced by the full manifest without a deprecation period. Do not rely on it in production projects.
60
+
61
+ ```yaml
62
+ assistant:
63
+ fieldName: hello
64
+ result: world
65
+ ```
66
+
67
+ The section generates a GraphQL schema (`type HelloWorld { hello: String! }`) at parse time. The schema reaches the configurator through `.phoenix/domain-spec.ts` and editor hot reload, and the new `graphQLSystem` configurator system executes queries against it (`{ hello }` answers `"world"`). The editor has a new GraphQL panel for running queries against the live configurator; several named operations in the panel are split into one operation list and run in order, and `phoenix deployment:prepare` logs the generated schema before registering the deployment.
68
+
69
+ feat: the experimental `assistant` section whitelists stored entity fields for the generated GraphQL schema
70
+
71
+ **Experimental and unstable:** the section is part of the conversational configurator work and can still change without a deprecation period. The hello-world shape (`fieldName`, `result`) is removed. Replace it with `entities`.
72
+
73
+ ```yaml
74
+ assistant:
75
+ entities:
76
+ Table:
77
+ length:
78
+ description: Tabletop length in millimeters
79
+ Top:
80
+ thickness:
81
+ as: topThickness
82
+ ```
83
+
84
+ The schema is built from the latest model. Each alias becomes an interface implementing `AnyDomainEntity`, and each entity type becomes an object type implementing every alias that contains it. Every type exposes `id`, `type` and its whitelisted stored fields. A field whitelisted on an alias is inherited by every member type.
85
+
86
+ Field types come from the shape:
87
+
88
+ - numbers, integers, strings and booleans become the matching scalars;
89
+ - `DomainEntityId` references become `ID`;
90
+ - string enums become `String`, with the allowed values in the field description;
91
+ - fields are non-null unless the schema admits `null`, read through phoenix definitions and common types;
92
+ - everything else is opaque `JSON`.
93
+
94
+ A manifest `description` overrides the JSON Schema description, and `as` exposes a field under another name.
95
+
96
+ The query has four entry points:
97
+
98
+ - `root`, typed as the root entity type itself when there is only one, so its fields need no inline fragment;
99
+ - `entities(ids:)`, which returns every entity, or the given ids in order with `null` for an unknown id. An id that is not of the form `<Type>-<number>` is an error;
100
+ - `selectedEntity`;
101
+ - `navigation`, with one field for each state kind that has more than one value (`navigationState`, `viewState`, `toolState`). It is left out when no kind has more than one value.
102
+
103
+ The configurator `graphQLSystem` resolves these against the committed state.
104
+
105
+ Manifest mistakes are parse diagnostics:
106
+
107
+ - an unknown entity type, alias or field;
108
+ - a `$` expression entry (not supported yet);
109
+ - a field exposed as the reserved `id`, `type` or `meta`;
110
+ - one GraphQL name mapped to two stored fields;
111
+ - an entity type or alias named like a built-in schema type.
112
+
113
+ feat: the experimental assistant GraphQL schema gets its first mutation, `deleteEntity`, and `GraphQLSystem` commits a list of operations as one undo step
114
+
115
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. `GraphQLSystem.execute` now returns `{ results, committed, message? }` instead of the bare results array.
116
+
117
+ ```yaml
118
+ assistant:
119
+ deletable:
120
+ Cabinet:
121
+ ```
122
+
123
+ `assistant.deletable` names the entity types or aliases the assistant may delete; an alias stands for all of its members. It adds `deleteEntity(id: ID!): [ID!]!` to a new `Mutation` type. The field takes no selection and returns the ids of every entity the deletion removed, the given one first, followed by the entities removed with it, such as composition children. Without `deletable`, the schema has no `Mutation` type. Do not declare projected entity types deletable: the configurator re-creates them after every change. `Mutation` is now a reserved type name, and an unknown `deletable` key reports `ASSISTANT_UNKNOWN_TYPE`.
124
+
125
+ - `GraphQLSystem.prepare(state, operations)` runs a list of single-operation documents through one working state without committing anything. A query reads the current working state. Mutation fields run in order, as GraphQL runs them: each computes its changes from the state the previous field left and produces its own emission, and a field that would leave the configuration invalid fails. The first operation that fails stops the list; every later operation reports that it did not run. Each operation comes back with its result and the emissions of its mutation fields.
126
+ - `GraphQLSystem.execute(operations)` prepares the list from the committed state and commits it as one emission, synthesized from every field emission: one undo step, with `onBeforeCommit` hooks run once. If the configuration changed in the meantime, the list is prepared again once from the new state. A commit an `onBeforeCommit` hook holds is reported in `message` and is not retried.
127
+ - `StoreSystem.commitPrepared(emission)` commits an emission prepared outside the store. It returns `'conflict'` when the committed state is no longer the emission's `previousState`, and `'held'` when a hook vetoes it. A held emission still commits through the hook's deferred `emit`, but only while the committed state is unchanged; otherwise it is dropped with a warning.
128
+ - Mutation implementations are registered in `assistantMutations`. `deleteEntityUpdateTag` is shared by the assistant and `useDeletedEntity`.
129
+ - The editor GraphQL panel commits on Run and has a "Dry run" toggle that prepares against the committed state without committing.
130
+
131
+ change: in the experimental assistant GraphQL schema, a reference to a common type is typed as the schema behind it instead of `JSON`
132
+
133
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
134
+
135
+ ```yaml
136
+ model:
137
+ latest:
138
+ commonTypes:
139
+ GunColor:
140
+ enum: [Black, Tan, Green]
141
+ shapes:
142
+ Gun:
143
+ gunColor:
144
+ $ref: 'common:GunColor'
145
+ ```
146
+
147
+ An exposed `gunColor` is now `String!` with `One of: "Black", "Tan", "Green".` in its description, and its `GunDelta` field is `String`. Before, both were `JSON`.
148
+
149
+ - A reference to a common type maps exactly as if the common type's schema were written inline: a string enum is `String` with its values listed, a primitive is its scalar, a list of either is a list. This holds for both reference forms (`common:Name` and `#/model/<version>/commonTypes/Name`), through a chain of references and for list items, in stored fields, declared expressions and `<Type>Delta` inputs.
150
+ - A union (`anyOf` or `oneOf`) whose non-null members are all string enums is `String`, and the description lists the allowed values once, in member order: every value of an `anyOf`, and of a `oneOf` the values of exactly one member, since `oneOf` rejects a value that two members allow. Other unions with more than one non-null member stay `JSON`, and so does a schema that has both `anyOf` and `oneOf`.
151
+ - A common type that is an object, a custom `tsType`, a mixed union or part of a reference cycle stays `JSON`.
152
+ - A field with no description of its own and none in the manifest takes the description of the common type it references, directly or as the only non-null member of a union (a nullable reference). A list does not inherit the description of its items' common type.
153
+ - Values are returned as before and only the schema text changes, with one exception: a common type that names a localizable phoenix definition now resolves to a string in the current locale, like an inline reference to that definition. Remove hand-written value lists from manifest descriptions, since the generated ones now follow them. A deployment must be prepared again for the assistant to see the new schema.
154
+ - A `JSON` value is plain serializable data. An expression exposed as `JSON` that returns a value containing itself now fails its field with an error naming the expression and the entity, instead of a stack overflow.
155
+
156
+ feat: the experimental assistant GraphQL schema lists the choices of a writable field under `meta`, `modify<Type>` rejects a value outside them, and `hints` say when a list can be reused
157
+
158
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. The shape of `assistant.childRecipes` changes with it; see the last point.
159
+
160
+ ```yaml
161
+ assistant:
162
+ writable:
163
+ KitchenRange:
164
+ color:
165
+ options:
166
+ componentType: materialSwatch
167
+ instanceName: color
168
+ as: enamelColors
169
+ validate: false
170
+ hints:
171
+ - sameForAllInstances
172
+ - constantDuringSession
173
+ childRecipes:
174
+ KitchenRange:
175
+ hints:
176
+ - constantDuringSession
177
+ groups:
178
+ base:
179
+ description: Base units that can be added to the range
180
+ ```
181
+
182
+ `options` on a `writable` field names the component instance that holds the field's choices. `componentType` is `materialSwatch`, `attributeSelection` or `distanceDimensionSelect`, and `instanceName` its instance on the entity type; an alias key adds the edge to every member, and every member must have the instance.
183
+
184
+ - Each such field adds the nullable edge `<field>Options: [FieldOption!]` to `<Type>Meta`, named after the delta field; `as` replaces the name. An entity type now gets `meta` when it has an options edge or a recipe group.
185
+ - `FieldOption` is `{ value: JSON!, name, description, imageUrl, color }`. `materialSwatch` maps the swatch key to `value`, `swatchName` to `name`, `thumbnailUrl` to `imageUrl`, and `color`; `attributeSelection` maps `value`, `label` to `name`, `description`, and `img` to `imageUrl`; `distanceDimensionSelect` maps `value` and `description`. Texts are in the current language.
186
+ - The edge reads the state its entity was read on, so it also works under a recipe dry-run. It is `null` when the component is not active on the entity; component filters apply as they do in the UI.
187
+ - `modify<Type>` checks each delta field that declares `options` against the choices its component offers in the state the mutation field starts from, by strict equality and before anything is applied. A value that is not among them fails the field with a message listing the valid values. The check is skipped for `null`, when the component is not active on the entity, and with `validate: false`. When the choices of one field depend on another field, write them as two mutation fields of one operation.
188
+ - `hints` is a list of `sameForAllInstances` and `constantDuringSession`, accepted inside `options` and on a `childRecipes` type. Each hint appends a sentence to the generated description of the edge; nothing verifies it. Hints on an alias apply to every member, and a member with its own `childRecipes` entry gets the union.
189
+ - New diagnostics `ASSISTANT_UNKNOWN_OPTIONS_COMPONENT`, `ASSISTANT_RESERVED_OPTIONS_EDGE_NAME` (an edge named `childRecipes`) and `ASSISTANT_DUPLICATE_OPTIONS_EDGE` (an entity type that would get one edge name, or the options of one field, twice, from its own key and from an alias). `ASSISTANT_META_TYPE_NAME_CONFLICT` also covers a type that gets `meta` from an options edge and is reported at the entry that gives the type its `meta`. `FieldOption` is now a reserved name for entity types and aliases of an application with an `assistant` section.
190
+ - The generated assistant prompt explains how to find the values a field can take when the schema has an options edge, so a deployment must be prepared again for the model to learn about it.
191
+ - New domain exports for the GraphQL system: `findAssistantFieldOptions`, `readAssistantFieldOptions` and the `AssistantFieldOption` and `AssistantFieldOptions` types.
192
+ - **Manifests written for the child recipes of this unreleased feature must be migrated:** the recipe groups of a type move under `groups` (`childRecipes: { Room: { cabinets: } }` becomes `childRecipes: { Room: { groups: { cabinets: } } }`). The old shape is a parse error, and the paths of the recipe group diagnostics gain the `groups` segment.
193
+
194
+ feat: the experimental assistant GraphQL schema gets a `modify<Type>` mutation for every entity type with writable fields
195
+
196
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
197
+
198
+ ```yaml
199
+ assistant:
200
+ writable:
201
+ Table:
202
+ length:
203
+ width:
204
+ ```
205
+
206
+ `assistant.writable` names, per entity type or alias, the stored fields the assistant may write; an alias stands for all of its members, and a writable field does not have to be exposed in `entities`. Each entity type with at least one writable field gets an input `<Type>Delta` with those fields, all optional, and `modify<Type>(id: ID!, delta: <Type>Delta!): <Type>` on `Mutation`. Aliases get no mutation of their own. Do not declare projected entity types writable: reconciliation may overwrite their values.
207
+
208
+ - A delta field is named as the field is exposed in `entities` (including `as:`), or by its property name when it is not exposed. When several names expose the property, the one from the type's own `entities` entry wins, then the first inherited from an alias. A localized field takes the raw stored value as `JSON`.
209
+ - A field changes one entity. An omitted delta field stays unchanged, an explicit `null` sets `null`, and an empty delta is refused. Values are in stored units; control hooks (unit conversion, snapping) are bypassed, transformers still run.
210
+ - The changed entity is checked against its shape schema before anything is applied, and the field fails with the validator's messages when it does not fit. An invalid resulting configuration fails the field as for `deleteEntity`.
211
+ - The result is the entity as it is after the field, including transformer adjustments and recomputed expressions, or `null` when the change made a transformer remove the entity.
212
+ - Once a mutation field fails, the later mutation fields of the same operation are not run and each reports a "Not run" error. This holds for every mutation, including nullable ones such as `modify<Type>`, where GraphQL alone would continue.
213
+ - The updates carry the `modifyEntityUpdateTag` (`'modifyEntity'`) update tag.
214
+ - New diagnostic `ASSISTANT_NOT_WRITABLE` for an expression, `id` or `type` under `writable`; `ASSISTANT_UNKNOWN_TYPE`, `ASSISTANT_UNKNOWN_FIELD` and `ASSISTANT_FIELD_NAME_CONFLICT` also cover `writable` entries; the conflict is reported at the `writable` entry and says how to fix it there.
215
+ - `assistantMutations` is replaced by `findAssistantMutation(fieldName, manifest)`, and mutation implementations receive the domain in their context.
216
+
217
+ feat: the experimental assistant GraphQL schema lists the recipes of whitelisted `childEntityRecipes` groups, dry-runs them and adds children with `addChild`
218
+
219
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
220
+
221
+ ```yaml
222
+ assistant:
223
+ childRecipes:
224
+ Room:
225
+ groups:
226
+ cabinets:
227
+ sofas:
228
+ as: seating
229
+ description: Add sofas to the room
230
+ allowDryRuns: false
231
+ ```
232
+
233
+ `assistant.childRecipes` names, per entity type or alias, the instances of the `childEntityRecipes` component whose recipes the assistant may add; an alias stands for all of its members, and every member must have the component instance. `as` is the id the assistant addresses the group by (the instance name by default), `description` replaces the component's group description, and `allowDryRuns: false` turns dry-runs off for a group too large to try out.
234
+
235
+ - Each such entity type gets `meta: <Type>Meta!` with `childRecipes: [RecipeGroup!]!`. A group is listed only while its component is active on the entity; component filters apply as they do in the UI.
236
+ - A recipe list item without recipes adds nothing and is not listed, unlike in the UI.
237
+ - A `Recipe` has `key`, `name`, `description`, `imageUrl` and `dryRun`. `key` is the new optional `key` of the recipe list item, or the title in the current language without one, with ` 2`, ` 3`, … on duplicates. Declare `key` for recipes that instructions or tests name, since a title key changes with the language.
238
+ - `dryRun` cooks the recipe on the state the query reads, the way the add controls do, and changes nothing: `ok`, the `root` and `newEntities` of the resulting configuration (everything selected under them reads that configuration), and `errors` with the violations of the failing severity (the fatal ones, or the fixable ones when none is fatal), or one `uncaught` entry when validation threw. It is `null` for a group with `allowDryRuns: false`.
239
+ - `addChild(parentId: ID!, recipeGroupId: String!, recipeKey: String!): [AnyDomainEntity!]!` makes the same updates as the add controls (`createRecipe` update tag, the component's collection) and returns every entity the change added to the configuration: the recipe's own first, the ones under the parent before the rest, then whatever transformers added in response, projected entities included.
240
+ - New diagnostics `ASSISTANT_UNKNOWN_RECIPE_GROUP`, `ASSISTANT_DUPLICATE_RECIPE_GROUP` and `ASSISTANT_META_TYPE_NAME_CONFLICT`; `ASSISTANT_UNKNOWN_TYPE` also covers `childRecipes` keys. `RecipeGroup`, `Recipe`, `RecipeDryRun` and `RecipeDryRunError` are now reserved names for entity types and aliases of an application with an `assistant` section, as is `<Type>Meta` for a type with recipe groups.
241
+ - The generated assistant prompt explains how to add entities when the schema has `addChild`, so a deployment must be prepared again for the model to learn about it.
242
+ - New domain exports for the GraphQL system: `findAssistantRecipeGroups`, `readAssistantRecipeGroup`, `prepareAssistantRecipeUpdates` and `assistantAddedEntityIds`.
243
+ - `RecipeListItem` gains the optional `key`, also on replace and sketching recipe lists, where nothing reads it yet.
244
+
245
+ feat: the experimental assistant reads and changes the configuration through the `query` and `apply` tools (CON-2870)
246
+
247
+ `useAssistantChat` runs the model's tool calls in the browser against the configurator's GraphQL schema. `query` runs a list of operations from the committed state and discards every change, so mutations in it answer "what if" questions. `apply` runs the same list and commits its changes as one undo step; outside owner mode it refuses without running anything. Both return the GraphQL results with `committed` and, when nothing was committed, a `message` saying why. The prompt generated at `deployment:prepare` gains a section on the two tools, so a deployment has to be prepared again for the model to learn about them.
248
+
249
+ `AssistantChat` renders nothing when the application spec has no `assistant` section. The new `useSpecAssistant` hook of the `react` entry point returns the spec's `assistant` manifest, or nothing when the spec has none.
250
+
251
+ change!: `useAssistantChat` and `AssistantChatState` move from `@salsita-ai/sdk/extras/assistant` to `@salsita-ai/sdk/configurator` (CON-2870)
252
+
253
+ The hook now reads the configurator's systems, so it belongs to the configurator entry point and must be rendered inside the configurator. `extras/assistant` keeps `AssistantChat`. The hook never shipped in a release from its old path.
254
+
255
+ Migration:
256
+
257
+ - Import `useAssistantChat` and `AssistantChatState` from `@salsita-ai/sdk/configurator` instead of `@salsita-ai/sdk/extras/assistant`.
258
+
259
+ change!: client-side image exports without Postprocessing tone map the finished image, not each material
260
+
261
+ Exports rendered by `imageRenderingSystem.renderImage` now run the renderer's tone mapping, exposure and sRGB transfer once over the finished offscreen frame. The scene renders over a transparent background, and a background colour (a Color `scene.background`, the export's `backgroundColor`, or white for JPEG) is composited behind it afterwards. These canvas behaviours, which 0.48.0 preserved by drawing exports on the live canvas, no longer hold without Postprocessing:
262
+
263
+ - material overrides with `toneMapped: false` are tone mapped in exported images;
264
+ - a texture or environment `scene.background` in an sRGB colour space is tone mapped in exported images, where the canvas leaves it untouched;
265
+ - semi-transparent materials blend in linear space before tone mapping, so they can come out lighter than on the canvas;
266
+ - additive, multiply and other blend modes that read the destination colour (`blendingMode` in a material override) render against a transparent background and do not blend with the export's background colour, so a multiply-blended shadow over a plain background can disappear and an additive glow can darken it;
267
+ - transmissive materials (`transmission`, e.g. glass) refract a neutral half-transparent white where the canvas refracts the background colour.
268
+
269
+ `CustomToneMapping` keeps applying, through the same `tonemapping_pars_fragment` chunk override that materials use.
270
+
271
+ Migration: compare the exports of projects that rely on `toneMapped: false` for exact colours (unlit labels, logos, engravings), on an image background, or on semi-transparent, additive, multiply-blended or transmissive materials against the previous release. There is no per-material opt-out in exports: keep such colours exact by choosing them so they survive the renderer's tone mapping, or set `toneMapping: NoToneMapping` on the renderer when the scene does not need it. On-canvas rendering is unchanged.
272
+
273
+ fix: client-side image exports no longer flash export frames on the live canvas
274
+
275
+ `imageRenderingSystem.renderImage` renders offscreen again, with or without Postprocessing, and keeps the exposure and transparent backgrounds of the current canvas-based images. It backs declared `clientSideImage` components, save-template thumbnails and editor screenshots. While images generate, the live canvas shows a still of the current view. Standalone `renderImage` calls now pause the frameloop for the duration of the export. The pause is `exportSystem.pauseFrameloop`, which `runExportJobs` holds too, so exports that overlap share it and the loop resumes with the last of them.
276
+
277
+ chore: regenerate the API client types for the SDK-builder build report endpoint (API `1.34.0`)
278
+
279
+ feat: add `phoenix deployment:build-envs`, which writes the environment for a build that runs apart from the deploy
280
+
281
+ For a deployment prepared with `deployment:prepare`, it writes only what a build running project code without platform secrets may see: the customer's environment variables, their Clerk/OAuth settings and the public platform values. The file has the format of `.env.generated` and is written there by default, so the project builds with it unchanged; move the full `.env.generated` that `deployment:prepare` wrote out of the folder first, or set `OUT_FILE`. Requires a server with API `1.35.0` or newer.
282
+
283
+ feat: add `phoenix login <workspace>` and `phoenix logout` for SDK-builder workspaces
284
+
285
+ `phoenix login` opens Studio, where you approve the sign-in. The CLI then receives a revocable API token through a PKCE-protected callback on `127.0.0.1` and stores it in `~/.phoenix/credentials.json`, readable only by you. Signing in again revokes the token it replaces. `phoenix logout` revokes the token and removes it. Both use the EU region by default; `--region us` selects the US region, and `--serverUrl` / `--studioUrl` point at a local or custom server.
286
+
287
+ feat: add `phoenix init --scaffold` and `phoenix deploy` for SDK-builder workspaces
288
+
289
+ `phoenix init [folder] --scaffold` only writes the starter project: a `package.json` that depends on the CLI's SDK version when the folder has none, and the starter spec. It records no development environment and doesn't sync. `phoenix deploy` validates the spec, uploads the project files and follows the server-side build until the configurator is live. See "SDK Builder Workflow" in the docs.
290
+
291
+ feat: add `phoenix deployment:extract-sources` for build pipelines
292
+
293
+ `phoenix deployment:extract-sources <archive> <folder>` validates the source archive of a `phoenix deploy` and extracts its project files. It refuses links, special files, unsafe paths, colliding entries, oversized content and archives it cannot read to the end, and leaves out entries that are not project files.
294
+
295
+ fix: new application-spec projects get a webhooks entry point that builds
296
+
297
+ The scaffolded `src/webhooks/index.ts` built a webhook with no handler, which throws. It now registers an anonymous auth hook on `phx.webhooks`, which stays inert until the spec sets `useAuthHook`. Existing projects keep their file.
298
+
299
+ feat: the experimental `assistant` section whitelists expressions and relation edges, and localizable values resolve to strings
300
+
301
+ **Experimental and unstable:** the section is part of the conversational configurator work and can still change without a deprecation period. The `.phoenix` field side table (`AssistantManifest.fields`) now maps each GraphQL field to `{ source, localized? }` instead of a stored field name.
302
+
303
+ ```yaml
304
+ assistant:
305
+ entities:
306
+ Table:
307
+ $price:
308
+ description: Total price in EUR
309
+ $top:
310
+ as: tableTop
311
+ ```
312
+
313
+ A `$` entry whitelists an expression declared in `expressions`, `constants` or `relations`. An entity type can also whitelist expressions declared on its aliases, while an alias can whitelist only its own. An expression is exposed as `_name` by default, and `as` renames it.
314
+
315
+ - Relation expressions become edges. The target is an entity's object type, an alias's interface, or, for an alias with a single member, that member's object type. `1-1` edges are `T!`, `0-1` edges are `T`, and `0-N` and `1-N` edges are `[T!]!`.
316
+ - Other expressions are typed from their declared schema like stored fields. Constraints that component `$read` inference adds do not change the type. Everything that is not a primitive, a string enum or a list of those is `JSON`. A `$` entity inside a `JSON` value is returned as its stored entity.
317
+ - Refs to the localizable phoenix definitions (`Localizable`, `EnhancedLocalizable`, `MessageDescriptor`, `EnhancedMessageDescriptor`, `MultilingualString`) are `String`, on stored fields and expressions alike. They resolve in the current locale, and lists resolve item by item. A `tsType` alone stays `JSON`.
318
+ - An expression that throws fails its field with an error naming the expression and the entity. The `null` propagates by GraphQL's rules, so a non-null field also nulls its parent.
319
+
320
+ A `$msg` expression value is now inferred as `{ $ref: 'phx:MessageDescriptor' }` rather than a bare `MessageDescriptor` `tsType`. The generated TypeScript types are unchanged.
321
+
322
+ Manifest diagnostics for `$` entries:
323
+
324
+ - `ASSISTANT_UNKNOWN_FIELD` for a `$` entry that is not declared where it is whitelisted, including an expression only inferred from a component `$read`;
325
+ - `ASSISTANT_UNSUPPORTED_EXPRESSION` for `$violations`, which gets its own edge later.
326
+
327
+ fix: dragging against a rotated `disjoint` peer no longer drops the frame rate — auto-snap reads the proposed drag state without validating it, so invariants are evaluated once per tick on the constrained state instead of also on the overlapping one
328
+
329
+ feat: give every configurator session a user identity (CON-2897)
330
+
331
+ The SDK keeps a random browser id under the localStorage key `configurator-user-id` (in memory when storage is unavailable) and sends it with every session authentication, so a session without a signed-in user belongs to the browser's anonymous user. It is separate from the analytics `user_id`, which stays unchanged. `UnauthenticatedPhoenixApiClient.authenticateSession` gains an optional `browserUserId` parameter.
332
+
333
+ Auth handlers may return the signed-in user's id as `userId`. The auth handler builder's Clerk handlers return the Clerk user id (it also stays in `data.id`), and the builder marks it as a Clerk id; ids from every other handler are custom ids, so a custom handler cannot pass as a Clerk user. A custom refresh handler that omits `userId` turns the session into the browser's anonymous user, so return it from every flow that knows the user.
334
+
335
+ feat: ship coding-agent guidance as Phoenix agent skills and a starter `AGENTS.md` (CON-2911)
336
+
337
+ Sync, dev and build now write the `phoenix`, `phoenix-cli` and `phoenix-review` skills to `.claude/skills/` and `.agents/skills/` and keep them in step with the installed SDK, so Claude Code, Codex and Cursor all pick them up. A short `AGENTS.md` with the build commands, the do-not-edit list and a pointer to the skills is written once when missing and is the project's to edit afterwards. Phoenix only manages `phoenix` and `phoenix-*` skill folders; any other skill folder is left alone.
338
+
339
+ The CLI no longer generates `CLAUDE.md`, `.cursor/rules/phoenix.mdc` or the `commands/review.md` files. On the next sync, the `[PHOENIX-SDK-START]`/`[PHOENIX-SDK-END]` block is removed from `CLAUDE.md` and `.cursor/rules/phoenix.mdc` with your own text around it kept, a file that held nothing else is deleted, and both `commands/review.md` files are deleted. Use the `phoenix-review` skill where you used `/review`.
340
+
341
+ feat: add `phoenix init` to start a project in a folder where the SDK is installed (CON-2911)
342
+
343
+ `phoenix init` writes a starter spec with one resizable, recolorable box, asks for the Studio development-environment values (`SERVER_URL`, `PROJECT_SLUG`, `ENVIRONMENT_SLUG`, `DEVELOPMENT_KEY`) that are not already in `.env` and appends them there, switches `package.json` to the application-spec pipeline, and runs `phoenix sync`. The development key is read without echo. Each value can also be passed as a flag; without a terminal, missing values fail the command instead of prompting. `phoenix init` refuses to run when a `spec/` folder already exists.
344
+
345
+ change!: use generic names for bundled theme presets and remove customer-specific commentary from published package content.
346
+
347
+ Migration: select `RoundedRed` for the red rounded preset in `themePreset` configuration and use `getPhoenixThemes().RoundedRed` in TypeScript. The previous red preset key is removed without an alias. All other public preset keys and theme styles are unchanged.
348
+
349
+ change!: publish the SDK publicly as `@salsita-ai/sdk`, the one package a project depends on, under a proprietary licence
350
+
351
+ The SDK is now public on npm under the Salsita SDK License (see `LICENSE`). It re-exports every entry point of `@salsita-ai/common` (`./common/{utils,env,eslint,prettier}`) and `@salsita-ai/contracts` (`./contracts/{api,clerk,domain,chat,assistant,webhook}`), so a project declares `@salsita-ai/sdk` alone; generated code imports through it, and sync drops the superseded dependency names from `package.json`. Published code is minified and ships without source maps, and the archive no longer carries the SDK's own test fixtures and snapshots. Builds from `develop` publish under the `canary` tag, `publish_to_npm` PR builds under `beta`. `@salsita-ai/platform-project-sdk`, `@salsita-ai/platform-common` and `@salsita-ai/platform-contracts` receive no further releases. The `phoenix` CLI, the `phx` global and the `.phoenix` generated folder are unchanged.
352
+
353
+ Migration:
354
+
355
+ - In `package.json`, replace the three old dependencies with `"@salsita-ai/sdk": "<version>"` alone, then run `pnpm install` and `pnpm phoenix sync`. Sync regenerates the project files and removes the old dependency names if they are still declared.
356
+ - Replace imports in your own code and YAML `tsType` imports:
357
+ - `@salsita-ai/platform-project-sdk/<path>` → `@salsita-ai/sdk/<path>`
358
+ - `@salsita-ai/platform-common/<path>` → `@salsita-ai/sdk/common/<path>` (`utils`, `env`, `eslint`, `prettier`)
359
+ - `@salsita-ai/platform-contracts/<path>` → `@salsita-ai/sdk/contracts/<path>` (`api`, `clerk`, `domain`, `chat`, `assistant`, `webhook`)
360
+ - Remove the `@salsita-ai` registry token from `.npmrc` and CI once no restricted package is installed; the public package needs none.
361
+
362
+ feat: add the `phoenix-create-configurator` agent skill (CON-2924)
363
+
364
+ Sync, dev and build now also write the `phoenix-create-configurator` skill to `.claude/skills/` and `.agents/skills/`, with its `references/` files beside it. It walks a coding agent from the builder's product documents and assets to a reviewed configurator in five stages: a representative first slice, a plan of the whole configuration experience, options and features added in small increments, finalization, and a complete review. The agent studies the SDK documentation through `pnpm phoenix docs` before building, keeps a `configurator-spec.md` in the project as the record of decisions and open questions, and asks the builder only for decisions it cannot derive from the materials.
365
+
366
+ Files inside a Phoenix-owned skill folder that the installed SDK no longer emits are now removed on sync, the same way stale `phoenix-*` skill folders are.
367
+
368
+ feat: `phoenix dev` works from the `phoenix login` sign-in, without a development key (CON-2948)
369
+
370
+ With no `DEVELOPMENT_KEY` set, `phoenix dev` uses the stored sign-in: on start it asks the server for your development environment in the project and works there. The server creates the environment on the first run, and every user gets their own. No `.env` and no environment in Studio are needed. The server is `--serverUrl`, then `SERVER_URL`, then the region (`--region` or `PHOENIX_REGION`). Projects with a `DEVELOPMENT_KEY` in `.env` work as before.
371
+
372
+ feat: the application spec names its server project with `project` (CON-2948)
373
+
374
+ `project` in a spec file is a project slug, or `default` for the default project of the workspace you are signed in to. `phoenix dev` resolves the project from `--projectSlug`, then `PROJECT_SLUG`, then `project`, and fails when none names one. `default` needs the sign-in; with a development key, set `PROJECT_SLUG` or name the slug. The field is optional, belongs in one spec file, is read only by the CLI and does not reach the built application.
375
+
376
+ feat: `phoenix init` only writes the starter project (CON-2948)
377
+
378
+ `phoenix init [folder]` writes `package.json` when the folder has none and the starter spec, which now begins with `project: default`. It no longer prompts for the Studio development-environment values, writes `.env` or runs `phoenix sync`, and the `--serverUrl`, `--projectSlug`, `--environmentSlug` and `--developmentKey` flags are removed. This replaces the prompting `phoenix init` and the `--scaffold` mode added earlier in this release; `--scaffold` is still accepted and changes nothing. To develop against a Studio development environment, paste its `.env` block into `.env` after `init`.
379
+
380
+ feat: the experimental assistant GraphQL schema lists the recipes an entity can be replaced with under `meta { replaceRecipes }`, dry-runs them and replaces the entity with `replaceEntity`
381
+
382
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
383
+
384
+ ```yaml
385
+ assistant:
386
+ replaceRecipes:
387
+ CookingTop:
388
+ hints:
389
+ - stable
390
+ groups:
391
+ replace:
392
+ as: others
393
+ description: Other cooking components this one can be replaced with
394
+ allowDryRuns: false
395
+ ```
396
+
397
+ `replaceRecipes` whitelists `replaceEntityRecipes` component instances per entity type or alias, in the shape `childRecipes` has: `groups` keyed by the instance name, each with `as`, `description` and `allowDryRuns`, and `hints` on the type.
398
+
399
+ - An entity type with a replace group gets `replaceRecipes: [RecipeGroup!]!` on its `<Type>Meta`, beside `childRecipes` when it has both. `RecipeGroup`, `Recipe`, `RecipeDryRun` and `RecipeDryRunError` are shared by both edges; their descriptions name the recipe mutations the schema has and no longer speak of adding only, so the SDL of a schema with `childRecipes` changes too.
400
+ - The schema gets `replaceEntity(entityId: ID!, recipeGroupId: String!, recipeKey: String!): [AnyDomainEntity!]!` when any entity type exposes a replace group. It makes the updates the configurator's replace controls make, with the `replaceRecipe` update tag and the component's `keepChildren`, and returns every entity that is new after the change and still exists: the replacement first, then what its recipe created under it, then what transformers added in response.
401
+ - The replacement is a new entity with a new id and the field values of its recipe. `dryRun` tries the replacement on the state the query reads.
402
+ - `replaceEntity` fails with a message that says how to correct the request when the entity does not exist, its type has no such group, the group is not active on the entity, the group has no recipe with that key, or the recipe does not cook to exactly one root entity. Such a recipe is still listed, and its `dryRun` reports `ok: false` with one `uncaught` error.
403
+ - Group ids are unique per entity type within `replaceRecipes`; a child group and a replace group of one type may share an id.
404
+ - `ASSISTANT_UNKNOWN_TYPE`, `ASSISTANT_UNKNOWN_RECIPE_GROUP` and `ASSISTANT_DUPLICATE_RECIPE_GROUP` are also reported for `replaceRecipes` entries and name the section, the component and the mutation of the entry. `ASSISTANT_RESERVED_OPTIONS_EDGE_NAME` also covers an options edge named `replaceRecipes`, and `ASSISTANT_META_TYPE_NAME_CONFLICT` a type that gets `meta` from a replace group.
405
+ - The generated assistant prompt explains how to replace entities when the schema has `replaceEntity`, so a deployment must be prepared again for the model to learn about it.
406
+ - The runtime manifest gains `replaceRecipes`. The domain exports `findAssistantRecipeGroups` and `readAssistantRecipeGroup` take the recipe kind (`'child'` or `'replace'`, the new `AssistantRecipeKind` type), and `AssistantRecipeGroupSnapshot` holds the component's `collection` or `keepChildren` under `target`.
407
+
408
+ feat: add `phoenix feedback <message>` and the `phoenix-feedback` agent skill (CON-2964)
409
+
410
+ `phoenix feedback "<message>" --type bug|idea|other` sends feedback about the SDK, the CLI, the documentation or Studio to the Phoenix team. It uses the `phoenix login` sign-in, so the server knows the workspace, user and API token it comes from. `--contact` leaves a contact for a reply, `--agent` names the coding agent that wrote the message, and `--projectSlug` (or `PROJECT_SLUG`, or `project` of the spec) names the workspace project it is about. Besides the message, only the SDK version, operating system and Node.js version are sent. It needs a server with API 1.40.0 or newer.
411
+
412
+ Sync, dev and build now also write the `phoenix-feedback` skill to `.claude/skills/` and `.agents/skills/`. It tells coding agents when to draft feedback, to show the draft and send it only after the user approves it, how to write it, and never to include secrets, customer data or the project's source.
413
+
414
+ feat: the options of a writable field in the experimental assistant GraphQL schema can be dry-run, and option and recipe dry-runs share one `DryRun` result with `changedEntities` and `deletedEntityIds`
415
+
416
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. The dry-run result of recipes changes with it; see the last point.
417
+
418
+ ```yaml
419
+ assistant:
420
+ writable:
421
+ KitchenRange:
422
+ color:
423
+ options:
424
+ componentType: materialSwatch
425
+ instanceName: color
426
+ allowDryRuns: false
427
+ ```
428
+
429
+ - `FieldOption` gains `dryRun: DryRun`. It tries the single write `modify<Type>` would make for the option's `value` on the state the entity was read on, the way a recipe dry-run tries a recipe, and changes nothing. It does not repeat the options check of `modify<Type>`, but it checks the entity against its shape first: a value that does not fit the field reports `ok: false` with one `uncaught` error.
430
+ - `allowDryRuns: false` inside `options` turns the dry-run of that field's options off: `dryRun` is `null`, and the generated description of the edge says its options cannot be dry-run. The default is `true`. Each selected dry-run is a full emission and nothing limits their number.
431
+ - `DryRun` is `{ ok: Boolean!, root, changedEntities: [AnyDomainEntity!]!, deletedEntityIds: [ID!]!, errors: [DryRunError!]! }`. `changedEntities` lists every entity the change would add or modify, read from the resulting state and in its order; an entity whose id the configuration does not have yet is new. `deletedEntityIds` lists the ids the change would remove. Both are empty when `ok` is false. Entities are compared by instance, not by value, so an entity that is written to is listed even when it gets the values it already has; the option that holds a field's current value reports its entity as changed.
432
+ - The generated assistant prompt describes the dry-run result by its new fields and explains the option dry-run and its cost, so a deployment must be prepared again for the model to learn about it.
433
+ - `DryRun` and `DryRunError` are now reserved names for entity types and aliases of an application with an `assistant` section; `RecipeDryRun` and `RecipeDryRunError` no longer are.
434
+ - **Queries and instructions written against the recipe dry-run of this unreleased feature must be migrated:** the types `RecipeDryRun` and `RecipeDryRunError` are renamed to `DryRun` and `DryRunError`, and `newEntities` is removed. Select `changedEntities` instead; it also holds the entities the change would modify, is ordered as the resulting state rather than with the recipe's own entities first, and a new entity is told by its id. `addChild`, `replaceEntity` and `modify<Type>` return what they did before.
435
+
436
+ chore: follow-ups to the experimental assistant schema: the hint `stable` is named `constantDuringSession`, `Recipe.entityType` is gone, and descriptions are more precise (CON-2981)
437
+
438
+ **Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. Neither the hint nor the field has shipped in a release, so there is no deprecation period: write `- constantDuringSession` where a manifest had `- stable` under `hints` (the old name is a parse error), and remove `entityType` from queries that select it on a `Recipe`; the type of what a recipe creates is the `type` of the entities under `dryRun { newEntities }`.
439
+
440
+ - The generated hint sentence is unchanged ("They do not change during the session.").
441
+ - An entity type that inherits a string enum field from an alias whose members declare different enums lists only its own values in the field description; the alias interface keeps listing all of them. The generated SDL changes for every such field.
442
+ - A field that references a common type which is itself a nullable reference to another common type inherits the inner description, as a nullable reference written on the field already did.
443
+ - The `RecipeDryRun` descriptions of `ok` and `newEntities` say that the result holds for the configuration the query reads and that ids may differ in the real mutation.
444
+ - The generated prompt says that an `apply` result with `committed: false` and no `message` means the operations changed nothing.
445
+ - `deleteEntity`, `modify<Type>`, `addChild` and `replaceEntity` parse their arguments with zod schemas. A malformed entity id is now rejected for its format before the entity is looked up; the "does not exist" messages are unchanged.
446
+ - The SDL or the generated prompt changes with each of these, so a deployment must be prepared again for the model to learn about them.
447
+
448
+ fix: a fresh SDK-builder project installs, previews and deploys with pnpm 11
449
+
450
+ - `phoenix dev` hands the server it prepared the development deployment on to the preview as `SERVER_URL`, so the preview of a signed-in project without a `.env` no longer calls `http://localhost:3000`.
451
+ - `phoenix init` writes a `pnpm-workspace.yaml` that decides the build scripts of the SDK's dependencies (`@swc/core`, `esbuild`, `es5-ext`) and exempts `@salsita-ai/*` from pnpm's minimum release age, so the first `pnpm install` no longer fails with `ERR_PNPM_IGNORED_BUILDS`. `phoenix sync` and `phoenix deploy` add the same entries to a standalone project that lacks them and keep the values the project set. A project inside a pnpm monorepo is left to its workspace root.
452
+ - `phoenix deploy` packs `pnpm-workspace.yaml`, so the server-side install follows the project's own pnpm settings and no longer rejects a same-day SDK build with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`.
453
+
454
+ feat: `phoenix dev` prints the preview address and `phoenix deploy` links to the build in Studio
455
+
456
+ `phoenix dev` prints `Preview: <url>` once the dev server is up. `phoenix login` stores the Studio URL of the sign-in, and `phoenix deploy` prints `Follow the build in Studio: <url>` when the build starts; sign in again to get the link for a sign-in stored by an older CLI. The generated `phoenix` agent skill tells the coding agent to open the preview in the user's browser before its first change and to open the build page after a deploy starts.
457
+
458
+ chore: regenerate the API types for the build log token and the build log write endpoint
459
+
460
+ feat: `phoenix dev` takes `--skipUpload` (env: `SKIP_UPLOAD`)
461
+
462
+ When `phoenix dev` prepares its development deployment at startup, it uploads the project's assets to S3. With `--skipUpload` the deployment is prepared without the upload, like `phoenix deployment:prepare-dev --skipUpload`. Defaults to `false`, preserving existing behavior. The flag also reaches the legacy pipeline's start-up prepare. A project whose `.env` already sets `SKIP_UPLOAD=true` for the `deployment:prepare-*` commands now skips the upload in `phoenix dev` too; remove the variable or pass `--skipUpload=false` to keep uploading.
463
+
464
+ feat: the `phoenix-feedback` agent skill asks for a rating after a live deploy
465
+
466
+ When `phoenix deploy` reports the configurator live, the coding agent asks the user once per conversation to rate building with Phoenix from 0 to 10 (10 is the best), with an optional comment. It says that the answer goes to the Phoenix team, shows the message of three labelled lines (`Rating`, `Comment`, `Stage`) and sends it with `phoenix feedback` once the user approves; it sends nothing when the user skips the question. Run `phoenix sync` to get the updated skill.
467
+
468
+ chore: the `phoenix` agent skill checks for the latest canary build during the internal beta
469
+
470
+ In a project generated by a canary build of the SDK from npm (not a workspace link, a stable release or a PR beta), once per conversation the coding agent compares the installed `@salsita-ai/sdk` with the newest `canary` build and, when they differ, tells the user, updates the project and runs `phoenix sync`. It is temporary and goes away at launch.
471
+
472
+ feat: preserve live camera distance, zoom, and rotation across matching lock groups
473
+
474
+ Perspective cameras can opt into distance preservation with `distanceLockGroup`, orthographic cameras can preserve zoom with `zoomLockGroup`, and both camera types can preserve rotation with `rotationLockGroup`. Matching explicit groups carry the live view into the destination camera while respecting its limits; Reset bypasses preservation for that invocation.
475
+
476
+ fix: compile production frontend bundles with React's production implementation
477
+
478
+ Phoenix builds now replace `process.env.NODE_ENV` with `"production"` for configurator, admin, embedded, and editor outputs, removing development-only dependency code and avoiding React development mode in deployed applications.
479
+
480
+ fix: show children passed to `Recipe.Item` through `slotProps.image` again
481
+
482
+ Since the overridable icons were added, `Recipe.Item` passed its placeholder icon as the children of the image slot, which replaced any children given through `slotProps.image`. A custom `image` slot that renders its children, for example layered thumbnails, showed only the placeholder. Children from `slotProps.image` now win over `imagePlaceholderIcon`, as they do in `Recipe.GroupTitle` and `Summary.Item`.
483
+
5
484
  ## [0.48.0]
6
485
 
7
486
  feat: expose the domain-aware localizable resolver to webhook handlers and use its `$lang` fallback in task builders
@@ -12,7 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  export { stripSdkMarkerContent } from './common/strip-sdk-marker-content.js';
15
- export { generateAgentsMd } from './agents-md.js';
15
+ export { generateAgentsMd } from './skills/agents-md.js';
16
16
  export { generateApplicationSpecAdminConfig } from './admin-config.js';
17
17
  export { generateApplicationSpecAdminCode } from './admin-domain-code.js';
18
18
  export { generateApplicationSpecAdminFrontendCode } from './admin-frontend-code.js';
@@ -42,11 +42,11 @@ export { generateApplicationSpecHtmlIndex } from './html-index.js';
42
42
  export { generateLinguiRcFile } from './lingui-rc.js';
43
43
  export { generateApplicationSpecNvmrc } from './nvmrc.js';
44
44
  export { generateApplicationSpecPackageJson } from './package-json.js';
45
- export { generatePhoenixCliSkill } from './phoenix-cli-skill.js';
46
- export { generatePhoenixCreateConfiguratorSkill, generatePhoenixCreateConfiguratorSkillReferences, } from './phoenix-create-configurator-skill.js';
47
- export { generatePhoenixFeedbackSkill } from './phoenix-feedback-skill.js';
48
- export { generatePhoenixReviewSkill } from './phoenix-review-skill.js';
49
- export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } from './phoenix-skill.js';
45
+ export { generatePhoenixCliSkill } from './skills/phoenix-cli-skill.js';
46
+ export { generatePhoenixCreateConfiguratorSkill, generatePhoenixCreateConfiguratorSkillReferences, } from './skills/phoenix-create-configurator-skill.js';
47
+ export { generatePhoenixFeedbackSkill } from './skills/phoenix-feedback-skill.js';
48
+ export { generatePhoenixReviewSkill } from './skills/phoenix-review-skill.js';
49
+ export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } from './skills/phoenix-skill.js';
50
50
  export { generateApplicationSpecPrettierConfig } from './prettier-config.js';
51
51
  export { generateApplicationSpecPrettierIgnore } from './prettier-ignore.js';
52
52
  export { generateApplicationSpecPrivateAssetsReadme } from './private-assets-readme.js';
@@ -1 +1 @@
1
- export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./phoenix-review-skill.js";export{followsCanaryBuilds,generatePhoenixSkill}from"./phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
1
+ export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./skills/agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./skills/phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./skills/phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./skills/phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./skills/phoenix-review-skill.js";export{followsCanaryBuilds,generatePhoenixSkill}from"./skills/phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
@@ -0,0 +1,2 @@
1
+ import{sdkGuidanceSections as e}from"./sdk-content.js";export const generateAgentsMd=()=>`${[e.projectIntro,e.buildCommands,e.doNotEdit,"Phoenix SDK guidance lives in the `phoenix`, `phoenix-cli`, `phoenix-create-configurator`, `phoenix-feedback` and `phoenix-review` skills under `.claude/skills/` and `.agents/skills/`. Phoenix rewrites them on every sync, removes anything else inside their folders and removes other `phoenix`-prefixed skill folders, so to customise a skill, copy it to a folder whose name does not start with `phoenix`."].join("\n\n")}
2
+ `;
@@ -0,0 +1 @@
1
+ import{renderAgentSkill as n}from"./agent-skill.js";import{sdkGuidanceSections as e}from"./sdk-content.js";export const generatePhoenixCliSkill=()=>n({name:"phoenix-cli",description:"Phoenix CLI tooling for building and inspecting this configurator, covering build, dev and sync commands, dev logs, glTF asset inspection, screenshots, runtime state, data and scene inspection, and Playwright MCP setup. Use when building, debugging build or runtime errors, checking 3D assets, or inspecting the running configurator.",sections:[e.buildCommands,e.devLogs,e.assetInspection,e.screenshotCli,e.inspectState,e.inspectData,e.inspectScene,e.playwrightMcpSetup]});
@@ -1,3 +1,3 @@
1
- import { type AgentSkillReference } from './common/agent-skill.js';
1
+ import { type AgentSkillReference } from './agent-skill.js';
2
2
  export declare const generatePhoenixCreateConfiguratorSkill: () => string;
3
3
  export declare const generatePhoenixCreateConfiguratorSkillReferences: () => readonly AgentSkillReference[];
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let t=`Guide the client through five stages: a representative first slice, the overall configuration flow, iterative options and features, finalization, and a complete review. Make the client comfortable, informed, and in control while taking responsibility for platform research, implementation, and verification.
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let t=`Guide the client through five stages: a representative first slice, the overall configuration flow, iterative options and features, finalization, and a complete review. Make the client comfortable, informed, and in control while taking responsibility for platform research, implementation, and verification.
2
2
 
3
3
  “Client” means the person creating the configurator. “Configurator user” means its intended user, such as a buyer, salesperson, or designer. Tailor the experience to that audience; do not assume a purchasing journey.`,n=`## Foundations
4
4
 
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let t=`## When to Raise Feedback
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let t=`## When to Raise Feedback
2
2
  Draft feedback for the Phoenix team when one of these happens while you work in this project:
3
3
  - **Bug** — the SDK, the \`phoenix\` CLI or generated code crashes, produces wrong output or behaves differently from its documentation, and the cause is not in this project's own code.
4
4
  - **Misleading guidance** — a doc page, a Phoenix skill or an error message sent you the wrong way, or did not say what you needed.
@@ -1,4 +1,4 @@
1
- import{renderAgentSkill as e}from"./common/agent-skill.js";let i=`Review all current git changes for correctness, Phoenix pattern violations, and security issues.
1
+ import{renderAgentSkill as e}from"./agent-skill.js";let i=`Review all current git changes for correctness, Phoenix pattern violations, and security issues.
2
2
 
3
3
  ## Phoenix-Specific Review
4
4
 
@@ -9,7 +9,7 @@ import{renderAgentSkill as e}from"./common/agent-skill.js";let i=`Review all cur
9
9
  - [ ] Invariants have matching implementations in src/invariants/
10
10
  - [ ] Component bindings use correct entity types or aliases
11
11
  - [ ] $read/$write/$bind reference valid properties or expressions
12
- - [ ] Model version incremented when schema changes (requires new v\\*.ts migration)
12
+ - [ ] A model that has been deployed is unchanged under its version: the change is in \`model.latest\`, the deployed model is kept as \`model.v<N>\`, and \`src/migrations/v<N+1>.ts\` migrates saved configurations
13
13
 
14
14
  ### Expressions & Invariants (src/expressions/, src/invariants/)
15
15