@salsita-ai/sdk 0.50.0-develop.bd84173265 → 0.50.0-develop.bded01bb03

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 (297) hide show
  1. package/README.md +8 -4
  2. package/dist/admin/default-admin-elements.d.ts +9 -0
  3. package/dist/admin/default-admin-elements.js +1 -0
  4. package/dist/admin/default-elements/built-in-entity-element.d.ts +10 -0
  5. package/dist/admin/default-elements/built-in-entity-element.js +1 -0
  6. package/dist/admin/default-elements/built-in-layout-element.d.ts +10 -0
  7. package/dist/admin/default-elements/built-in-layout-element.js +1 -0
  8. package/dist/admin/default-elements/index.d.ts +2 -0
  9. package/dist/admin/default-elements/index.js +1 -0
  10. package/dist/admin/index.d.ts +1 -0
  11. package/dist/admin/index.js +1 -1
  12. package/dist/application-spec/errors/diagnostic-codes.d.ts +1 -0
  13. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  14. package/dist/application-spec/errors/diagnostic-messages.d.ts +1 -0
  15. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  16. package/dist/application-spec/generate-application/code/common/code-imports.js +1 -1
  17. package/dist/application-spec/generate-application/code/common/env-reader.js +2 -2
  18. package/dist/application-spec/generate-application/code/common/index.d.ts +1 -0
  19. package/dist/application-spec/generate-application/code/common/index.js +1 -1
  20. package/dist/application-spec/generate-application/code/common/single-quoted-string.d.ts +6 -0
  21. package/dist/application-spec/generate-application/code/common/single-quoted-string.js +1 -0
  22. package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
  23. package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +5 -7
  24. package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
  25. package/dist/application-spec/generate-application/code/story-files.js +1 -1
  26. package/dist/application-spec/generate-application/code/templates/admin-config.js +3 -0
  27. package/dist/application-spec/generate-application/code/templates/common/format-property-key.d.ts +7 -0
  28. package/dist/application-spec/generate-application/code/templates/common/format-property-key.js +1 -0
  29. package/dist/application-spec/generate-application/code/templates/common/schema-to-type-string.js +3 -3
  30. package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/domain-factory-types.d.ts +35 -0
  31. package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/domain-factory-types.js +5 -0
  32. package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/index.d.ts +1 -0
  33. package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/index.js +1 -0
  34. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-component-optics.d.ts +1 -1
  35. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-component-optics.js +6 -6
  36. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +59 -63
  37. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.d.ts +8 -0
  38. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.js +1 -1
  39. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-presets.js +1 -1
  40. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-resources.js +13 -13
  41. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-states.d.ts +1 -1
  42. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-states.js +2 -2
  43. package/dist/application-spec/generate-application/code/templates/domain-spec/index.d.ts +2 -1
  44. package/dist/application-spec/generate-application/code/templates/domain-spec/index.js +1 -1
  45. package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.d.ts +43 -0
  46. package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.js +31 -0
  47. package/dist/application-spec/generate-application/code/templates/global-definition/index.d.ts +1 -0
  48. package/dist/application-spec/generate-application/code/templates/global-definition/index.js +1 -1
  49. package/dist/application-spec/generate-application/code/templates/globals.js +24 -43
  50. package/dist/application-spec/generate-application/code/templates/index.d.ts +2 -2
  51. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  52. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -1
  53. package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.js +1 -1
  54. package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.js +5 -5
  55. package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.js +2 -2
  56. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -1
  57. package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.js +2 -2
  58. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +13 -2
  59. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +2 -2
  60. package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.d.ts +58 -0
  61. package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.js +1 -0
  62. package/dist/application-spec/generate-application/code/templates/spec-code/index.d.ts +2 -0
  63. package/dist/application-spec/generate-application/code/templates/spec-code/index.js +1 -0
  64. package/dist/application-spec/generate-application/code/templates/spec-code/spec-code.d.ts +18 -0
  65. package/dist/application-spec/generate-application/code/templates/spec-code/spec-code.js +41 -0
  66. package/dist/application-spec/generate-application/code/templates/spec-messages/index.d.ts +1 -0
  67. package/dist/application-spec/generate-application/code/templates/spec-messages/index.js +1 -0
  68. package/dist/application-spec/generate-application/code/templates/spec-messages/spec-messages.d.ts +20 -0
  69. package/dist/application-spec/generate-application/code/templates/spec-messages/spec-messages.js +6 -0
  70. package/dist/application-spec/generate-application/code/templates/story/phx-module.d.ts +8 -2
  71. package/dist/application-spec/generate-application/code/templates/story/phx-module.js +15 -3
  72. package/dist/application-spec/generate-application/code/templates/story/wrapper.d.ts +3 -0
  73. package/dist/application-spec/generate-application/code/templates/story/wrapper.js +2 -1
  74. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.d.ts +2 -1
  75. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.js +1 -1
  76. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.d.ts +2 -1
  77. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.js +1 -1
  78. package/dist/application-spec/generate-application/runtime/application-runtime.d.ts +8 -2
  79. package/dist/application-spec/generate-application/runtime/application-runtime.js +1 -1
  80. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.d.ts +2 -1
  81. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.js +1 -1
  82. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +2 -1
  83. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +1 -1
  84. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.d.ts +2 -1
  85. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.js +1 -1
  86. package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.d.ts +2 -1
  87. package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.js +1 -1
  88. package/dist/application-spec/generate-application/runtime/sections/component-optic.d.ts +3 -0
  89. package/dist/application-spec/generate-application/runtime/sections/component-optic.js +1 -1
  90. package/dist/application-spec/generate-application/runtime/sections/expression-runtime.d.ts +5 -3
  91. package/dist/application-spec/generate-application/runtime/sections/expression-runtime.js +1 -1
  92. package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.d.ts +9 -5
  93. package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.js +1 -1
  94. package/dist/application-spec/generate-application/runtime/sections/reflection-constants-runtime.d.ts +3 -1
  95. package/dist/application-spec/generate-application/runtime/sections/reflection-constants-runtime.js +1 -1
  96. package/dist/application-spec/generate-application/runtime/spec-code/index.d.ts +2 -0
  97. package/dist/application-spec/generate-application/runtime/spec-code/index.js +1 -0
  98. package/dist/application-spec/generate-application/runtime/spec-code/resolve-spec-code.d.ts +18 -0
  99. package/dist/application-spec/generate-application/runtime/spec-code/resolve-spec-code.js +1 -0
  100. package/dist/application-spec/generate-application/runtime/spec-code/spec-code-module.d.ts +17 -0
  101. package/dist/application-spec/generate-application/runtime/spec-code/spec-code-module.js +1 -0
  102. package/dist/application-spec/generate-application/runtime-spec/index.d.ts +1 -0
  103. package/dist/application-spec/generate-application/runtime-spec/index.js +1 -1
  104. package/dist/application-spec/generate-application/runtime-spec/spec-code-id.d.ts +40 -0
  105. package/dist/application-spec/generate-application/runtime-spec/spec-code-id.js +1 -0
  106. package/dist/application-spec/parse-application-files/categorize/categorize-files.js +1 -1
  107. package/dist/application-spec/parse-application-files/spec/expressions/expression-read-types.d.ts +9 -4
  108. package/dist/application-spec/parse-application-files/spec/expressions/expression-read-types.js +1 -1
  109. package/dist/application-spec/parse-application-files/spec/expressions/expression-value-type.d.ts +9 -0
  110. package/dist/application-spec/parse-application-files/spec/expressions/expression-value-type.js +1 -0
  111. package/dist/application-spec/parse-application-files/spec/expressions/expressions.js +1 -1
  112. package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.d.ts +3 -1
  113. package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.js +1 -1
  114. package/dist/configurator/application-runtime/extensions/systems/graphql-system/close-unclosed-braces.d.ts +12 -0
  115. package/dist/configurator/application-runtime/extensions/systems/graphql-system/close-unclosed-braces.js +1 -0
  116. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +1 -1
  117. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
  118. package/dist/configurator/common/configurator-interface.d.ts +11 -2
  119. package/dist/configurator/common/configurator-interface.js +1 -1
  120. package/dist/configurator/common/index.d.ts +2 -2
  121. package/dist/configurator/common/index.js +1 -1
  122. package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.d.ts +12 -0
  123. package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.js +1 -0
  124. package/dist/configurator/configuration-not-found-page/index.d.ts +1 -0
  125. package/dist/configurator/configuration-not-found-page/index.js +1 -0
  126. package/dist/configurator/configurator-context/PresetSelectionProvider.js +1 -1
  127. package/dist/configurator/pages/ConfiguratorNotFoundPage.js +1 -1
  128. package/dist/configurator/pages/DefaultNotAuthenticatedPage.d.ts +8 -0
  129. package/dist/configurator/pages/DefaultNotAuthenticatedPage.js +1 -0
  130. package/dist/configurator/pages/DefaultPresetUi.d.ts +7 -0
  131. package/dist/configurator/pages/DefaultPresetUi.js +1 -0
  132. package/dist/configurator/pages/NewConfiguratorPage.js +1 -1
  133. package/dist/configurator/pages/RootPage.js +1 -1
  134. package/dist/configurator/pages/default-configurator-elements.d.ts +4 -1
  135. package/dist/configurator/pages/default-configurator-elements.js +1 -1
  136. package/dist/configurator/pages/default-elements/built-in-entity-element.d.ts +1 -1
  137. package/dist/configurator/pages/default-elements/built-in-layout-element.d.ts +1 -1
  138. package/dist/configurator/preset-screen-elements/PresetDeviceNotSupportedElement.d.ts +4 -0
  139. package/dist/configurator/preset-screen-elements/PresetDeviceNotSupportedElement.js +1 -0
  140. package/dist/configurator/preset-screen-elements/PresetPageActionElement.d.ts +4 -0
  141. package/dist/configurator/preset-screen-elements/PresetPageActionElement.js +1 -0
  142. package/dist/configurator/preset-screen-elements/index.d.ts +2 -0
  143. package/dist/configurator/preset-screen-elements/index.js +1 -0
  144. package/dist/configurator/scene/scene-controls/DeleteEntityFab.js +1 -1
  145. package/dist/configurator/scene/scene-controls/SelectionHotspot.js +1 -1
  146. package/dist/configurator/ui/assistant/AssistantMarkdown.d.ts +1 -1
  147. package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -1
  148. package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.js +1 -1
  149. package/dist/configurator/ui/assistant/resource-image/AssistantImage.d.ts +9 -0
  150. package/dist/configurator/ui/assistant/resource-image/AssistantImage.js +1 -0
  151. package/dist/configurator/ui/assistant/resource-image/index.d.ts +3 -0
  152. package/dist/configurator/ui/assistant/resource-image/index.js +1 -0
  153. package/dist/configurator/ui/assistant/resource-image/resource-path.d.ts +11 -0
  154. package/dist/configurator/ui/assistant/resource-image/resource-path.js +1 -0
  155. package/dist/configurator/ui/assistant/resource-image/use-resource-image-url.d.ts +5 -0
  156. package/dist/configurator/ui/assistant/resource-image/use-resource-image-url.js +1 -0
  157. package/dist/configurator/ui/assistant/streamed-markdown.js +1 -1
  158. package/dist/configurator/ui/assistant/use-follow-stream.js +1 -1
  159. package/dist/configurator/ui/configurator-layout/slots/mobile-tabs/use-mobile-tabs.d.ts +1 -1
  160. package/dist/configurator/ui/configurator-layout/slots/mobile-tabs/use-mobile-tabs.js +1 -1
  161. package/dist/configurator/ui/configurator-layout/slots/services/InvariantViolations.js +1 -1
  162. package/dist/configurator/ui/configurator-layout/slots/services/Services.js +1 -1
  163. package/dist/configurator/ui/configurator-layout/slots/sidebar-tabs/use-sidebar-tabs.d.ts +1 -1
  164. package/dist/configurator/ui/configurator-layout/slots/sidebar-tabs/use-sidebar-tabs.js +1 -1
  165. package/dist/configurator/ui/legacy/layout/common/CommonFinalizeButton.js +1 -1
  166. package/dist/configurator/ui/legacy/layout/common/CommonSummaryButton.js +1 -1
  167. package/dist/configurator/ui/legacy/layout/common/CommonTopNavigation.js +1 -1
  168. package/dist/configurator/ui/legacy/layout/common/ResponsiveFinalizeButton.js +1 -1
  169. package/dist/configurator/ui/legacy/layout/common/SummaryReadLayout.js +1 -1
  170. package/dist/configurator/ui/legacy/layout/simple/SimpleOwnerLayout.js +1 -1
  171. package/dist/configurator/ui/legacy/layout/simple/SimpleTopNavigation.js +1 -1
  172. package/dist/configurator/ui/legacy/layout/steps/StepsNavigation.js +1 -1
  173. package/dist/configurator/ui/legacy/layout/steps/StepsOwnerLayout.js +1 -1
  174. package/dist/configurator/ui/legacy/layout/steps/StepsSidebarFooter.js +1 -1
  175. package/dist/configurator/ui/legacy/layout/tabs/TabsOwnerLayout.js +1 -1
  176. package/dist/configurator/ui/legacy/toolbar/common/DimensionUnitSelector.js +1 -1
  177. package/dist/configurator/ui/legacy/toolbar/common/MeasurementsIndicatorsSwitch.js +1 -1
  178. package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/FullscreenToolbarAction.js +1 -1
  179. package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/HelpToolbarAction.js +1 -1
  180. package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/MeasurementsToolbarAction.js +1 -1
  181. package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/UndoRedoToolbarGroup.js +1 -1
  182. package/dist/configurator/ui/phoenix-layout-elements/EnterFullscreenToolbarButton.js +1 -1
  183. package/dist/configurator/ui/phoenix-layout-elements/ExitFullscreenToolbarButton.js +1 -1
  184. package/dist/configurator/ui/phoenix-layout-elements/HelpToolbarButton.js +1 -1
  185. package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomOwnerUi.d.ts +1 -0
  186. package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomOwnerUi.js +1 -1
  187. package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomReadUi.d.ts +1 -0
  188. package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomReadUi.js +1 -1
  189. package/dist/configurator/ui/phoenix-layout-elements/MeasurementsToolbarButton.js +1 -1
  190. package/dist/configurator/ui/phoenix-layout-elements/RedoToolbarButton.js +1 -1
  191. package/dist/configurator/ui/phoenix-layout-elements/UndoToolbarButton.js +1 -1
  192. package/dist/configurator/ui/phoenix-layout-elements/header/common/back-button/use-go-back-action.js +1 -1
  193. package/dist/configurator/ui/phoenix-layout-elements/header/common/horizontal-tabs/DesktopHorizontalTabs.js +1 -1
  194. package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/StepsNavigation.js +1 -1
  195. package/dist/configurator/ui/phoenix-layout-elements/measurements/DimensionUnitSelector.js +1 -1
  196. package/dist/configurator/ui/phoenix-layout-elements/measurements/MeasurementsIndicatorsSwitch.js +1 -1
  197. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/basic/BasicFinalizeButton.js +1 -1
  198. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/CustomFinalizeButton.js +1 -1
  199. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/EnterSummaryButton.js +1 -1
  200. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/NextStepButton.js +1 -1
  201. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/PreviousStepButton.js +1 -1
  202. package/dist/configurator/ui/preset-layout/CategoryPresetUi.js +1 -1
  203. package/dist/configurator/ui/preset-layout/PresetUi.d.ts +4 -0
  204. package/dist/configurator/ui/preset-layout/PresetUi.js +1 -1
  205. package/dist/configurator/ui/save-message-snackbars/SaveMessageSnackbars.d.ts +10 -0
  206. package/dist/configurator/ui/save-message-snackbars/SaveMessageSnackbars.js +1 -0
  207. package/dist/configurator/ui/save-message-snackbars/index.d.ts +1 -0
  208. package/dist/configurator/ui/save-message-snackbars/index.js +1 -0
  209. package/dist/docs/index.json +1 -1
  210. package/dist/docs/layouts/custom-layout.mdx +8 -7
  211. package/dist/docs/other-features/not-found-404.mdx +1 -1
  212. package/dist/docs/phoenix/clerk-sign-in.mdx +1 -1
  213. package/dist/docs/phoenix/configurator-interface.mdx +51 -22
  214. package/dist/docs/phoenix/getting-started.mdx +5 -3
  215. package/dist/docs/phoenix/layout-slots.mdx +19 -19
  216. package/dist/docs/phoenix/localization.mdx +2 -0
  217. package/dist/docs/phoenix/migration-guide.mdx +3 -3
  218. package/dist/docs/phoenix/phoenix-cli.mdx +22 -11
  219. package/dist/docs/phoenix/project-structure.mdx +45 -27
  220. package/dist/docs/phoenix/quickstart.mdx +7 -7
  221. package/dist/docs/phoenix/sdk-builder-workflow.mdx +6 -6
  222. package/dist/docs/phoenix/testing.mdx +8 -0
  223. package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -1
  224. package/dist/domain/domain-runtime/spec/module-checks/element-registration.d.ts +12 -11
  225. package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +1 -1
  226. package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +1 -1
  227. package/dist/domain/domain-runtime/spec/module-checks/index.js +1 -1
  228. package/dist/domain/domain-runtime/spec/selectors.js +1 -1
  229. package/dist/editor/Editor.d.ts +7 -0
  230. package/dist/editor/Editor.js +1 -1
  231. package/dist/editor/mocks/CreateMockYaml.js +2 -2
  232. package/dist/editor/mocks/EditorMockContext.d.ts +2 -0
  233. package/dist/editor/mocks/EditorMockContext.js +1 -1
  234. package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
  235. package/dist/legacy/domain-cli/utils/cache.js +1 -1
  236. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-admin.js +10 -11
  237. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-embedded-admin.js +9 -10
  238. package/dist/phoenix/application-spec/application-spec-builder.d.ts +0 -2
  239. package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
  240. package/dist/phoenix/application-spec/generate-application-spec-project.d.ts +0 -2
  241. package/dist/phoenix/application-spec/generate-application-spec-project.js +2 -2
  242. package/dist/phoenix/application-spec/generate-project-files.d.ts +0 -2
  243. package/dist/phoenix/cli.js +1 -1
  244. package/dist/phoenix/commands/build.js +1 -1
  245. package/dist/phoenix/commands/clean.js +1 -1
  246. package/dist/phoenix/commands/deploy.js +2 -2
  247. package/dist/phoenix/commands/deps/deps.d.ts +22 -1
  248. package/dist/phoenix/commands/deps/deps.js +2 -2
  249. package/dist/phoenix/commands/deps/index.d.ts +1 -1
  250. package/dist/phoenix/commands/deps/index.js +1 -1
  251. package/dist/phoenix/commands/deps/sdk-manifest.js +1 -1
  252. package/dist/phoenix/commands/dev.js +4 -4
  253. package/dist/phoenix/commands/init.d.ts +17 -5
  254. package/dist/phoenix/commands/init.js +1 -1
  255. package/dist/phoenix/commands/link.js +1 -1
  256. package/dist/phoenix/commands/sync.js +1 -1
  257. package/dist/phoenix/commands/unlink.js +1 -1
  258. package/dist/phoenix/services/application-spec-project-watcher.d.ts +0 -2
  259. package/dist/phoenix/services/application-spec-project-watcher.js +2 -2
  260. package/dist/phoenix/utils/index.d.ts +2 -1
  261. package/dist/phoenix/utils/index.js +1 -1
  262. package/dist/phoenix/utils/package-info.d.ts +0 -9
  263. package/dist/phoenix/utils/package-info.js +1 -1
  264. package/dist/phoenix/utils/package-manager/index.d.ts +2 -0
  265. package/dist/phoenix/utils/package-manager/index.js +1 -0
  266. package/dist/phoenix/utils/package-manager/package-manager-commands.d.ts +19 -0
  267. package/dist/phoenix/utils/package-manager/package-manager-commands.js +1 -0
  268. package/dist/phoenix/utils/package-manager/pnpm-project.d.ts +9 -0
  269. package/dist/phoenix/utils/package-manager/pnpm-project.js +1 -0
  270. package/dist/react-sdk/application-runtime/common-services/analytics/index.d.ts +1 -0
  271. package/dist/react-sdk/application-runtime/common-services/analytics/index.js +1 -1
  272. package/dist/react-sdk/domain-application-context/DomainLifecycleBoundary.js +1 -1
  273. package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.d.ts +15 -4
  274. package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.js +1 -1
  275. package/dist/react-sdk/domain-context/index.d.ts +2 -0
  276. package/dist/react-sdk/domain-context/index.js +1 -1
  277. package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.d.ts +61 -0
  278. package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.js +1 -0
  279. package/dist/react-sdk/domain-context/system-hooks/undo-redo-hooks.d.ts +20 -0
  280. package/dist/react-sdk/domain-context/system-hooks/undo-redo-hooks.js +1 -0
  281. package/dist/react-sdk/handlers/index.d.ts +1 -0
  282. package/dist/react-sdk/handlers/index.js +1 -1
  283. package/dist/react-sdk/handlers/use-finalize-action.js +1 -0
  284. package/dist/react-sdk/session/NotAuthenticatedPage.d.ts +4 -0
  285. package/dist/react-sdk/session/NotAuthenticatedPage.js +1 -1
  286. package/dist/ui-sdk/theme/types.d.ts +1 -1
  287. package/package.json +3 -3
  288. package/dist/application-spec/generate-application/code/templates/nvmrc.d.ts +0 -1
  289. package/dist/application-spec/generate-application/code/templates/nvmrc.js +0 -2
  290. package/dist/application-spec/generate-application/code/templates/package-json.d.ts +0 -10
  291. package/dist/application-spec/generate-application/code/templates/package-json.js +0 -1
  292. package/dist/configurator/common/use-finalize-action.js +0 -1
  293. package/dist/phoenix/commands/generate-project-files.d.ts +0 -6
  294. package/dist/phoenix/commands/generate-project-files.js +0 -1
  295. /package/dist/{configurator/common → react-sdk/application-runtime/common-services/analytics}/frontend-analytics.d.ts +0 -0
  296. /package/dist/{configurator/common → react-sdk/application-runtime/common-services/analytics}/frontend-analytics.js +0 -0
  297. /package/dist/{configurator/common → react-sdk/handlers}/use-finalize-action.d.ts +0 -0
@@ -1 +1 @@
1
- import{jsx as t}from"react/jsx-runtime";import{Trans as o}from"@lingui/react";import{useCallback as i}from"react";import{useAnalyticsService as e,useResolveLocalizable as r}from"#package/react-sdk";import{PhxIcon as a,ResponsiveSidebarFooterButton as s}from"#package/ui-sdk";import{FrontendAnalyticsFactory as n}from"../../../../common/index.js";export const CustomFinalizeButton=m=>{let{busy:l,disabled:p,hints:c,icon:u,onClick:d,title:f}=m,k=r(),g=e();return t(s,{tooltip:c,busy:l,disabled:p,onClick:i(t=>{t.preventDefault(),t.stopPropagation(),g.push(n.FinalizeButtonClicked()),d?.()},[g,d]),slotProps:{button:{startIcon:u?t(a,{name:u}):void 0}},children:f?k(f):t(o,{id:"sdk.layout.sidebar.finalizePage.label",message:"Finalize"})})};
1
+ import{jsx as t}from"react/jsx-runtime";import{Trans as o}from"@lingui/react";import{useCallback as a}from"react";import{FrontendAnalyticsFactory as e,useAnalyticsService as i,useResolveLocalizable as r}from"#package/react-sdk";import{PhxIcon as s,ResponsiveSidebarFooterButton as n}from"#package/ui-sdk";export const CustomFinalizeButton=l=>{let{busy:m,disabled:p,hints:u,icon:c,onClick:d,title:f}=l,k=r(),g=i();return t(n,{tooltip:u,busy:m,disabled:p,onClick:a(t=>{t.preventDefault(),t.stopPropagation(),g.push(e.FinalizeButtonClicked()),d?.()},[g,d]),slotProps:{button:{startIcon:c?t(s,{name:c}):void 0}},children:f?k(f):t(o,{id:"sdk.layout.sidebar.finalizePage.label",message:"Finalize"})})};
@@ -1 +1 @@
1
- import{jsx as r}from"react/jsx-runtime";import{Trans as m}from"@lingui/react";import{memo as o,useCallback as t}from"react";import{createPhoenixLayoutElement as a,enterSummaryButtonSchema as e}from"#package/domain";import{useAnalyticsService as i}from"#package/react-sdk";import{ResponsiveSidebarFooterButton as n}from"#package/ui-sdk";import{FrontendAnalyticsFactory as c}from"../../../../common/index.js";import{useEnterSidebarSummary as s}from"../../../../configurator-context/index.js";export const EnterSummaryButton=a({schema:e,component:o(()=>{let o=i(),a=s();return r(n,{onClick:t(()=>{o.push(c.SummaryButtonClicked()),a()},[o,a]),children:r(m,{id:"sdk.layout.summary.button.label",message:"Summary"})})})});
1
+ import{jsx as r}from"react/jsx-runtime";import{Trans as t}from"@lingui/react";import{memo as m,useCallback as o}from"react";import{createPhoenixLayoutElement as a,enterSummaryButtonSchema as e}from"#package/domain";import{FrontendAnalyticsFactory as i,useAnalyticsService as n}from"#package/react-sdk";import{ResponsiveSidebarFooterButton as c}from"#package/ui-sdk";import{useEnterSidebarSummary as s}from"../../../../configurator-context/index.js";export const EnterSummaryButton=a({schema:e,component:m(()=>{let m=n(),a=s();return r(c,{onClick:o(()=>{m.push(i.SummaryButtonClicked()),a()},[m,a]),children:r(t,{id:"sdk.layout.summary.button.label",message:"Summary"})})})});
@@ -1 +1 @@
1
- import{jsx as t}from"react/jsx-runtime";import{Trans as e}from"@lingui/react";import{Button as a}from"@mui/material";import{memo as i,useCallback as o}from"react";import{createPhoenixLayoutElement as r,nextStepButtonSchema as n}from"#package/domain";import{useAnalyticsService as m,useDomain as s}from"#package/react-sdk";import{useResponsive as l}from"#package/ui-sdk";import{useConfigurator as c}from"../../../../application-runtime/index.js";import{FrontendAnalyticsFactory as p}from"../../../../common/index.js";export const NextStepButton=r({schema:n,component:i(()=>{let i=m(),{layoutSystem:r}=c(),{navigationSystem:n,storeSystem:d}=s(),{isDesktop:g}=l(),u=o(()=>{let t=n.getActiveNavigationState(d.stateSlices.committed.value.state),e=r.getNavigationSteps(d.stateSlices.committed.value.state);i.push(p.NextStepButtonClicked());let a=e.findIndex(e=>e.navigationStates.includes(t)),o=e[a+1];if(!o)return;let m=o.navigationStates[0];m&&d.stageAndCommit([n.getNavigationUpdate(m)])},[i,n,d,r]);return g?t(a,{variant:"contained",color:"primary",size:"large",onClick:u,children:t(e,{id:"sdk.layout.sidebar.nextStep.label",message:"Next step"})}):t(a,{variant:"contained",color:"primary",size:"small",onClick:u,children:t(e,{id:"sdk.layout.sidebar.nextStepShort.label",message:"Next"})})})});
1
+ import{jsx as t}from"react/jsx-runtime";import{Trans as e}from"@lingui/react";import{Button as a}from"@mui/material";import{memo as i,useCallback as o}from"react";import{createPhoenixLayoutElement as r,nextStepButtonSchema as n}from"#package/domain";import{FrontendAnalyticsFactory as m,useAnalyticsService as s,useDomain as l}from"#package/react-sdk";import{useResponsive as c}from"#package/ui-sdk";import{useConfigurator as p}from"../../../../application-runtime/index.js";export const NextStepButton=r({schema:n,component:i(()=>{let i=s(),{layoutSystem:r}=p(),{navigationSystem:n,storeSystem:d}=l(),{isDesktop:g}=c(),u=o(()=>{let t=n.getActiveNavigationState(d.stateSlices.committed.value.state),e=r.getNavigationSteps(d.stateSlices.committed.value.state);i.push(m.NextStepButtonClicked());let a=e.findIndex(e=>e.navigationStates.includes(t)),o=e[a+1];if(!o)return;let s=o.navigationStates[0];s&&d.stageAndCommit([n.getNavigationUpdate(s)])},[i,n,d,r]);return g?t(a,{variant:"contained",color:"primary",size:"large",onClick:u,children:t(e,{id:"sdk.layout.sidebar.nextStep.label",message:"Next step"})}):t(a,{variant:"contained",color:"primary",size:"small",onClick:u,children:t(e,{id:"sdk.layout.sidebar.nextStepShort.label",message:"Next"})})})});
@@ -1 +1 @@
1
- import{jsx as t}from"react/jsx-runtime";import{IconButton as e,Tooltip as i}from"@mui/material";import{memo as a,useCallback as o}from"react";import{createPhoenixLayoutElement as r,previousStepButtonSchema as m}from"#package/domain";import{useAnalyticsService as n,useDomain as s,useLocalizable as l}from"#package/react-sdk";import{PhxIcon as c,useResponsive as p}from"#package/ui-sdk";import{useConfigurator as d}from"../../../../application-runtime/index.js";import{FrontendAnalyticsFactory as u}from"../../../../common/index.js";import{useBackToPresetEnabled as g,useConfiguratorRouting as f,useSteps as v}from"../../../../configurator-context/index.js";let S={id:"sdk.layout.sidebar.previousStep.label",message:"Previous step"};export const PreviousStepButton=r({schema:m,component:a(a=>{let{icon:r}=a,m=l(S),x=n(),k=g(),{layoutSystem:h}=d(),j=f(),{navigationSystem:C,storeSystem:b}=s(),{isDesktop:N}=p(),{previousStepDisabled:P}=v();return t(i,{title:m,placement:"top",children:t("span",{style:{alignSelf:"center"},children:t(e,{variant:"muted",color:"default",size:N?"medium":"small",disabled:P,onClick:o(()=>{let t=C.getActiveNavigationState(b.stateSlices.committed.value.state),e=h.getNavigationSteps(b.stateSlices.committed.value.state);x.push(u.PreviousStepButtonClicked());let i=e.findIndex(e=>e.navigationStates.includes(t)),a=e[i-1];if(!a){k&&j.goToIndex();return}let o=a.navigationStates[0];o&&b.stageAndCommit([C.getNavigationUpdate(o)])},[x,C,k,j,b,h]),children:t(c,{name:r??"CaretLeft"})})})})})});
1
+ import{jsx as t}from"react/jsx-runtime";import{IconButton as e,Tooltip as i}from"@mui/material";import{memo as a,useCallback as o}from"react";import{createPhoenixLayoutElement as r,previousStepButtonSchema as n}from"#package/domain";import{FrontendAnalyticsFactory as m,useAnalyticsService as s,useDomain as l,useLocalizable as c}from"#package/react-sdk";import{PhxIcon as p,useResponsive as d}from"#package/ui-sdk";import{useConfigurator as u}from"../../../../application-runtime/index.js";import{useBackToPresetEnabled as g,useConfiguratorRouting as f,useSteps as v}from"../../../../configurator-context/index.js";let S={id:"sdk.layout.sidebar.previousStep.label",message:"Previous step"};export const PreviousStepButton=r({schema:n,component:a(a=>{let{icon:r}=a,n=c(S),k=s(),x=g(),{layoutSystem:h}=u(),j=f(),{navigationSystem:C,storeSystem:b}=l(),{isDesktop:N}=d(),{previousStepDisabled:P}=v();return t(i,{title:n,placement:"top",children:t("span",{style:{alignSelf:"center"},children:t(e,{variant:"muted",color:"default",size:N?"medium":"small",disabled:P,onClick:o(()=>{let t=C.getActiveNavigationState(b.stateSlices.committed.value.state),e=h.getNavigationSteps(b.stateSlices.committed.value.state);k.push(m.PreviousStepButtonClicked());let i=e.findIndex(e=>e.navigationStates.includes(t)),a=e[i-1];if(!a){x&&j.goToIndex();return}let o=a.navigationStates[0];o&&b.stageAndCommit([C.getNavigationUpdate(o)])},[k,C,x,j,b,h]),children:t(p,{name:r??"CaretLeft"})})})})})});
@@ -1 +1 @@
1
- import{jsx as e,jsxs as t,Fragment as i}from"react/jsx-runtime";import{useCallback as r,useState as s}from"react";import{ConfigurationNotFoundPage as l,useResolveLocalizable as o,useResourceUrl as n}from"#package/react-sdk";import{Preset as a}from"#package/ui-sdk";import{usePresetSelection as d,usePreview as m}from"../../configurator-context/index.js";import{ClosePresetScreenButton as c}from"./ClosePresetScreenButton.js";import{PresetBackButton as g}from"./PresetBackButton.js";import{PresetPreviewLayout as p}from"./PresetPreviewLayout.js";import{PresetSnackbarsRoot as u}from"./PresetSnackbarsRoot.js";let f={id:"sdk.presets.select.button.label",message:"Select"},k={id:"sdk.presets.detail.close.label",message:"Close"},S={id:"sdk.presets.detail.learnMore.label",message:"Learn more"},h={id:"sdk.presets.detail.select.label",message:"Select"},B=t=>{let{id:i,name:s,description:l,img:m,imgAlt:c,detailTitle:g,detailDescription:p}=t,{selectPreset:u,creatingPresetId:B}=d(),b=o(),{getResourceUrl:C}=n(),T=r(()=>{u(i)},[u,i]),y=(m&&C(m))??"",A=B===i;return e(a.Card,{title:b(s),description:b(l),imgSrc:y,imgAlt:b(c),selectButtonTitle:b(f),loading:A,disabled:null!==B&&!A,onSelect:T,additionalInfoButton:void 0!==g&&void 0!==p&&e(a.DetailDialog,{img:y,imgAlt:b(c),detailTitle:b(g),detailDescription:b(p),closeButtonTitle:b(k),learnMoreLinkTitle:b(S),selectButtonTitle:b(h),onSelect:T})},b(s))},b=t=>{let{id:i,name:s,description:l,img:d,imgAlt:m,onSelect:c}=t,g=o(),{getResourceUrl:p}=n(),u=r(()=>{c(i)},[c,i]);return e(a.Card,{title:g(s),description:g(l),imgSrc:(d&&p(d))??"",imgAlt:g(m),selectButtonTitle:g(f),onSelect:u},g(s))},C={id:"sdk.presets.category.count.message",message:"{presetCount, plural, one {# preset} other {# presets}}"},T=t=>{let{selectedCategory:r,onCategorySelected:s}=t,{presetCategories:l}=d(),n=o();if(1===l.length)return e(i,{children:l[0].presets.map(t=>e(B,{id:t.id,name:t.name,description:t.description,img:t.img,imgAlt:t.imgAlt,detailTitle:t.detailTitle,detailDescription:t.detailDescription},t.id))});if(null!==r){let t=l.find(e=>e.id===r);if(void 0===t)throw Error(`Category with id ${r} not found!`);return e(i,{children:t.presets.map(t=>e(B,{id:t.id,name:t.name,description:t.description,img:t.img,imgAlt:t.imgAlt,detailTitle:t.detailTitle,detailDescription:t.detailDescription},t.id))})}return e(i,{children:l.map(t=>e(b,{id:t.id,name:t.name,description:t.description??n({...C,values:{presetCount:t.presets.length}}),img:t.img,imgAlt:t.imgAlt,onSelect:s},t.id))})},y={id:"sdk.presets.category.heading.title",message:"Select starting preset"},A={id:"sdk.presets.preview.title",message:"Customize product"},j={id:"sdk.presets.start.button.label",message:"Start configuring"};export const CategoryPresetUi=n=>{let{action:f,footer:k,overlay:S}=n,[h,B]=s(null),{isPreview:b}=m(),C=o(),v=r(()=>{B(null)},[]),{presetCategories:D}=d();return b?e(p,{title:C(A),startButtonLabel:C(j)}):0===D.length?e(l,{}):t(i,{children:[e(a.ScreenLayout,{heading:C(y),action:f,footer:k,overlay:S,backButton:h&&e(g,{onBack:v}),closeButton:!h&&e(c,{}),children:e(T,{selectedCategory:h,onCategorySelected:B})}),e(u,{})]})};
1
+ import{jsx as e,jsxs as t,Fragment as i}from"react/jsx-runtime";import{useCallback as r,useState as s}from"react";import{useResolveLocalizable as o,useResourceUrl as l}from"#package/react-sdk";import{Preset as n}from"#package/ui-sdk";import{ConfigurationNotFoundPageElement as a}from"../../configuration-not-found-page/index.js";import{usePresetSelection as d,usePreview as m}from"../../configurator-context/index.js";import{ClosePresetScreenButton as c}from"./ClosePresetScreenButton.js";import{PresetBackButton as g}from"./PresetBackButton.js";import{PresetPreviewLayout as p}from"./PresetPreviewLayout.js";import{PresetSnackbarsRoot as u}from"./PresetSnackbarsRoot.js";let f={id:"sdk.presets.select.button.label",message:"Select"},k={id:"sdk.presets.detail.close.label",message:"Close"},S={id:"sdk.presets.detail.learnMore.label",message:"Learn more"},h={id:"sdk.presets.detail.select.label",message:"Select"},B=t=>{let{id:i,name:s,description:a,img:m,imgAlt:c,detailTitle:g,detailDescription:p}=t,{selectPreset:u,creatingPresetId:B}=d(),b=o(),{getResourceUrl:C}=l(),T=r(()=>{u(i)},[u,i]),j=(m&&C(m))??"",y=B===i;return e(n.Card,{title:b(s),description:b(a),imgSrc:j,imgAlt:b(c),selectButtonTitle:b(f),loading:y,disabled:null!==B&&!y,onSelect:T,additionalInfoButton:void 0!==g&&void 0!==p&&e(n.DetailDialog,{img:j,imgAlt:b(c),detailTitle:b(g),detailDescription:b(p),closeButtonTitle:b(k),learnMoreLinkTitle:b(S),selectButtonTitle:b(h),onSelect:T})},b(s))},b=t=>{let{id:i,name:s,description:a,img:d,imgAlt:m,onSelect:c}=t,g=o(),{getResourceUrl:p}=l(),u=r(()=>{c(i)},[c,i]);return e(n.Card,{title:g(s),description:g(a),imgSrc:(d&&p(d))??"",imgAlt:g(m),selectButtonTitle:g(f),onSelect:u},g(s))},C={id:"sdk.presets.category.count.message",message:"{presetCount, plural, one {# preset} other {# presets}}"},T=t=>{let{selectedCategory:r,onCategorySelected:s}=t,{presetCategories:l}=d(),n=o();if(1===l.length)return e(i,{children:l[0].presets.map(t=>e(B,{id:t.id,name:t.name,description:t.description,img:t.img,imgAlt:t.imgAlt,detailTitle:t.detailTitle,detailDescription:t.detailDescription},t.id))});if(null!==r){let t=l.find(e=>e.id===r);if(void 0===t)throw Error(`Category with id ${r} not found!`);return e(i,{children:t.presets.map(t=>e(B,{id:t.id,name:t.name,description:t.description,img:t.img,imgAlt:t.imgAlt,detailTitle:t.detailTitle,detailDescription:t.detailDescription},t.id))})}return e(i,{children:l.map(t=>e(b,{id:t.id,name:t.name,description:t.description??n({...C,values:{presetCount:t.presets.length}}),img:t.img,imgAlt:t.imgAlt,onSelect:s},t.id))})},j={id:"sdk.presets.category.heading.title",message:"Select starting preset"},y={id:"sdk.presets.preview.title",message:"Customize product"},A={id:"sdk.presets.start.button.label",message:"Start configuring"};export const CategoryPresetUi=l=>{let{action:f,footer:k,overlay:S}=l,[h,B]=s(null),{isPreview:b}=m(),C=o(),v=r(()=>{B(null)},[]),{presetCategories:x}=d();return b?e(p,{title:C(y),startButtonLabel:C(A)}):0===x.length?e(a,{}):t(i,{children:[e(n.ScreenLayout,{heading:C(j),action:f,footer:k,overlay:S,backButton:h&&e(g,{onBack:v}),closeButton:!h&&e(c,{}),children:e(T,{selectedCategory:h,onCategorySelected:B})}),e(u,{})]})};
@@ -5,4 +5,8 @@ export type CreatePresetPageAction = (Element: () => JSX.Element) => PhoenixLayo
5
5
  export declare const createPresetPageAction: (Element: () => JSX.Element) => PhoenixLayoutElementRegistration;
6
6
  export declare const createPresetDeviceNotSupported: (Element: () => JSX.Element) => PhoenixLayoutElementRegistration;
7
7
  export type CreatePresetDeviceNotSupported = (Element: () => JSX.Element) => PhoenixLayoutElementRegistration;
8
+ /**
9
+ * Renders the `PresetUi` element. Its default, the built-in preset screen, comes from
10
+ * `defaultConfiguratorElements`; a project element registered under the name replaces it.
11
+ */
8
12
  export declare const PresetUi: () => import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- import{jsx as e}from"react/jsx-runtime";import{createCustomPhoenixLayoutElement as t}from"#package/domain";import{PhoenixLayoutElement as r}from"#package/react-sdk";import{CategoryPresetUi as o}from"./CategoryPresetUi.js";let a="PresetUi";export const createPresetUi=e=>t(a,e);let s="PresetPageAction";export const createPresetPageAction=e=>t(s,e);let l="PresetDeviceNotSupported";export const createPresetDeviceNotSupported=e=>t(l,e);let n=()=>e(r,{name:s,elementProps:{},defaultSlot:()=>null}),p=()=>e(r,{name:l,elementProps:{},defaultSlot:()=>null}),c=()=>e(o,{action:e(n,{}),overlay:e(p,{})});export const PresetUi=()=>e(r,{name:a,elementProps:{},defaultSlot:c});
1
+ import{jsx as e}from"react/jsx-runtime";import{createCustomPhoenixLayoutElement as t}from"#package/domain";import{PhoenixLayoutElement as r}from"#package/react-sdk";import{PresetDeviceNotSupportedName as o,PresetPageActionName as s}from"../../preset-screen-elements/index.js";let c="PresetUi";export const createPresetUi=e=>t(c,e);export const createPresetPageAction=e=>t(s,e);export const createPresetDeviceNotSupported=e=>t(o,e);export const PresetUi=()=>e(r,{name:c,elementProps:{}});
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shows the save messages of `useConfiguratorMessages` as snackbars: a `saveError` as an error snackbar that stays
3
+ * until it is dismissed or a later save succeeds, and a `saveRecovery` as an informational one. Mounted wherever a
4
+ * layout mounts its message services: `LayoutSlots.Services`, the legacy layouts' overlays and the `custom` owner UI.
5
+ * In read mode, from the access or the web component's `readonly`, nothing is saved: an error snackbar left from
6
+ * owner mode is closed and no message is shown. The read UIs that replace an owner UI in read mode
7
+ * (`SummaryReadLayout`, the `custom` read UI) mount it too, since the owner UI's instance unmounts before it can close
8
+ * the snackbar.
9
+ */
10
+ export declare const SaveMessageSnackbars: () => null;
@@ -0,0 +1 @@
1
+ import{jsx as r}from"react/jsx-runtime";import{Trans as e}from"@lingui/react";import{Button as s}from"@mui/material";import{useSnackbar as i}from"notistack";import{useEffect as t}from"react";import{useApplicationRuntime as o,useApplicationRuntimeSelector as a,useConfiguratorMessages as n}from"#package/react-sdk";let m="phx-configuration-persist-system-failure";export const SaveMessageSnackbars=()=>{let c=a(o().selectors.domainContext.configurator.getInitializationState).isViewMode,{enqueueSnackbar:d,closeSnackbar:p}=i();return t(()=>{c&&p(m)},[c,p]),n(i=>{c||("saveError"===i.kind&&d(r(e,{id:"sdk.persist.system.error.message",message:"An error occured when trying to update a configuration"}),{variant:"error",key:m,persist:!0,preventDuplicate:!0,action:i=>r(s,{color:"inherit",size:"small",onClick:()=>{p(i)},children:r(e,{id:"sdk.persist.system.error.dismiss",message:"Dismiss"})})}),"saveRecovery"===i.kind&&(p(m),d(r(e,{id:"sdk.persist.system.error.recovered",message:"Configuration updated"}),{variant:"info"})))}),null};
@@ -0,0 +1 @@
1
+ export { SaveMessageSnackbars } from './SaveMessageSnackbars.js';
@@ -0,0 +1 @@
1
+ export{SaveMessageSnackbars}from"./SaveMessageSnackbars.js";
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-15T11:03:16.283Z",
2
+ "generatedAt": "2026-10-15T16:00:13.549Z",
3
3
  "categories": [
4
4
  {
5
5
  "name": "Phoenix",
@@ -335,6 +335,7 @@ export const ProductPanel = ({ collapsed = false, inSheet = false }: ProductPane
335
335
  | `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | Read the fullscreen flag, and set it from the layout's own button. |
336
336
  | `phx.frontend.useActiveViewState()` and `phx.frontend.useSetViewState()` | Read the active view state, one of the spec's `viewStates`, to mark the active view button, and make another one active; see [Control the scene](#control-the-scene). |
337
337
  | `phx.frontend.useResetCamera()` | Move the camera back to the framing of the active camera, undoing the customer's orbit, pan and zoom. |
338
+ | `phx.frontend.useUndoRedo()` | Not used here: whether there is a change to undo or redo, and `undo` and `redo`, for a layout's own undo and redo buttons; see [Known gaps](#known-gaps). |
338
339
  | `useActiveViewports()` from `@salsita-ai/sdk/react` | The width classes of the configurator's container, or `null` before it is measured; the layout renders its phone arrangement unless it includes `desktop`, see [Fit a phone](#fit-a-phone). |
339
340
  | `phx.frontend.useConfiguratorMessages(onMessage)` | Receive violations, side-effect hints and errors one by one, with their text resolved, for the layout's own toast; see [Show messages](#show-messages). |
340
341
  | `useResolveLocalizable()` from `@salsita-ai/sdk/react` | Turns the localizable texts of the spec into strings in the current language. [Localization](?path=/docs/phoenix-localization--docs) describes it and `LocalizableMessage`. |
@@ -540,7 +541,7 @@ The button stays in the free area in both states, so the way back is always wher
540
541
 
541
542
  ## Show messages
542
543
 
543
- When a change is rejected because it breaks an invariant, when a committed change carries a hint, or when an error is thrown while a change is validated, the configurator has a message for the customer. `phx.frontend.useConfiguratorMessages(onMessage)` calls `onMessage` once for each, with the text already in the customer's language, and the layout shows it its own way. Here a toast shows a message for six seconds, colored by its severity, at the bottom left of the free area above the view controls on a desktop, and at its top on a phone:
544
+ When a change is rejected because it breaks an invariant, when a committed change carries a hint, when an error is thrown while a change is validated, or when saving the configuration fails and later succeeds again, the configurator has a message for the customer. `phx.frontend.useConfiguratorMessages(onMessage)` calls `onMessage` once for each, with the text already in the customer's language, and the layout shows it its own way. Here a toast shows a message for six seconds, colored by its severity, at the bottom left of the free area above the view controls on a desktop, and at its top on a phone:
544
545
 
545
546
  ```tsx
546
547
  import { Alert } from '@mui/material'
@@ -559,7 +560,7 @@ const severityOf = (message: ConfiguratorMessage) => {
559
560
  return VIOLATION_SEVERITY[message.violation.severity]
560
561
  }
561
562
 
562
- return message.kind === 'error' ? 'error' : 'info'
563
+ return message.kind === 'error' || message.kind === 'saveError' ? 'error' : 'info'
563
564
  }
564
565
 
565
566
  export const MessageToast = () => {
@@ -596,7 +597,7 @@ export const MessageToast = () => {
596
597
  }
597
598
  ```
598
599
 
599
- The hook takes a new function on every render without missing a message, so passing the state setter, or an inline function, is fine. `message.kind` is `'violation'`, `'sideEffect'` or `'error'`; a violation also carries `entityId` and the `violation` itself, with its `severity`. A rejected change brings one message for each violation of the rejected state, including notices and warnings that were already present, so the toast keeps the most severe message while it is shown instead of letting a later notice replace the reason for the rejection. The layout mounts the toast in the safe-area element, which sets `pointerEvents: 'none'`, so the toast's wrapper switches pointer events back on for the close button.
600
+ The hook takes a new function on every render without missing a message, so passing the state setter, or an inline function, is fine. `message.kind` is `'violation'`, `'sideEffect'`, `'error'`, `'saveError'` or `'saveRecovery'`; the toast shows an error and a failed save as errors, a hint and the save that succeeded again as information. A violation also carries `entityId` and the `violation` itself, with its `severity`. A rejected change brings one message for each violation of the rejected state, including notices and warnings that were already present, so the toast keeps the most severe message while it is shown instead of letting a later notice replace the reason for the rejection. The layout mounts the toast in the safe-area element, which sets `pointerEvents: 'none'`, so the toast's wrapper switches pointer events back on for the close button.
600
601
 
601
602
  To give the toast something to show, the spec registers one transformer. Choosing **Tree trunk** keeps the change and adds a hint to it, which arrives as a `sideEffect` message:
602
603
 
@@ -619,7 +620,7 @@ export default phx.frontend.transformerBuilder.when
619
620
  .build('tree-trunk-delivery')
620
621
  ```
621
622
 
622
- [Configurator Interface → Loaded Configuration UI](?path=/docs/phoenix-configurator-interface--docs#loaded-configuration-ui) lists the messages the hook does not deliver: a failure to save, errors of the built-in finalize and other actions, and preset errors stay snackbars.
623
+ [Configurator Interface → Loaded Configuration UI](?path=/docs/phoenix-configurator-interface--docs#loaded-configuration-ui) lists the messages the hook does not deliver: errors of the built-in finalize and other actions stay snackbars, and a failure to start from a preset is raised on the preset screen.
623
624
 
624
625
  ## Request a quote
625
626
 
@@ -1266,8 +1267,8 @@ The same layout in a frame 390 pixels wide, as on a phone. Opened on its own fro
1266
1267
 
1267
1268
  A project layout has only what it renders. This one leaves out the following, each of which a product may need. [Layout Slots](?path=/docs/phoenix-layout-slots--docs) describes every `LayoutSlots` member named here.
1268
1269
 
1269
- - **Messages that stay snackbars.** The toast shows violations, side effects and errors, and the finalize button shows its own error. A failure to save the configuration is still raised as a snackbar, which renders into `LayoutSlots.DesktopSnackbarsRoot`; this layout mounts no snackbar root, so it appears outside the configurator. A layout that wants it inside mounts the root.
1270
- - **Toolbar actions.** Undo and redo, measurements, sharing and language switching are not offered. A layout can mount `LayoutSlots.DesktopToolbar` for the toolbar the spec configures, or build its own buttons.
1270
+ - **Messages that stay snackbars.** The toast shows violations, side effects, errors and failed saves, and the finalize button shows its own error. Two kinds of message stay snackbars. An error of a built-in action, such as one from `LayoutSlots.DesktopToolbar`, renders into `LayoutSlots.DesktopSnackbarsRoot`; this layout mounts neither, and a layout that mounts such an action mounts the root too, or the snackbar appears outside the configurator. A failure to start from a preset appears on the preset screen, before this layout mounts; a project replaces that screen with `phx.frontend.createPresetUi`, see [Configurator Interface → Preset UI](?path=/docs/phoenix-configurator-interface--docs#preset-ui).
1271
+ - **Toolbar actions.** Undo and redo, measurements, sharing and language switching are not offered. A layout can mount `LayoutSlots.DesktopToolbar` for the toolbar the spec configures, or build its own buttons; for undo and redo, `phx.frontend.useUndoRedo()` gives `canUndo`, `canRedo`, `undo` and `redo`, and pushes the same analytics events as the built-in buttons.
1271
1272
  - **The spec's view controls, tool controls and leaving a tool mode.** A spec can configure view controls and tool controls, and a tool mode has a button that leaves it. The built-in layout places them with `LayoutSlots.DesktopViewControls`, `LayoutSlots.DesktopToolControls` and `LayoutSlots.DesktopCallToAction`; this layout mounts none of them, so the spec's `viewControls` and `toolControls` do not appear. Its views come from its own buttons instead, see [Control the scene](#control-the-scene), and it has no tool modes.
1272
1273
  - **A project's layout dialog.** A dialog the project registers with `phx.frontend.createLayoutDialog` is shown by `LayoutDialog`, which the built-in layout mounts. This layout does not; a layout that needs the dialog mounts `LayoutDialog` from `@salsita-ai/sdk/configurator`.
1273
1274
  - **Selection.** The table has no parts to select. A product with selectable parts reads `phx.frontend.useSelectedDomainEntityId()` and shows the controls of the selected part.
@@ -1846,7 +1847,7 @@ const severityOf = (message: ConfiguratorMessage) => {
1846
1847
  return VIOLATION_SEVERITY[message.violation.severity]
1847
1848
  }
1848
1849
 
1849
- return message.kind === 'error' ? 'error' : 'info'
1850
+ return message.kind === 'error' || message.kind === 'saveError' ? 'error' : 'info'
1850
1851
  }
1851
1852
 
1852
1853
  export const MessageToast = () => {
@@ -12,7 +12,7 @@ Error page that is displayed when the configuration is not accesible.
12
12
 
13
13
  ## Code
14
14
 
15
- The `NotFoundPage` is provided in the default in the `ConfiguratorRoot` and `ConfiguratorApp`.
15
+ The configurator renders the layout element `ConfigurationNotFoundPage`, whose default is the built-in page, `ConfigurationNotFoundPage` from `@salsita-ai/sdk/react`. This example renders that component directly to show it. A project replaces the page by registering a layout element under the name `ConfigurationNotFoundPage`, see [Configurator Interface → Configuration Not Found Page](?path=/docs/phoenix-configurator-interface--docs#configuration-not-found-page), which also lists when the page is shown.
16
16
 
17
17
  ## Example
18
18
 
@@ -14,7 +14,7 @@ useAuthHook: true
14
14
  useClerk: true
15
15
  ```
16
16
 
17
- `phoenix sync` then adds `@clerk/clerk-react` as a direct application dependency and generates typed Clerk environment readers. `phoenix deployment:prepare-local` writes the deployment's public `CLERK_PUBLISHABLE_KEY` to `.env.generated`; application source should read it from `phx.frontendEnv`, not hardcode a key.
17
+ `@clerk/clerk-react` then becomes a dependency the project declares: run `phoenix deps` through your package manager (for example `pnpm phoenix deps`) and install to add it, because `phoenix sync`, `dev` and `build` stop while it is missing. `phoenix sync` generates typed Clerk environment readers. In a project on the legacy pipeline (see [Current and legacy project tooling](?path=/docs/phoenix-phoenix-cli--docs#current-and-legacy-project-tooling)), `phoenix sync` adds the dependency itself. `phoenix deployment:prepare-local` writes the deployment's public `CLERK_PUBLISHABLE_KEY` to `.env.generated`; application source should read it from `phx.frontendEnv`, not hardcode a key.
18
18
 
19
19
  ## Configure the auth webhook
20
20
 
@@ -222,7 +222,7 @@ const controlViolations = phx.frontend.useControlViolations([])
222
222
  // Finalize violations
223
223
  const { disabled, hints } = phx.frontend.useFinalizeViolations()
224
224
 
225
- // Messages for the user: violations of a rejected change, side-effect hints and errors, one call each
225
+ // Messages for the user: violations of a rejected change, side-effect hints, errors and failed saves, one call each
226
226
  phx.frontend.useConfiguratorMessages(message => showToast(message.text))
227
227
 
228
228
  // Map violations to hints
@@ -391,12 +391,39 @@ Use these factories to customize the preset-selection screen without changing ho
391
391
  - `createPresetPageAction` adds project-specific content below the default preset cards.
392
392
  - `createPresetDeviceNotSupported` adds project-specific overlay content above the default preset cards.
393
393
 
394
+ Without `createPresetUi`, Phoenix renders its built-in preset screen, which is the default `PresetUi` registration in `defaultConfiguratorElements`. The action and the overlay are parts of that built-in screen and render nothing until the project registers them.
395
+
394
396
  ```typescript
395
397
  const PresetScreen = phx.frontend.createPresetUi(CustomPresetScreen)
396
398
  const PresetAction = phx.frontend.createPresetPageAction(PresetHelpAction)
397
399
  const PresetOverlay = phx.frontend.createPresetDeviceNotSupported(SmallScreenNotice)
398
400
  ```
399
401
 
402
+ ### Configuration Not Found Page
403
+
404
+ A layout element registered under the name `ConfigurationNotFoundPage` replaces the page the configurator shows when it cannot open a configuration. Without it, Phoenix renders its built-in "Configuration not accessible" page, which is the default `ConfigurationNotFoundPage` registration in `defaultConfiguratorElements`.
405
+
406
+ ```typescript
407
+ // src/elements/ConfigurationNotFoundPage.tsx
408
+ import type { PropsWithChildren } from 'react'
409
+ import type { ConfigurationNotFoundPageProps } from '@salsita-ai/sdk/react'
410
+
411
+ export default phx.frontend.createCustomPhoenixLayoutElement(
412
+ 'ConfigurationNotFoundPage',
413
+ (props: PropsWithChildren<ConfigurationNotFoundPageProps>) => (
414
+ <main>
415
+ <h1>We could not find this configuration</h1>
416
+ {props.onBack && <button onClick={props.onBack}>Start a new one</button>}
417
+ </main>
418
+ )
419
+ )
420
+ ```
421
+
422
+ - The page receives one optional prop, `onBack`, which returns to the start of the configurator. It is set when a configuration fails to load or is opened without a valid access key. It is not set on the start page itself, so a page that offers a way back shows it only when `onBack` is set.
423
+ - The configurator renders the element in four cases: a configuration fails to load, for example because its ID does not exist or the server rejects its access key; a configuration is opened without an access key for it; a signed-in user whose role may not create configurations (`acl.canCreate`) opens the start page; the preset screen has no preset category to show, for example because the host's `presetTags` match no preset. The last case happens inside the built-in preset screen, so a project that replaces it with `createPresetUi` decides what to show itself.
424
+ - The not-authenticated page, shown when session authentication fails or to an anonymous visitor who may not create a configuration, is its own element, which `createNotAuthenticatedPage` replaces. Its default, the `NotAuthenticatedPage` registration in `defaultConfiguratorElements`, renders the `ConfigurationNotFoundPage` element, so a project's not-found page shows there too.
425
+ - The admin's not-authorized page still shows the built-in page directly, not the element.
426
+
400
427
  ### Loaded Configuration UI
401
428
 
402
429
  A layout element registered under the name `ConfiguratorLayout` replaces the whole UI of a loaded configuration, in owner and in read mode, whatever `layout` the spec sets. Without it, Phoenix renders the layout the spec selects. [Custom Layout](?path=/docs/layouts-custom-layout--docs) is a worked example: a fully custom product page with its panel on the left, built from the scene, the safe-area hook and state hooks.
@@ -433,10 +460,10 @@ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout
433
460
  - `LayoutSlots.DesktopSidebar` is the built-in desktop sidebar as one piece: the spec's tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Use it when the spec's property pages are what the customer should see and only their place changes, for example a sidebar on the left; build the controls yourself when they have to look like the brand's own. Without `width` it fills its container, so the layout positions and sizes that container; `miniSidebar` does not shrink it, so a layout that honours it reads `LayoutSlots.useSidebarContent().hasMiniSidebar` and lets the container take the sidebar's height. Its tabs sit outside it on the left; `side="start"` puts them on its right, for a sidebar on the left. In fullscreen it stays in place, transparent and inert, so a layout that wants the space back removes it while `phx.frontend.useIsFullscreenActive()` is set. See [Sidebar in a Custom Layout](?path=/docs/layouts-sidebar-in-a-custom-layout--docs).
434
461
  - Nothing of the built-in layouts is rendered for it. A project layout finalizes over the spec's `finalizeAction` components, see the finalize paragraph of [Hooks for a Project Layout](#hooks-for-a-project-layout). The `finalizePage` and `finalizeButton` components are legacy: they cannot be declared in YAML, only in TypeScript, and `Legacy.CommonFinalizePage`, which is deprecated, shows a `finalizePage` for projects that still have one. `LayoutDialog` renders only what the project registers with `createLayoutDialog` and is not part of the finalize flow. The preview start screen is `DesktopPreview` or `MobilePreview`, which the layout shows while `usePreview().isPreview` is set. Both previews frame the scene themselves, so the layout renders the component that calls `useSceneSafeArea()` only while `isPreview` is not set; it then measures when it mounts after the preview. The assistant panel is available to a project layout only inside `LayoutSlots.DesktopSidebar`.
435
462
  - The layout reads the state it has to react to from hooks, listed with what each returns in [Hooks for a Project Layout](#hooks-for-a-project-layout).
436
- - Messages for the user come one of two ways. `LayoutSlots.Services` shows violations of a rejected change, side-effect hints and errors as the built-in snackbars, which render into `LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot` wherever the layout mounts it. `phx.frontend.useConfiguratorMessages(onMessage)` hands the same messages to the layout's own UI instead, with their text resolved, as the toast in [Custom Layout](?path=/docs/layouts-custom-layout--docs#show-messages) does; mount one of the two, not both, or every message is shown twice. Some messages stay snackbars either way: a failure to save the configuration and an error of the built-in finalize and other actions. They render into the snackbar root, so a layout that mounts none shows them outside the configurator, in the page's `body`. An error while a preset is applied appears on the preset screen, outside the layout. A finalize action the layout runs with `phx.frontend.useFinalizeAction` reports its error as that hook's `error` instead, neither as a message nor as a snackbar.
463
+ - Messages for the user come one of two ways. `LayoutSlots.Services` shows violations of a rejected change, side-effect hints, errors and failed saves as the built-in snackbars, which render into `LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot` wherever the layout mounts it. `phx.frontend.useConfiguratorMessages(onMessage)` hands the same messages to the layout's own UI instead, with their text resolved, as the toast in [Custom Layout](?path=/docs/layouts-custom-layout--docs#show-messages) does; mount one of the two, not both, or every message is shown twice. A failure to save the configuration, and the first successful save after it, are messages too, so a layout that mounts neither shows no sign of a failed save. An error of the built-in finalize and other actions stays a snackbar either way. It renders into the snackbar root, so a layout that mounts none shows it outside the configurator, in the page's `body`. A failure to start a configuration from a preset is not a message of the hook either: it happens on the preset screen, before any `ConfiguratorLayout` mounts. In production with more than one preset it is a snackbar there, also on a preset screen replaced with `createPresetUi` (see [Preset UI](#preset-ui)), which reads the error from `phx.frontend.usePresetSelection().selectionError` and mounts `PresetSnackbarsRoot` to keep the snackbar inside the configurator. A finalize action the layout runs with `phx.frontend.useFinalizeAction` reports its error as that hook's `error` instead, neither as a message nor as a snackbar.
437
464
  - Any project layout element named `ConfiguratorLayout` takes over the page.
438
465
 
439
- `defaultConfiguratorElements`, exported from `@salsita-ai/sdk/configurator`, holds the built-in UI as a `ConfiguratorLayout` registration that renders the layout the spec selects. Generated code registers these defaults with `withDefaultElements`, apart from the project's elements, so the built-in UI is a registration like any other; a project element registered under the same name wins over a default. The list also holds the configurator's built-in controls and layout elements under the kinds the spec names them by, such as `attributeSelect` or `undoToolbarButton`, so a project element registered under one of those names replaces the built-in one wherever the spec names that kind. A config assembled by hand, without generated code, calls `withDefaultElements(defaultConfiguratorElements)` too; without it no `ConfiguratorLayout` is registered and the page shows a "not found" box instead of the configurator.
466
+ `defaultConfiguratorElements`, exported from `@salsita-ai/sdk/configurator`, holds the built-in UI as a `ConfiguratorLayout` registration that renders the layout the spec selects, the built-in not-found page as a `ConfigurationNotFoundPage` registration, the built-in not-authenticated page as a `NotAuthenticatedPage` registration, and the built-in preset screen as a `PresetUi` registration. Generated code registers these defaults with `withDefaultElements`, apart from the project's elements, so the built-in UI is a registration like any other; a project element registered under the same name wins over a default. The list also holds the configurator's built-in controls and layout elements under the kinds the spec names them by, such as `attributeSelect` or `undoToolbarButton`, so a project element registered under one of those names replaces the built-in one wherever the spec names that kind. A config assembled by hand, without generated code, calls `withDefaultElements(defaultConfiguratorElements)` too; the lists are the only way the built-in elements are registered, so without it the page shows a "not found" box instead of the configurator, and a project layout shows one in place of every built-in control. The admin mounts a full configurator, so its list, `defaultAdminElements` from `@salsita-ai/sdk/admin`, holds the configurator's defaults followed by the admin's built-in pages, sidebars and actions; generated admin code passes it to `withDefaultElements` in place of `defaultConfiguratorElements`, because each call replaces the defaults passed before.
440
467
 
441
468
  #### Hooks for a Project Layout
442
469
 
@@ -467,28 +494,30 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
467
494
 
468
495
  **Mode, fullscreen, preview and navigation**
469
496
 
470
- | Hook | Returns |
471
- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
472
- | `phx.frontend.useAccessService()` | `{ configurationId, accessInfo }`. `accessInfo.viewMode` is `true` while a configuration shared for viewing is open, and also while the host sets the `readonly` property. |
473
- | `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it. |
474
- | `usePreview()` from `@salsita-ai/sdk/configurator` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. |
475
- | `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`. |
476
- | `phx.frontend.usePushNavigate()` and `phx.frontend.usePopNavigate()` | `(state) => void`, which enters a state and remembers the active one, and `() => void`, which returns to the remembered state and does nothing when there is none. `phx.frontend.useNavigate()` returns `(state) => void`, which enters a state and forgets the history. |
477
- | `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state. |
478
- | `phx.frontend.useSetViewState()` | `(state) => void`, which makes the given view state, one of the spec's `viewStates`, the active one; setting the active state again keeps it. Cameras and components filtered by `viewStates` follow the change. |
479
- | `phx.frontend.useResetCamera()` | `() => void`, which moves the camera back to the framing of the active camera configuration, undoing the user's orbit, pan and zoom, as the toolbar's reset button does. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
480
- | `useActiveViewports()` from `@salsita-ai/sdk/react` | The responsive breakpoints the configurator's container satisfies, as `Viewport[]`, or `null` before it is measured. It follows the container, not the window, so it is the switch between a desktop and a phone layout. `desktop` is a container wider than 768 pixels and `mobile` one at most 768 pixels wide; `phone` is narrower than 480 and `tablet` between 480 and 1024 pixels; `landscape` and `portrait` follow the browser window's orientation. The built-in layout uses its phone arrangement unless `desktop` is active. |
497
+ | Hook | Returns |
498
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
499
+ | `phx.frontend.useAccessService()` | `{ configurationId, accessInfo }`. `accessInfo.viewMode` is `true` while a configuration shared for viewing is open, and also while the host sets the `readonly` property. |
500
+ | `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it. |
501
+ | `usePreview()` from `@salsita-ai/sdk/configurator` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. |
502
+ | `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`. |
503
+ | `phx.frontend.usePushNavigate()` and `phx.frontend.usePopNavigate()` | `(state) => void`, which enters a state and remembers the active one, and `() => void`, which returns to the remembered state and does nothing when there is none. `phx.frontend.useNavigate()` returns `(state) => void`, which enters a state and forgets the history. |
504
+ | `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state. |
505
+ | `phx.frontend.useSetViewState()` | `(state) => void`, which makes the given view state, one of the spec's `viewStates`, the active one; setting the active state again keeps it. Cameras and components filtered by `viewStates` follow the change. |
506
+ | `phx.frontend.useResetCamera()` | `() => void`, which moves the camera back to the framing of the active camera configuration, undoing the user's orbit, pan and zoom, as the toolbar's reset button does. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
507
+ | `phx.frontend.useUndoRedo()` | `{ canUndo, canRedo, undo, redo }`: whether the undo history has a change to undo or to redo, and two `() => void` functions that undo or redo it, as the toolbar's undo and redo buttons do, pushing the same analytics events; each does nothing while there is nothing to undo or redo. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
508
+ | `phx.frontend.useTabNavigation(options?)` | `{ source, tabs, activeTab, select }` for the spec's tabs: `source` is `'sidebarTabs'` (every active `sidebarTabs` of the selected entity, in spec order) or `'horizontalTabs'` (the first active `horizontalTabs`), or `null` without tabs. Each tab is `{ index, label, icon, iconNode, navigationStates, state, isActive }`, with the label resolved, `icon` a PhxIcon name and `iconNode` a React node given by TypeScript spec code; `activeTab` is the first active tab or `null`. `select(index or navigation state)` enters the tab's first navigation state, as the built-in tabs do, pushing the same analytics event for `horizontalTabs`. `options.source` (`'auto'`, `'sidebarTabs'` or `'horizontalTabs'`) chooses the kind; `'auto'` prefers `sidebarTabs` when both are active, with a warning in development. `options.entityId` reads another entity's `sidebarTabs`. A tab without navigation states is left out, with a warning in development. It does not check read mode. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
509
+ | `useActiveViewports()` from `@salsita-ai/sdk/react` | The responsive breakpoints the configurator's container satisfies, as `Viewport[]`, or `null` before it is measured. It follows the container, not the window, so it is the switch between a desktop and a phone layout. `desktop` is a container wider than 768 pixels and `mobile` one at most 768 pixels wide; `phone` is narrower than 480 and `tablet` between 480 and 1024 pixels; `landscape` and `portrait` follow the browser window's orientation. The built-in layout uses its phone arrangement unless `desktop` is active. |
481
510
 
482
511
  **Violations**
483
512
 
484
- | Hook | Returns |
485
- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
486
- | `phx.frontend.useStateValidity()` | The validity of the last committed state: `{ valid: true, severity: null }`, or `{ valid: true, severity: 'notice' \| 'warning', entityViolations }`. A change that leaves a `fixable` or `fatal` violation is rejected and never committed, and neither is a change during which an error was thrown, so neither is seen here. |
487
- | `phx.frontend.useFinalizeViolations()` | `{ disabled, hints, violations }`. `disabled` is `true` while a `warning`, `fixable` or `fatal` violation holds or the configuration failed to save; `hints` is the violations' text, or `undefined`. |
488
- | `phx.frontend.useConfiguratorMessages(onMessage)` | Nothing. It calls `onMessage(message)` once per message: `{ kind: 'violation', entityId, violation, text }` for each violation of the rejected state, including notices and warnings already present, so `violation.severity` tells the reason for the rejection (`fixable` or `fatal`) from them, `{ kind: 'sideEffect', text }` for each hint of a committed update, and `{ kind: 'error', error, text }` when an error is thrown while a change is validated, with `text` the generic "Oops, something went wrong". `text` is resolved to the current language; a violation's is its `message`, or its `type` without one, and the built-in snackbar prefixes it with `entityId`. Hints committed before the layout mounted are not delivered; when the most recent change was rejected or threw, its messages are delivered again when the hook mounts. `onMessage` may be a new function on every render. On `phx.frontend` the message is typed by the project, so `violation.type` narrows to its violation types; from `@salsita-ai/sdk/react` the type is `ConfiguratorMessage`. |
489
- | `phx.frontend.useFinalizeAction(handler)` | `{ run, isPending, error, disabled, hints }` for the `finalizeAction` whose `handler` it is given; see the Finalize paragraph below for how to call it. `run()` records the finalize-button click for analytics and runs the project's handler, and does nothing while `disabled` or `isPending`; `error` is what the handler's last run rejected with, or `null`; `disabled` and `hints` are those of `useFinalizeViolations()`. The type of the result is `UseFinalizeActionResult` from `@salsita-ai/sdk/configurator`, which also exports the hook. |
513
+ | Hook | Returns |
514
+ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
515
+ | `phx.frontend.useStateValidity()` | The validity of the last committed state: `{ valid: true, severity: null }`, or `{ valid: true, severity: 'notice' \| 'warning', entityViolations }`. A change that leaves a `fixable` or `fatal` violation is rejected and never committed, and neither is a change during which an error was thrown, so neither is seen here. |
516
+ | `phx.frontend.useFinalizeViolations()` | `{ disabled, hints, violations }`. `disabled` is `true` while a `warning`, `fixable` or `fatal` violation holds or the configuration failed to save; `hints` is the violations' text, or `undefined`. |
517
+ | `phx.frontend.useConfiguratorMessages(onMessage)` | Nothing. It calls `onMessage(message)` once per message: `{ kind: 'violation', entityId, violation, text }` for each violation of the rejected state, including notices and warnings already present, so `violation.severity` tells the reason for the rejection (`fixable` or `fatal`) from them, `{ kind: 'sideEffect', text }` for each hint of a committed update, `{ kind: 'error', error, text }` when an error is thrown while a change is validated, with `text` the generic "Oops, something went wrong", `{ kind: 'saveError', error, text }` each time saving the configuration fails, with `error` what the save rejected with and `text` a generic message, and `{ kind: 'saveRecovery', text }` when a save succeeds after a failed one, with `text` "Configuration updated". `text` is resolved to the current language; a violation's is its `message`, or its `type` without one, and the built-in snackbar prefixes it with `entityId`. Hints committed before the layout mounted are not delivered, nor is a save that failed before it mounted; when the most recent change was rejected or threw, its messages are delivered again when the hook mounts. `onMessage` may be a new function on every render. On `phx.frontend` the message is typed by the project, so `violation.type` narrows to its violation types; from `@salsita-ai/sdk/react` the type is `ConfiguratorMessage`. |
518
+ | `phx.frontend.useFinalizeAction(handler)` | `{ run, isPending, error, disabled, hints }` for the `finalizeAction` whose `handler` it is given; see the Finalize paragraph below for how to call it. `run()` records the finalize-button click for analytics and runs the project's handler, and does nothing while `disabled` or `isPending`; `error` is what the handler's last run rejected with, or `null`; `disabled` and `hints` are those of `useFinalizeViolations()`. The type of the result is `UseFinalizeActionResult` from `@salsita-ai/sdk/configurator`, which also exports the hook. |
490
519
 
491
- `LayoutSlots.Services` shows the messages of `useConfiguratorMessages` as snackbars; see the messages paragraph of [Loaded Configuration UI](#loaded-configuration-ui) for choosing between the two and for the messages that stay snackbars.
520
+ `LayoutSlots.Services` shows every message of `useConfiguratorMessages` as a snackbar, `saveError` and `saveRecovery` included; see the messages paragraph of [Loaded Configuration UI](#loaded-configuration-ui) for choosing between the two and for the messages that stay snackbars.
492
521
 
493
522
  **Finalize**
494
523
 
@@ -624,7 +653,7 @@ const NotAuthorizedAdminPage = phx.frontend.admin.createNotAuthorizedAdminPage((
624
653
  ```
625
654
 
626
655
  When no authentication-error page is registered, Phoenix falls back to the registered not-authorized page and then
627
- to the default configuration-not-found page.
656
+ to the built-in configuration-not-found page. The admin shows the built-in page even when the project registers a `ConfigurationNotFoundPage`, see [Configuration Not Found Page](#configuration-not-found-page).
628
657
 
629
658
  ## What We Learned
630
659
 
@@ -22,10 +22,10 @@ The frontend SDK provides the core libraries and components for building configu
22
22
 
23
23
  The Phoenix CLI (`phoenix` command) is the unified tool for all configurator development. It combines domain code generation, webhook building, deployment management, and development workflows into a single interface:
24
24
 
25
- - `phoenix init` - Start a new project with a working starter spec
25
+ - `phoenix init` - Start a new project: a working starter spec and a `package.json` with the SDK, the packages it requires next to it, ESLint and Prettier
26
26
  - `phoenix dev` - Start the development server with hot reload
27
27
  - `phoenix build` - Build the configurator for production
28
- - `phoenix sync` - Synchronize dependencies and generated code
28
+ - `phoenix sync` - Regenerate the generated code and project files
29
29
  - `phoenix deps` - Add the SDK's missing peer dependencies to `package.json` and move out-of-range ones into range
30
30
  - `phoenix check` - Validate domain configuration and code
31
31
  - `phoenix fix` - Auto-fix common issues in domain files
@@ -58,12 +58,14 @@ This section provides practical guidance for developers who are new to Phoenix P
58
58
 
59
59
  Before diving into Phoenix Platform development, ensure you have:
60
60
 
61
- - **Node.js 22.13 or newer** and **pnpm** - The SDK and the Phoenix CLI run on Node.js 22.13 or newer (see [Starting a New Project](?path=/docs/phoenix-phoenix-cli--docs#starting-a-new-project)); a project generated by Phoenix pins the exact Node.js and pnpm versions it is developed with
61
+ - **Node.js 22.13 or newer** and **a package manager** - The SDK and the Phoenix CLI run on Node.js 22.13 or newer (see [Starting a New Project](?path=/docs/phoenix-phoenix-cli--docs#starting-a-new-project)). A project installs with any package manager; with pnpm 11 it needs the settings in [Package manager](?path=/docs/phoenix-project-structure--docs#package-manager)
62
62
  - **JavaScript/TypeScript** - Phoenix generates TypeScript code and uses modern JavaScript patterns
63
63
  - **React** - The platform builds on React components, hooks, and modern React patterns
64
64
  - **Three.js** - Basic knowledge of 3D graphics, scenes, cameras, and rendering concepts
65
65
  - **React Three Fiber** - Understanding of declarative 3D development and R3F component patterns
66
66
 
67
+ The commands in this documentation use pnpm, as in `pnpm phoenix sync`. They work the same through any other package manager: `npx phoenix sync` with npm, `yarn phoenix sync` with Yarn, `bunx phoenix sync` with Bun.
68
+
67
69
  We assume you have working knowledge of these technologies before starting with Phoenix Platform. If you're new to any of these areas, we recommend familiarizing yourself with them first, as Phoenix Platform builds directly on these foundations.
68
70
 
69
71
  ### Your First Steps
@@ -31,31 +31,31 @@ export const Toolbar = () => (
31
31
 
32
32
  ## Canvas and services
33
33
 
34
- | Member | Shows | Fed by | With nothing configured | Read mode |
35
- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------- | --------- |
36
- | `SharedCanvas` | The 3D scene, in a container that is `position: absolute; inset: 0` with the scene's background colour. The scene is hidden for a moment while the preview expands or collapses. | The scene of the spec, through `Canvas` | Always renders | No change |
37
- | `Canvas` | The WebGL canvas alone, or `GPUIssuePage` when the browser has no WebGL or no GPU acceleration. It reports the scene-assets loading phase the app loader waits for. | The scene, the canvas settings and the `CanvasLoader` element | Always renders | No change |
38
- | `Services` | Nothing visible: it mounts `InvariantViolations` and `SideEffects`, which show the messages of `useConfiguratorMessages()` as snackbars | The configurator's state | Always mounted | No change |
39
- | `InvariantViolations` | When a change is rejected, a snackbar for each violation of the rejected state, including notices and warnings already present, as `<entity id>: <message>` (a `notice` as info, a `warning` as warning, `fixable` and `fatal` as error), and "Oops, something went wrong" for an error thrown while the state was validated | The spec's invariants | Nothing | No change |
40
- | `SideEffects` | An info snackbar for each committed update that carries a hint | The hints of committed updates | Nothing | No change |
41
- | `DesktopSnackbarsRoot` | The element the snackbars are rendered into: a stack anchored to its bottom edge, at most 320 pixels wide | Every snackbar the configurator raises | An empty element | No change |
34
+ | Member | Shows | Fed by | With nothing configured | Read mode |
35
+ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
36
+ | `SharedCanvas` | The 3D scene, in a container that is `position: absolute; inset: 0` with the scene's background colour. The scene is hidden for a moment while the preview expands or collapses. | The scene of the spec, through `Canvas` | Always renders | No change |
37
+ | `Canvas` | The WebGL canvas alone, or `GPUIssuePage` when the browser has no WebGL or no GPU acceleration. It reports the scene-assets loading phase the app loader waits for. | The scene, the canvas settings and the `CanvasLoader` element | Always renders | No change |
38
+ | `Services` | Nothing visible: it mounts `InvariantViolations` and `SideEffects`, which show the messages of `useConfiguratorMessages()` as snackbars, and shows a failed save of the configuration as a persistent error snackbar with **Dismiss**, followed by "Configuration updated" when a later save succeeds | The configurator's state | Always mounted | No change; the save-failure and "Configuration updated" snackbars are not shown, and one left from owner mode closes |
39
+ | `InvariantViolations` | When a change is rejected, a snackbar for each violation of the rejected state, including notices and warnings already present, as `<entity id>: <message>` (a `notice` as info, a `warning` as warning, `fixable` and `fatal` as error), and "Oops, something went wrong" for an error thrown while the state was validated | The spec's invariants | Nothing | No change |
40
+ | `SideEffects` | An info snackbar for each committed update that carries a hint | The hints of committed updates | Nothing | No change |
41
+ | `DesktopSnackbarsRoot` | The element the snackbars are rendered into: a stack anchored to its bottom edge, at most 320 pixels wide | Every snackbar the configurator raises | An empty element | No change |
42
42
 
43
43
  - Mount `SharedCanvas`, not `Canvas`: `Canvas` suspends while it probes the GPU on its first mount, and `SharedCanvas` holds the boundary that catches it. Without either, the app loader stays on screen unless the project sets `appLoader: legacy`.
44
44
  - `SharedCanvas` fills its nearest positioned ancestor and paints an opaque background, so the layout positions its own UI after it in the markup.
45
- - Snackbars are raised by `Services`, and also by the built-in finalize and footer actions when their handler fails and when saving the configuration fails. A layout that shows violations, side-effect hints and validation errors its own way calls `phx.frontend.useConfiguratorMessages(onMessage)` instead of mounting `Services`, see [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout); the other snackbars still need a root. They render into the snackbar root the layout mounts; with no root mounted they render into the page's `document.body`, outside the configurator. The desktop root is a zero-height element the snackbars stack upwards from, so the layout places it where their bottom edge belongs. The phone root leaves them fixed to the viewport, see `MobileSnackbarsRoot`. A finalize action the layout runs with `phx.frontend.useFinalizeAction` raises no snackbar; its error is the hook's `error`.
45
+ - Snackbars are raised by `Services`, for violations, side-effect hints, validation errors and a failed save, and also by the built-in finalize and footer actions when their handler fails. A layout that shows these messages its own way calls `phx.frontend.useConfiguratorMessages(onMessage)` instead of mounting `Services`, and then shows the failed save too, see [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout); the other snackbars still need a root. They render into the snackbar root the layout mounts; with no root mounted they render into the page's `document.body`, outside the configurator. The desktop root is a zero-height element the snackbars stack upwards from, so the layout places it where their bottom edge belongs. The phone root leaves them fixed to the viewport, see `MobileSnackbarsRoot`. A finalize action the layout runs with `phx.frontend.useFinalizeAction` raises no snackbar; its error is the hook's `error`.
46
46
 
47
47
  ## Desktop sidebar
48
48
 
49
- | Member | Shows | Fed by | With nothing configured | Read mode |
50
- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
- | `DesktopSidebar` | The whole built-in desktop sidebar: tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Props: `width`, and `side` (`'start'` or `'end'`, typed `DesktopSidebarSide`; `DesktopSidebarProps` types both) | The members below | As its parts | As its parts. In fullscreen it stays in place, transparent and inert |
52
- | `DesktopSidebarTabs` | Tabs that switch the navigation state | The `sidebarTabs` components of the selected entity; the `horizontalTabs` component is not shown here | Nothing; also nothing while the summary is shown | No change |
53
- | `useSidebarTabs()` | A hook, `{ tabs, activeTab, onChange }`: each tab is `{ label, icon, value, isActive }`, `activeTab` is the active tab's value or `'none'`, and `onChange(value)` switches to that tab's first navigation state | As `DesktopSidebarTabs` | `tabs` is empty | No change |
54
- | `DesktopSidebarBackButton` | A "Back" button. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxDesktopSidebarBackButton`; the application config's `sidebarBackIcon` takes precedence over both | The sidebar's way back: a navigation state to pop, a stacked property page, or a selected entity other than the root, unless the application config sets `showSelectionBackButton` to `false` or sets `ignoreSelectionInSidebar` | Nothing while there is no way back | No change |
55
- | `DesktopSidebarContent` | The property pages of the selected entity with its entity header, a stacked page opened from them, or the summary page while the summary is shown. With the application config's `ignoreSelectionInSidebar` set, every `propertyPage` of every entity shows under the root, whatever is selected | `propertyPage`, `entityHeader`, `simpleSummary` and `customSummaryPage` components, and `ignoreSelectionInSidebar` | Nothing | **No change: the controls stay editable.** Limit them with `$filter.modes`, or render something else in read mode |
56
- | `useSidebarContent(options?)` | A hook, `{ content, goBack, hasMiniSidebar, hasSplitSidebar }`: `content` is what `DesktopSidebarContent` shows, `goBack` is the back action or `null`. `options` sets the `HeaderContainer` and `BodyContainer` components the content is wrapped in | As `DesktopSidebarContent` | `content` is empty | No change |
57
- | `EntityHeader` | An entity's title, subtitle, description and thumbnail, with its primary and secondary buttons, such as delete or replace. Props, typed `EntityHeaderProps`: `entityId` and `headerComponent` | The texts and thumbnail from the entity's `detail` component (the title falls back to the entity type); the buttons from the `entityHeader` component passed in | — | No change |
58
- | `DesktopSidebarFooter` | The price and the actions | `basicSidebarFooter`, `universalSidebarFooter`, `readSidebarFooter`, `hiddenSidebarFooter` or `customSidebarFooter`. The basic footer offers a `finalizeButton`, else a button for a `finalizePage`, else a disabled button; the universal footer offers the `finalizeAction` and `otherAction` components | The basic footer: the price and a finalize button. With a `navigationSteps` component of two or more steps, previous and next step buttons and the finalize button only on the last step; with a summary and no steps, a **Summary** button until the summary is shown; in a preset embed, **Save Template** instead of finalize | With nothing configured, the read footer: price only. The basic footer hides its buttons. **The universal footer still shows its finalize and other actions to a viewer**; limit them with `$filter.modes` |
49
+ | Member | Shows | Fed by | With nothing configured | Read mode |
50
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `DesktopSidebar` | The whole built-in desktop sidebar: tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Props: `width`, and `side` (`'start'` or `'end'`, typed `DesktopSidebarSide`; `DesktopSidebarProps` types both) | The members below | As its parts | As its parts. In fullscreen it stays in place, transparent and inert |
52
+ | `DesktopSidebarTabs` | Tabs that switch the navigation state | The `sidebarTabs` components of the selected entity; the `horizontalTabs` component is not shown here | Nothing; also nothing while the summary is shown | No change |
53
+ | `useSidebarTabs()` | A hook, `{ tabs, activeTab, onChange }`: each tab is `{ label, icon, value, isActive }`, `activeTab` is the active tab's value or `'none'`, and `onChange(value)` switches to that tab's first navigation state. A project layout reads its tabs with `phx.frontend.useTabNavigation()` instead, see [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout) | As `DesktopSidebarTabs` | `tabs` is empty | No change |
54
+ | `DesktopSidebarBackButton` | A "Back" button. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxDesktopSidebarBackButton`; the application config's `sidebarBackIcon` takes precedence over both | The sidebar's way back: a navigation state to pop, a stacked property page, or a selected entity other than the root, unless the application config sets `showSelectionBackButton` to `false` or sets `ignoreSelectionInSidebar` | Nothing while there is no way back | No change |
55
+ | `DesktopSidebarContent` | The property pages of the selected entity with its entity header, a stacked page opened from them, or the summary page while the summary is shown. With the application config's `ignoreSelectionInSidebar` set, every `propertyPage` of every entity shows under the root, whatever is selected | `propertyPage`, `entityHeader`, `simpleSummary` and `customSummaryPage` components, and `ignoreSelectionInSidebar` | Nothing | **No change: the controls stay editable.** Limit them with `$filter.modes`, or render something else in read mode |
56
+ | `useSidebarContent(options?)` | A hook, `{ content, goBack, hasMiniSidebar, hasSplitSidebar }`: `content` is what `DesktopSidebarContent` shows, `goBack` is the back action or `null`. `options` sets the `HeaderContainer` and `BodyContainer` components the content is wrapped in | As `DesktopSidebarContent` | `content` is empty | No change |
57
+ | `EntityHeader` | An entity's title, subtitle, description and thumbnail, with its primary and secondary buttons, such as delete or replace. Props, typed `EntityHeaderProps`: `entityId` and `headerComponent` | The texts and thumbnail from the entity's `detail` component (the title falls back to the entity type); the buttons from the `entityHeader` component passed in | — | No change |
58
+ | `DesktopSidebarFooter` | The price and the actions | `basicSidebarFooter`, `universalSidebarFooter`, `readSidebarFooter`, `hiddenSidebarFooter` or `customSidebarFooter`. The basic footer offers a `finalizeButton`, else a button for a `finalizePage`, else a disabled button; the universal footer offers the `finalizeAction` and `otherAction` components | The basic footer: the price and a finalize button. With a `navigationSteps` component of two or more steps, previous and next step buttons and the finalize button only on the last step; with a summary and no steps, a **Summary** button until the summary is shown; in a preset embed, **Save Template** instead of finalize | With nothing configured, the read footer: price only. The basic footer hides its buttons. **The universal footer still shows its finalize and other actions to a viewer**; limit them with `$filter.modes` |
59
59
 
60
60
  A sidebar footer with `universalSidebarFooter` does not check read mode, although the component's own description says its buttons are hidden then. A viewer of a shared configuration sees its actions. The `custom` footer renders its children unchanged as well.
61
61
 
@@ -152,6 +152,8 @@ entities:
152
152
 
153
153
  This is the way to go when you're dealing with content that might evolve over time. The message ID stays the same, so your existing translations remain valid even when you tweak the wording. Plus, you can use dot notation to create nice, organized namespaces for your translations.
154
154
 
155
+ A `$msg` compiles to a message descriptor. When an expression that holds one also declares a schema, the schema has to accept a message descriptor, such as `$ref: 'phx:Localizable'` or `$ref: 'phx:MessageDescriptor'`; a `$msg` declared as `type: string` or `type: number` is reported as `EXPRESSION_VALUE_TYPE_MISMATCH`.
156
+
155
157
  You can use both approaches throughout your domain specifications. Here are some common patterns that work well:
156
158
 
157
159
  ```yaml