@salsita-ai/sdk 0.50.0-develop.d87b28256e → 0.50.0-develop.d96df76afa

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 (398) hide show
  1. package/dist/application-spec/errors/diagnostic-codes.d.ts +9 -0
  2. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  3. package/dist/application-spec/errors/diagnostic-messages.d.ts +10 -1
  4. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  5. package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
  6. package/dist/application-spec/generate-application/code/templates/application-types.js +6 -6
  7. package/dist/application-spec/generate-application/code/templates/common/doc-comment.d.ts +2 -0
  8. package/dist/application-spec/generate-application/code/templates/common/doc-comment.js +1 -0
  9. package/dist/application-spec/generate-application/code/templates/configurator-globals.d.ts +2 -1
  10. package/dist/application-spec/generate-application/code/templates/configurator-globals.js +19 -6
  11. package/dist/application-spec/generate-application/code/templates/global-definition/generate-action-types.js +7 -7
  12. package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.d.ts +12 -0
  13. package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.js +7 -0
  14. package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.js +24 -22
  15. package/dist/application-spec/generate-application/code/templates/global-definition/index.d.ts +1 -0
  16. package/dist/application-spec/generate-application/code/templates/global-definition/index.js +1 -1
  17. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +2 -1
  18. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +2 -2
  19. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.d.ts +33 -3
  20. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
  21. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.d.ts +9 -0
  22. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.js +1 -0
  23. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.d.ts +4 -1
  24. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.js +1 -1
  25. package/dist/configurator/app-loader-content/DefaultAppLoaderContent.d.ts +2 -0
  26. package/dist/configurator/app-loader-content/DefaultAppLoaderContent.js +5 -0
  27. package/dist/configurator/app-loader-content/app-loader-content-name.d.ts +5 -0
  28. package/dist/configurator/app-loader-content/app-loader-content-name.js +1 -0
  29. package/dist/configurator/app-loader-content/index.d.ts +2 -0
  30. package/dist/configurator/app-loader-content/index.js +1 -0
  31. package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
  32. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
  33. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.d.ts +12 -2
  34. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -1
  35. package/dist/configurator/application-runtime/extensions/systems/canvas-system.d.ts +8 -0
  36. package/dist/configurator/application-runtime/extensions/systems/canvas-system.js +1 -1
  37. package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.d.ts +8 -0
  38. package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.js +1 -0
  39. package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.d.ts +23 -1
  40. package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.js +1 -1
  41. package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.d.ts +14 -1
  42. package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.js +1 -1
  43. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +45 -4
  44. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
  45. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.d.ts +14 -5
  46. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.js +1 -1
  47. package/dist/configurator/application-runtime/extensions/systems/index.d.ts +1 -1
  48. package/dist/configurator/common/configurator-interface.d.ts +90 -4
  49. package/dist/configurator/common/configurator-interface.js +1 -1
  50. package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.d.ts +3 -3
  51. package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.js +1 -1
  52. package/dist/configurator/configurator-context/PreviewContextProvider.js +1 -1
  53. package/dist/configurator/configurator-context/configurator-hooks/layout-hooks.d.ts +4 -0
  54. package/dist/configurator/configurator-context/configurator-routing/ConfiguratorRoutingProvider.js +1 -1
  55. package/dist/configurator/index.d.ts +1 -0
  56. package/dist/configurator/index.js +1 -1
  57. package/dist/configurator/pages/ConfiguratorErrorPage.d.ts +0 -1
  58. package/dist/configurator/pages/ConfiguratorErrorPage.js +1 -1
  59. package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
  60. package/dist/configurator/pages/default-configurator-elements.d.ts +4 -3
  61. package/dist/configurator/pages/default-configurator-elements.js +1 -1
  62. package/dist/configurator/pages/default-elements/index.d.ts +1 -0
  63. package/dist/configurator/pages/default-elements/index.js +1 -1
  64. package/dist/configurator/pages/default-elements/scene-controls/BuiltInAddEntityFab.d.ts +3 -0
  65. package/dist/configurator/pages/default-elements/scene-controls/BuiltInAddEntityFab.js +1 -0
  66. package/dist/configurator/pages/default-elements/scene-controls/BuiltInDeleteEntityFab.d.ts +3 -0
  67. package/dist/configurator/pages/default-elements/scene-controls/BuiltInDeleteEntityFab.js +1 -0
  68. package/dist/configurator/pages/default-elements/scene-controls/BuiltInNavigationHotspot.d.ts +3 -0
  69. package/dist/configurator/pages/default-elements/scene-controls/BuiltInNavigationHotspot.js +1 -0
  70. package/dist/configurator/pages/default-elements/scene-controls/BuiltInSelectionHotspot.d.ts +3 -0
  71. package/dist/configurator/pages/default-elements/scene-controls/BuiltInSelectionHotspot.js +1 -0
  72. package/dist/configurator/pages/default-elements/scene-controls/BuiltInToolMenuFab.d.ts +3 -0
  73. package/dist/configurator/pages/default-elements/scene-controls/BuiltInToolMenuFab.js +1 -0
  74. package/dist/configurator/pages/default-elements/scene-controls/index.d.ts +6 -0
  75. package/dist/configurator/pages/default-elements/scene-controls/index.js +1 -0
  76. package/dist/configurator/pages/default-elements/scene-controls/scene-control-elements.d.ts +6 -0
  77. package/dist/configurator/pages/default-elements/scene-controls/scene-control-elements.js +1 -0
  78. package/dist/configurator/scene/index.d.ts +1 -0
  79. package/dist/configurator/scene/index.js +1 -1
  80. package/dist/configurator/scene/scene-controls/ToolFab.d.ts +4 -0
  81. package/dist/configurator/scene/scene-controls/ToolFab.js +1 -1
  82. package/dist/configurator/scene/scene-controls/use-scene-control-tooltip.d.ts +2 -1
  83. package/dist/configurator/scene/scene-entity/scene-controls/SceneEntityHtmlHotspots.js +1 -1
  84. package/dist/configurator/toolbar-actions/index.d.ts +3 -0
  85. package/dist/configurator/toolbar-actions/index.js +1 -0
  86. package/dist/configurator/toolbar-actions/use-assistant-panel.d.ts +18 -0
  87. package/dist/configurator/toolbar-actions/use-assistant-panel.js +1 -0
  88. package/dist/configurator/toolbar-actions/use-measurements.d.ts +35 -0
  89. package/dist/configurator/toolbar-actions/use-measurements.js +1 -0
  90. package/dist/configurator/toolbar-actions/use-toolbar-actions.d.ts +100 -0
  91. package/dist/configurator/toolbar-actions/use-toolbar-actions.js +1 -0
  92. package/dist/configurator/toolbar-actions/use-toolbar-help.d.ts +7 -0
  93. package/dist/configurator/toolbar-actions/use-toolbar-help.js +1 -0
  94. package/dist/configurator/ui/assistant/AssistantChips.d.ts +3 -2
  95. package/dist/configurator/ui/assistant/AssistantChips.js +1 -1
  96. package/dist/configurator/ui/assistant/AssistantMarkdown.d.ts +1 -1
  97. package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -1
  98. package/dist/configurator/ui/assistant/alert/AssistantAlert.d.ts +8 -0
  99. package/dist/configurator/ui/assistant/alert/AssistantAlert.js +1 -0
  100. package/dist/configurator/ui/assistant/alert/index.d.ts +2 -0
  101. package/dist/configurator/ui/assistant/alert/index.js +1 -0
  102. package/dist/configurator/ui/assistant/alert/remark-alerts.d.ts +10 -0
  103. package/dist/configurator/ui/assistant/alert/remark-alerts.js +1 -0
  104. package/dist/configurator/ui/assistant/form/AssistantForm.js +1 -1
  105. package/dist/configurator/ui/assistant/form/compose-answer.d.ts +1 -1
  106. package/dist/configurator/ui/assistant/form/compose-answer.js +1 -1
  107. package/dist/configurator/ui/assistant/form/form-model.d.ts +3 -4
  108. package/dist/configurator/ui/assistant/form/form-state.d.ts +1 -1
  109. package/dist/configurator/ui/assistant/form/index.d.ts +2 -0
  110. package/dist/configurator/ui/assistant/form/index.js +1 -1
  111. package/dist/configurator/ui/assistant/form/parse-form-block.d.ts +3 -2
  112. package/dist/configurator/ui/assistant/form/parse-form-block.js +1 -1
  113. package/dist/configurator/ui/assistant/form/read-form-attributes.d.ts +1 -1
  114. package/dist/configurator/ui/assistant/form/read-form-attributes.js +1 -1
  115. package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.d.ts +3 -0
  116. package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.js +1 -0
  117. package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.d.ts +31 -0
  118. package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.js +1 -0
  119. package/dist/configurator/ui/assistant/form/{form-element-definition.d.ts → registry/form-element-definition.d.ts} +3 -1
  120. package/dist/configurator/ui/assistant/form/registry/form-element-definition.js +1 -0
  121. package/dist/configurator/ui/assistant/form/{form-registry.d.ts → registry/form-registry.d.ts} +2 -2
  122. package/dist/configurator/ui/assistant/form/registry/form-registry.js +1 -0
  123. package/dist/configurator/ui/assistant/form/registry/index.d.ts +6 -0
  124. package/dist/configurator/ui/assistant/form/registry/index.js +1 -0
  125. package/dist/configurator/ui/assistant/form/registry/use-form-registry.d.ts +8 -0
  126. package/dist/configurator/ui/assistant/form/registry/use-form-registry.js +1 -0
  127. package/dist/configurator/ui/assistant/form-elements/FormCard.d.ts +9 -0
  128. package/dist/configurator/ui/assistant/form-elements/FormCard.js +1 -0
  129. package/dist/configurator/ui/assistant/form-elements/FormConfirm.d.ts +9 -0
  130. package/dist/configurator/ui/assistant/form-elements/FormConfirm.js +1 -0
  131. package/dist/configurator/ui/assistant/form-elements/FormGroup.d.ts +4 -1
  132. package/dist/configurator/ui/assistant/form-elements/FormNumber.d.ts +9 -0
  133. package/dist/configurator/ui/assistant/form-elements/FormNumber.js +1 -0
  134. package/dist/configurator/ui/assistant/form-elements/FormOptions.d.ts +4 -1
  135. package/dist/configurator/ui/assistant/form-elements/FormReply.d.ts +4 -1
  136. package/dist/configurator/ui/assistant/form-elements/FormReply.js +1 -1
  137. package/dist/configurator/ui/assistant/form-elements/FormSwatch.d.ts +10 -0
  138. package/dist/configurator/ui/assistant/form-elements/FormSwatch.js +1 -0
  139. package/dist/configurator/ui/assistant/form-elements/FormText.d.ts +4 -1
  140. package/dist/configurator/ui/assistant/form-elements/FormText.js +1 -1
  141. package/dist/configurator/ui/assistant/form-elements/clamp-form-number.d.ts +10 -0
  142. package/dist/configurator/ui/assistant/form-elements/clamp-form-number.js +1 -0
  143. package/dist/configurator/ui/assistant/form-elements/form-element-props.d.ts +20 -16
  144. package/dist/configurator/ui/assistant/form-elements/index.d.ts +4 -0
  145. package/dist/configurator/ui/assistant/form-elements/index.js +1 -1
  146. package/dist/configurator/ui/assistant/index.d.ts +3 -0
  147. package/dist/configurator/ui/assistant/index.js +1 -1
  148. package/dist/configurator/ui/assistant/resource-image/index.d.ts +1 -1
  149. package/dist/configurator/ui/assistant/resource-image/index.js +1 -1
  150. package/dist/configurator/ui/assistant/resource-image/resource-path.d.ts +0 -9
  151. package/dist/configurator/ui/assistant/resource-image/resource-path.js +1 -1
  152. package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.d.ts +9 -0
  153. package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.js +1 -0
  154. package/dist/configurator/ui/assistant/screenshots/index.d.ts +2 -0
  155. package/dist/configurator/ui/assistant/screenshots/index.js +1 -0
  156. package/dist/configurator/ui/assistant/screenshots/tool-screenshots.d.ts +14 -0
  157. package/dist/configurator/ui/assistant/screenshots/tool-screenshots.js +1 -0
  158. package/dist/configurator/ui/assistant/streamed-markdown.d.ts +2 -2
  159. package/dist/configurator/ui/assistant/streamed-markdown.js +2 -2
  160. package/dist/configurator/ui/assistant/tool-chip.js +1 -1
  161. package/dist/configurator/ui/configurator-layout/slots/canvas/LayoutCanvas.js +1 -1
  162. package/dist/configurator/ui/configurator-layout/slots/canvas/SharedCanvas.js +1 -1
  163. package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.d.ts +5 -0
  164. package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.js +1 -0
  165. package/dist/configurator/ui/configurator-layout/universal/Layout.js +1 -1
  166. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -1
  167. package/dist/configurator/ui/index.d.ts +1 -0
  168. package/dist/configurator/ui/index.js +1 -1
  169. package/dist/configurator/ui/legacy/layout/common/ReactiveInterfaceLayout.js +1 -1
  170. package/dist/configurator/ui/phoenix-layout-elements/AppLoader.js +2 -6
  171. package/dist/configurator/ui/phoenix-layout-elements/AssistantToolbarButton.js +1 -1
  172. package/dist/configurator/ui/phoenix-layout-elements/LanguageMenuAction.js +1 -1
  173. package/dist/configurator/ui/phoenix-layout-elements/LanguageToolbarButton.js +1 -1
  174. package/dist/configurator/ui/phoenix-layout-elements/MeasurementsMenuAction.js +1 -1
  175. package/dist/configurator/ui/phoenix-layout-elements/MeasurementsToolbarButton.js +1 -1
  176. package/dist/configurator/ui/phoenix-layout-elements/ShareToolbarButton.js +1 -1
  177. package/dist/configurator/ui/phoenix-layout-elements/measurements/DimensionUnitSelector.js +1 -1
  178. package/dist/configurator/ui/phoenix-layout-elements/measurements/MeasurementsIndicatorsSwitch.js +1 -1
  179. package/dist/configurator/ui/phoenix-layout-elements/share/ShareModal.js +1 -1
  180. package/dist/configurator/ui/phoenix-layout-elements/share/use-share.d.ts +7 -8
  181. package/dist/configurator/ui/phoenix-layout-elements/share/use-share.js +1 -1
  182. package/dist/docs/complex-use-cases/focus-mode.mdx +2 -0
  183. package/dist/docs/fabs/basic.mdx +4 -0
  184. package/dist/docs/index.json +62 -1
  185. package/dist/docs/layouts/custom-layout.mdx +4 -2
  186. package/dist/docs/layouts/gallery/ardent-compressor.mdx +3516 -0
  187. package/dist/docs/layouts/gallery/fenwick-window.mdx +2803 -0
  188. package/dist/docs/layouts/gallery/ferrule-bar-station.mdx +6567 -0
  189. package/dist/docs/layouts/gallery/kestrel-kettle.mdx +1858 -0
  190. package/dist/docs/layouts/gallery/sunfold-solar.mdx +2683 -0
  191. package/dist/docs/other-features/client-side-rendering/declarative.mdx +1 -1
  192. package/dist/docs/other-features/navigation-hotspot.mdx +2 -0
  193. package/dist/docs/phoenix/configurator-interface.mdx +93 -10
  194. package/dist/docs/phoenix/layout-slots.mdx +4 -4
  195. package/dist/docs/phoenix/sdk-builder-workflow.mdx +1 -1
  196. package/dist/docs/phoenix/testing.mdx +2 -2
  197. package/dist/docs/selection-and-highlight/hotspots.mdx +2 -0
  198. package/dist/domain/application/assistant/assistant-manifest.d.ts +16 -1
  199. package/dist/domain/application/assistant/assistant-resolved-manifest.d.ts +8 -1
  200. package/dist/domain/application/assistant/build-assistant-manifest.js +1 -1
  201. package/dist/domain/application/assistant/form/assistant-form-element-name.d.ts +11 -0
  202. package/dist/domain/application/assistant/form/assistant-form-element-name.js +8 -0
  203. package/dist/domain/application/assistant/form/assistant-form-elements.d.ts +174 -0
  204. package/dist/domain/application/assistant/form/assistant-form-elements.js +1 -0
  205. package/dist/domain/application/assistant/form/assistant-resource-path.d.ts +10 -0
  206. package/dist/domain/application/assistant/form/assistant-resource-path.js +1 -0
  207. package/dist/domain/application/assistant/form/index.d.ts +4 -0
  208. package/dist/domain/application/assistant/form/index.js +1 -0
  209. package/dist/domain/application/assistant/generate-assistant-prompt.d.ts +3 -0
  210. package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -1
  211. package/dist/domain/application/assistant/index.d.ts +2 -3
  212. package/dist/domain/application/assistant/index.js +1 -1
  213. package/dist/domain/application/assistant/session-setup/apply-assistant-setup.js +1 -1
  214. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.d.ts +2 -0
  215. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.js +1 -1
  216. package/dist/domain/available-system-components.d.ts +1 -1
  217. package/dist/domain/available-system-components.js +1 -1
  218. package/dist/domain/domain-context/store-system/store-system.d.ts +5 -0
  219. package/dist/domain/domain-context/store-system/store-system.js +1 -1
  220. package/dist/domain/domain-runtime/domain-runtime.d.ts +4 -0
  221. package/dist/domain/domain-runtime/environment/types.d.ts +1 -1
  222. package/dist/domain/domain-runtime/spec/index.d.ts +1 -1
  223. package/dist/domain/domain-runtime/spec/index.js +1 -1
  224. package/dist/domain/domain-runtime/spec/initialize.js +1 -1
  225. package/dist/domain/domain-runtime/spec/module-checks/element-registration.d.ts +15 -1
  226. package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +1 -1
  227. package/dist/domain/domain-runtime/spec/module-checks/find-assistant-form-element-errors.d.ts +12 -0
  228. package/dist/domain/domain-runtime/spec/module-checks/find-assistant-form-element-errors.js +1 -0
  229. package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +2 -1
  230. package/dist/domain/domain-runtime/spec/module-checks/index.js +1 -1
  231. package/dist/domain/domain-runtime/spec/selectors.d.ts +3 -1
  232. package/dist/domain/domain-runtime/spec/selectors.js +1 -1
  233. package/dist/domain/extensions/export-images/agent-screenshot.d.ts +24 -0
  234. package/dist/domain/extensions/export-images/agent-screenshot.js +1 -0
  235. package/dist/domain/extensions/{client-side-image.d.ts → export-images/client-side-image.d.ts} +2 -2
  236. package/dist/domain/extensions/export-images/client-side-image.js +1 -0
  237. package/dist/domain/extensions/export-images/index.d.ts +3 -0
  238. package/dist/domain/extensions/export-images/index.js +1 -0
  239. package/dist/domain/extensions/{save-template-thumbnail.d.ts → export-images/save-template-thumbnail.d.ts} +2 -2
  240. package/dist/domain/extensions/export-images/save-template-thumbnail.js +1 -0
  241. package/dist/domain/extensions/index.d.ts +1 -2
  242. package/dist/domain/extensions/index.js +1 -1
  243. package/dist/domain/mocks/create-mock-domain-runtime.d.ts +2 -0
  244. package/dist/editor/domain-editors/DomainSpecContext.js +1 -1
  245. package/dist/editor/domain-editors/apply-spec-update.d.ts +8 -1
  246. package/dist/editor/domain-editors/apply-spec-update.js +1 -1
  247. package/dist/editor/domain-editors/use-implementation-files.js +1 -1
  248. package/dist/editor/screenshot/ScreenshotHandler.js +1 -1
  249. package/dist/i18n/locales/cs/messages.js +1 -1
  250. package/dist/i18n/locales/da/messages.js +1 -1
  251. package/dist/i18n/locales/de/messages.js +1 -1
  252. package/dist/i18n/locales/de-AT/messages.js +1 -1
  253. package/dist/i18n/locales/en/messages.js +1 -1
  254. package/dist/i18n/locales/en-AU/messages.js +1 -1
  255. package/dist/i18n/locales/en-GB/messages.js +1 -1
  256. package/dist/i18n/locales/es/messages.js +1 -1
  257. package/dist/i18n/locales/fr/messages.js +1 -1
  258. package/dist/i18n/locales/hu/messages.js +1 -1
  259. package/dist/i18n/locales/it/messages.js +1 -1
  260. package/dist/i18n/locales/nl/messages.js +1 -1
  261. package/dist/i18n/locales/pl/messages.js +1 -1
  262. package/dist/i18n/locales/pt/messages.js +1 -1
  263. package/dist/i18n/locales/ro/messages.js +1 -1
  264. package/dist/i18n/locales/sk/messages.js +1 -1
  265. package/dist/i18n/locales/tr/messages.js +1 -1
  266. package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +16 -0
  267. package/dist/legacy/domain-spec/spec/domain-spec.js +1 -1
  268. package/dist/react-sdk/application-runtime/common-services/analytics/frontend-analytics.d.ts +4 -0
  269. package/dist/react-sdk/application-runtime/hooks/index.d.ts +1 -0
  270. package/dist/react-sdk/application-runtime/hooks/index.js +1 -1
  271. package/dist/react-sdk/application-runtime/hooks/responsive/index.d.ts +3 -0
  272. package/dist/react-sdk/application-runtime/hooks/responsive/index.js +1 -0
  273. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.d.ts +9 -0
  274. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.js +1 -0
  275. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.d.ts +26 -0
  276. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.js +1 -0
  277. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.d.ts +10 -0
  278. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.js +1 -0
  279. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.d.ts +24 -0
  280. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.js +1 -0
  281. package/dist/react-sdk/application-runtime/hooks/use-phoenix-element-slots.d.ts +2 -1
  282. package/dist/react-sdk/application-runtime/index.d.ts +1 -0
  283. package/dist/react-sdk/application-runtime/integration/initialize.d.ts +7 -6
  284. package/dist/react-sdk/application-runtime/integration/initialize.js +1 -1
  285. package/dist/react-sdk/application-runtime/integration/selectors.d.ts +11 -2
  286. package/dist/react-sdk/application-runtime/integration/selectors.js +1 -1
  287. package/dist/react-sdk/application-runtime/integration/types.d.ts +5 -2
  288. package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.js +1 -1
  289. package/dist/react-sdk/application-runtime/responsive/actions.d.ts +47 -0
  290. package/dist/react-sdk/application-runtime/responsive/actions.js +1 -0
  291. package/dist/react-sdk/application-runtime/responsive/index.d.ts +4 -0
  292. package/dist/react-sdk/application-runtime/responsive/index.js +1 -0
  293. package/dist/react-sdk/application-runtime/responsive/initialize.d.ts +3 -0
  294. package/dist/react-sdk/application-runtime/responsive/initialize.js +1 -0
  295. package/dist/react-sdk/application-runtime/responsive/selectors.d.ts +20 -0
  296. package/dist/react-sdk/application-runtime/responsive/selectors.js +1 -0
  297. package/dist/react-sdk/application-runtime/responsive/types.d.ts +104 -0
  298. package/dist/react-sdk/application-runtime/routing/routing-service.d.ts +34 -2
  299. package/dist/react-sdk/application-runtime/routing/routing-service.js +1 -1
  300. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +91 -2
  301. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.js +1 -1
  302. package/dist/react-sdk/domain-context/common-hooks/index.d.ts +9 -0
  303. package/dist/react-sdk/domain-context/common-hooks/index.js +1 -0
  304. package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.d.ts +23 -0
  305. package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.js +1 -0
  306. package/dist/react-sdk/domain-context/component-hooks/index.d.ts +1 -0
  307. package/dist/react-sdk/domain-context/component-hooks/index.js +1 -0
  308. package/dist/react-sdk/domain-context/component-hooks/use-component.js +1 -1
  309. package/dist/react-sdk/domain-context/entity-hooks/use-domain-entity-state.js +1 -1
  310. package/dist/react-sdk/domain-context/entity-hooks/use-entity-price.js +1 -1
  311. package/dist/react-sdk/domain-context/entity-hooks/use-root-entity-id.js +1 -1
  312. package/dist/react-sdk/domain-context/entity-hooks/use-root-entity-state.js +1 -1
  313. package/dist/react-sdk/domain-context/index.d.ts +4 -0
  314. package/dist/react-sdk/domain-context/index.js +1 -1
  315. package/dist/react-sdk/domain-context/system-hooks/composition-hooks.js +1 -1
  316. package/dist/react-sdk/domain-context/system-hooks/dimension-hooks.js +1 -1
  317. package/dist/react-sdk/domain-context/system-hooks/generate-share-link-hooks.d.ts +8 -0
  318. package/dist/react-sdk/domain-context/system-hooks/generate-share-link-hooks.js +1 -0
  319. package/dist/react-sdk/domain-context/system-hooks/hover-hooks.js +1 -1
  320. package/dist/react-sdk/domain-context/system-hooks/locale-switch-hooks.d.ts +24 -0
  321. package/dist/react-sdk/domain-context/system-hooks/locale-switch-hooks.js +1 -0
  322. package/dist/react-sdk/domain-context/system-hooks/navigation-hooks.js +1 -1
  323. package/dist/react-sdk/domain-context/system-hooks/selection-hooks.js +1 -1
  324. package/dist/react-sdk/domain-context/system-hooks/share-links-hooks.d.ts +32 -0
  325. package/dist/react-sdk/domain-context/system-hooks/share-links-hooks.js +1 -0
  326. package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.js +1 -1
  327. package/dist/react-sdk/domain-context/system-hooks/undo-redo-hooks.js +1 -1
  328. package/dist/react-sdk/index.d.ts +1 -1
  329. package/dist/react-sdk/index.js +1 -1
  330. package/dist/react-sdk/loading-state/loading-state-manager.d.ts +5 -0
  331. package/dist/react-sdk/loading-state/loading-state-manager.js +1 -1
  332. package/dist/react-sdk/scene-control-elements/AddEntityFabElement.d.ts +36 -0
  333. package/dist/react-sdk/scene-control-elements/AddEntityFabElement.js +1 -0
  334. package/dist/react-sdk/scene-control-elements/DeleteEntityFabElement.d.ts +44 -0
  335. package/dist/react-sdk/scene-control-elements/DeleteEntityFabElement.js +1 -0
  336. package/dist/react-sdk/scene-control-elements/NavigationHotspotElement.d.ts +38 -0
  337. package/dist/react-sdk/scene-control-elements/NavigationHotspotElement.js +1 -0
  338. package/dist/react-sdk/scene-control-elements/SelectionHotspotElement.d.ts +30 -0
  339. package/dist/react-sdk/scene-control-elements/SelectionHotspotElement.js +1 -0
  340. package/dist/react-sdk/scene-control-elements/ToolMenuFabElement.d.ts +30 -0
  341. package/dist/react-sdk/scene-control-elements/ToolMenuFabElement.js +1 -0
  342. package/dist/react-sdk/scene-control-elements/create-add-entity-fab.d.ts +9 -0
  343. package/dist/react-sdk/scene-control-elements/create-add-entity-fab.js +1 -0
  344. package/dist/react-sdk/scene-control-elements/create-delete-entity-fab.d.ts +9 -0
  345. package/dist/react-sdk/scene-control-elements/create-delete-entity-fab.js +1 -0
  346. package/dist/react-sdk/scene-control-elements/create-navigation-hotspot.d.ts +10 -0
  347. package/dist/react-sdk/scene-control-elements/create-navigation-hotspot.js +1 -0
  348. package/dist/react-sdk/scene-control-elements/create-selection-hotspot.d.ts +9 -0
  349. package/dist/react-sdk/scene-control-elements/create-selection-hotspot.js +1 -0
  350. package/dist/react-sdk/scene-control-elements/create-tool-menu-fab.d.ts +9 -0
  351. package/dist/react-sdk/scene-control-elements/create-tool-menu-fab.js +1 -0
  352. package/dist/react-sdk/scene-control-elements/index.d.ts +11 -0
  353. package/dist/react-sdk/scene-control-elements/index.js +1 -0
  354. package/dist/react-sdk/scene-control-elements/scene-control-tooltip-visibility.d.ts +5 -0
  355. package/dist/react-sdk/web-component/create-web-component.js +1 -1
  356. package/dist/react-sdk/web-component/god-mode/god-mode-registry.d.ts +30 -0
  357. package/dist/react-sdk/web-component/god-mode/god-mode-registry.js +1 -0
  358. package/dist/react-sdk/web-component/god-mode/index.d.ts +1 -0
  359. package/dist/react-sdk/web-component/god-mode/index.js +1 -0
  360. package/dist/ui-sdk/icons/__generated/Lightbulb.d.ts +3 -0
  361. package/dist/ui-sdk/icons/__generated/Lightbulb.js +1 -0
  362. package/dist/ui-sdk/icons/__generated/XCircle.d.ts +3 -0
  363. package/dist/ui-sdk/icons/__generated/XCircle.js +1 -0
  364. package/dist/ui-sdk/icons/__generated/index.d.ts +4 -0
  365. package/dist/ui-sdk/icons/__generated/index.js +1 -1
  366. package/dist/ui-sdk/icons/icons-registry.js +1 -1
  367. package/dist/ui-sdk/responsive-application/ResponsiveApplicationProvider.d.ts +2 -2
  368. package/dist/ui-sdk/responsive-application/responsive-state-store.d.ts +11 -9
  369. package/dist/ui-sdk/responsive-application/use-store-responsive-info.js +1 -1
  370. package/dist/ui-sdk/theme/types.d.ts +5 -1
  371. package/package.json +13 -3
  372. package/schema/domain-spec.json +140 -0
  373. package/dist/configurator/ui/assistant/form/built-in-form-registry.d.ts +0 -10
  374. package/dist/configurator/ui/assistant/form/built-in-form-registry.js +0 -1
  375. package/dist/configurator/ui/assistant/form/form-registry.js +0 -8
  376. package/dist/configurator/ui/phoenix-layout-elements/language/use-locale-options.d.ts +0 -4
  377. package/dist/configurator/ui/phoenix-layout-elements/language/use-locale-options.js +0 -1
  378. package/dist/configurator/ui/phoenix-layout-elements/measurements/use-measurements-visibility.d.ts +0 -4
  379. package/dist/configurator/ui/phoenix-layout-elements/measurements/use-measurements-visibility.js +0 -1
  380. package/dist/domain/application/assistant/assistant-form-elements.d.ts +0 -28
  381. package/dist/domain/application/assistant/assistant-form-elements.js +0 -1
  382. package/dist/domain/extensions/client-side-image.js +0 -1
  383. package/dist/domain/extensions/save-template-thumbnail.js +0 -1
  384. package/dist/react-sdk/application-runtime/runtime/god-mode.d.ts +0 -19
  385. package/dist/react-sdk/application-runtime/runtime/god-mode.js +0 -1
  386. package/dist/react-sdk/responsive-state/ResponsiveStateProvider.d.ts +0 -25
  387. package/dist/react-sdk/responsive-state/ResponsiveStateProvider.js +0 -1
  388. package/dist/react-sdk/responsive-state/canvas-padding.d.ts +0 -14
  389. package/dist/react-sdk/responsive-state/index.d.ts +0 -5
  390. package/dist/react-sdk/responsive-state/index.js +0 -1
  391. package/dist/react-sdk/responsive-state/responsive-state-manager.d.ts +0 -111
  392. package/dist/react-sdk/responsive-state/responsive-state-manager.js +0 -1
  393. package/dist/react-sdk/responsive-state/use-responsive-state-manager.d.ts +0 -10
  394. package/dist/react-sdk/responsive-state/use-responsive-state-manager.js +0 -1
  395. package/dist/react-sdk/responsive-state/use-responsive-state.d.ts +0 -11
  396. package/dist/react-sdk/responsive-state/use-responsive-state.js +0 -1
  397. /package/dist/{configurator/ui/assistant/form/form-element-definition.js → react-sdk/application-runtime/responsive/types.js} +0 -0
  398. /package/dist/react-sdk/{responsive-state/canvas-padding.js → scene-control-elements/scene-control-tooltip-visibility.js} +0 -0
@@ -126,7 +126,7 @@ The SDK waits up to 10 seconds for each canvas flush and logs a `Force flush tim
126
126
 
127
127
  ### Known gaps
128
128
 
129
- Images render offscreen, and the live canvas shows a still of the current view while they generate, so animations pause until generation finishes. Without [Postprocessing](?path=/docs/other-features-client-side-rendering-postprocessing--docs), tone mapping and the sRGB conversion run once over the finished image instead of per material, as on the canvas. The scene renders over a transparent background, and a background colour (a Color `scene.background`, the image's `backgroundColor`, or white for JPEG) is composited behind it afterwards. That means:
129
+ Images render offscreen, and the live canvas shows a still of the current view while they generate, so animations pause until generation finishes. Exports run one after another, never interleaved, and while one is waiting or running the built-in universal layout does not respond: a press shows that the configurator is exporting. Afterwards it shows the configuration as currently committed. Without [Postprocessing](?path=/docs/other-features-client-side-rendering-postprocessing--docs), tone mapping and the sRGB conversion run once over the finished image instead of per material, as on the canvas. The scene renders over a transparent background, and a background colour (a Color `scene.background`, the image's `backgroundColor`, or white for JPEG) is composited behind it afterwards. That means:
130
130
 
131
131
  - materials with `toneMapped: false` are tone mapped in exported images;
132
132
  - a texture or environment `scene.background` in an sRGB colour space is tone mapped in exported images, while the canvas draws it without tone mapping;
@@ -96,6 +96,8 @@ Key observations:
96
96
  - With `pushToStack: true` and no `canStackOnto`, the new state is always pushed on top
97
97
  - With `canStackOnto: [Legs]`, the stack is "rewound" to `Legs` before pushing - any states after `Legs` are removed
98
98
 
99
+ The hotspot is the entity element `NavigationHotspot`. A project replaces its look by registering its own with `phx.frontend.createNavigationHotspot`, which receives `navigationState` and `onNavigate`, the navigation with the stack rules above. See [Configurator Interface → In-Scene Controls](?path=/docs/phoenix-configurator-interface--docs#in-scene-controls).
100
+
99
101
  ## Example
100
102
 
101
103
  This example demonstrates navigation hotspots with stack behavior. The sidebar shows breadcrumbs displaying the current navigation stack for easier understanding.
@@ -441,18 +441,87 @@ The helper types the page's props as `ConfiguratorErrorPageProps` from `@salsita
441
441
  - A registered page that throws does not take the configurator down: the built-in page is shown in its place and the error is logged, until a hot reload replaces or removes the page.
442
442
  - Three cases do not show the element. In development the configurator shows its detailed error pages instead. A failed preset creation with more than one preset is a snackbar on the preset screen (see [Loaded Configuration UI](#loaded-configuration-ui) for how a project shows it). The admin's own error page stays the built-in one, while the configurator the admin embeds renders the element.
443
443
 
444
+ ### App Loader
445
+
446
+ While the configurator loads, Phoenix shows the app loader: an overlay over the scene's safe area. A layout element registered under the name `AppLoaderContent` replaces what the overlay shows. It receives `phases`, the loading phases running now, each with an `id` and an optional `label` and `progress`; the list can be empty while the loader waits for the scene to start loading. `createAppLoader` registers a component under that name with the same `phases`:
447
+
448
+ ```typescript
449
+ // src/elements/AppLoader.tsx
450
+ export default phx.frontend.createAppLoader(({ phases }) => <span>{phases[0]?.label ?? 'Loading…'}</span>)
451
+ ```
452
+
453
+ - Phoenix keeps the overlay, its place over the safe area and its fade-in; the element is only what it shows.
454
+ - The overlay stays up while a loading phase runs: the session, the project's data, the configuration and the 3D scene. It waits for the scene only when the loaded configuration's layout mounts a canvas, as the built-in layouts and `LayoutSlots.SharedCanvas` do. A `ConfiguratorLayout` without a canvas is not held by the loader: the overlay leaves once the configuration is up.
455
+ - The top-level `appLoader: legacy` key in the spec turns the overlay off.
456
+ - The built-in content is the default `AppLoaderContent` registration in `defaultConfiguratorElements`, see below.
457
+
458
+ ### In-Scene Controls
459
+
460
+ The spec's built-in controls on the product are rendered through entity elements, one name per control. A project element registered under one of the names replaces that control everywhere the spec places it, in the built-in layouts and in a project layout. Without it, Phoenix renders its built-in control, which is the default registration of the name in `defaultConfiguratorElements`.
461
+
462
+ | Element name | Rendered for | Props besides `entityId` | Registration helper |
463
+ | ------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
464
+ | `AddEntityFab` | `sceneEntityAddFab` | `instanceNames`, `fixableViolations`, `icon`, `label`, `title`, `tooltipVisibility`, `skipMenuWhenOnlyOne`, `hideWhenNone` | `phx.frontend.createAddEntityFab` |
465
+ | `DeleteEntityFab` | `sceneEntityDeleteFab` | `icon`, `label`, `title`, `tooltipVisibility`, `onDelete`, `disabled`, `disabledHint`, `trace` | `phx.frontend.createDeleteEntityFab` |
466
+ | `SelectionHotspot` | `sceneEntitySelectHotspot` | `title`, `tooltipVisibility`, `onSelect` | `phx.frontend.createSelectionHotspot` |
467
+ | `NavigationHotspot` | `sceneEntityNavigationHotspot` | `navigationState`, `title`, `tooltipVisibility`, `onNavigate` | `phx.frontend.createNavigationHotspot` |
468
+ | `ToolMenuFab` | `toolMenuFab` | `options`, `icon`, `title`, `tooltipVisibility`, `onSelectTool` | `phx.frontend.createToolMenuFab` |
469
+
470
+ ```tsx
471
+ // src/elements/SceneControls.tsx
472
+ import { LocalizableMessage } from '@salsita-ai/sdk/react'
473
+
474
+ const AddEntityButtons = phx.frontend.createAddEntityFab(({ entityId, instanceNames, fixableViolations }) => {
475
+ const recipes = phx.frontend
476
+ .useGroupedRecipeOptions(entityId, instanceNames, fixableViolations)
477
+ .flatMap(group => group.recipes)
478
+
479
+ return (
480
+ <div className="brand-scene-buttons">
481
+ {recipes.map((recipe, index) => (
482
+ <button
483
+ key={index}
484
+ disabled={recipe.disabled}
485
+ title={recipe.tooltipTitle}
486
+ onClick={event => {
487
+ event.stopPropagation()
488
+ recipe.handleClick()
489
+ }}
490
+ >
491
+ <LocalizableMessage>{recipe.title}</LocalizableMessage>
492
+ </button>
493
+ ))}
494
+ </div>
495
+ )
496
+ })
497
+
498
+ const DeleteEntityButton = phx.frontend.createDeleteEntityFab(({ onDelete, disabled, disabledHint }) => (
499
+ <button className="brand-scene-button" onClick={onDelete} disabled={disabled} title={disabledHint}>
500
+ Remove
501
+ </button>
502
+ ))
503
+
504
+ export default [AddEntityButtons, DeleteEntityButton]
505
+ ```
506
+
507
+ - Each helper registers the element under its name with the props typed by `AddEntityFabElementProps`, `DeleteEntityFabElementProps`, `SelectionHotspotElementProps`, `NavigationHotspotElementProps` or `ToolMenuFabElementProps`, exported with the helpers from `@salsita-ai/sdk/react` and `@salsita-ai/sdk/configurator`. `createCustomPhoenixEntityElement` with the same name works too, with only `entityId` and `children` typed. The component may return `null` to show nothing.
508
+ - The props are the component's settings from the spec, with `tooltipVisibility` already defaulted to `on-hover`, and the actions the built-in control performs. `onDelete` deletes the entity, `onSelect` selects it and `onNavigate` goes to `navigationState`; each pushes the same analytics event as the built-in control, and each can be passed straight to `onClick`: given the click, it stops its propagation, so the click does not also reach the scene. `onSelectTool(toolState)` switches to the tool state of one of the `options`. `disabled` and `disabledHint` say whether deleting would cause a violation the component's `hiddenViolations` do not allow, and why. `trace` logs the deletion's dry run to the console.
509
+ - The add FAB gets no recipes: it reads them with `phx.frontend.useGroupedRecipeOptions(entityId, instanceNames, fixableViolations)`. Each recipe has `handleClick`, which adds it, `disabled` and `tooltipTitle`. Every recipe is dry-run to tell whether it is allowed, so the built-in FAB reads them only while its menu is open, or up front when `skipMenuWhenOnlyOne` applies; a replacement with a menu does the same. Its `handleClick` does not stop the click's propagation, so the replacement does.
510
+ - Where and when the control shows stays with the spec and the scene: the offsets, `showWhen`, the camera limits, `hideWhileDragging` and `occlude` apply to the element as they did to the built-in control. The element is rendered into the scene's HTML layer, with the theme and every `phx.frontend` hook.
511
+ - A project's own control, one the SDK has no built-in for, is an `htmlHotspot` that names an element of the project, as the edge hotspot of [Custom Layout](?path=/docs/layouts-custom-layout--docs#put-a-control-on-the-product) is.
512
+
444
513
  ### Loaded Configuration UI
445
514
 
446
515
  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.
447
516
 
448
517
  ```typescript
449
518
  // src/elements/ConfiguratorLayout.tsx
450
- import { LayoutSlots, useSceneSafeArea } from '@salsita-ai/sdk/configurator'
519
+ import { LayoutSlots } from '@salsita-ai/sdk/configurator'
451
520
 
452
521
  const SIDEBAR_WIDTH = 320
453
522
 
454
523
  export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', () => {
455
- const { containerRef, safeAreaRef } = useSceneSafeArea()
524
+ const { containerRef, safeAreaRef } = phx.frontend.useSceneSafeArea()
456
525
 
457
526
  return (
458
527
  <div ref={containerRef} style={{ position: 'absolute', inset: 0 }}>
@@ -471,20 +540,20 @@ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout
471
540
  ```
472
541
 
473
542
  - `LayoutSlots.SharedCanvas` renders the scene. It is absolutely positioned and fills its nearest positioned ancestor, so the layout's root is positioned and covers the page. The layout positions its own UI above the canvas and after it in the markup, as the `aside` does; unpositioned content is painted under the canvas.
474
- - `useSceneSafeArea()`, called without arguments, tells the camera which part of the canvas the UI leaves free. Attach `containerRef` to the element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in: the hook measures the gap between the two and the camera frames the scene inside the safe area. Without it the scene is framed in the whole canvas. The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'` to leave orbiting and clicking to the scene. A `canvasPadding` component in the spec scales the measured gaps, see [Canvas Padding](?path=/docs/layouts-canvas-padding--docs).
475
- - The layout has to mount `LayoutSlots.SharedCanvas`, or the project sets `appLoader: legacy`. Otherwise the app loader stays on screen.
543
+ - `phx.frontend.useSceneSafeArea()`, called without arguments, tells the camera which part of the canvas the UI leaves free. Attach `containerRef` to the element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in: the hook measures the gap between the two and the camera frames the scene inside the safe area. Without it the scene is framed in the whole canvas. The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'` to leave orbiting and clicking to the scene. A `canvasPadding` component in the spec scales the measured gaps, see [Canvas Padding](?path=/docs/layouts-canvas-padding--docs). Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it.
544
+ - With `LayoutSlots.SharedCanvas` mounted, the app loader stays on screen until the scene has loaded. A layout without a canvas is not held by the loader, see [App Loader](#app-loader).
476
545
  - The layout composes the rest from what `@salsita-ai/sdk/configurator` exports: `LayoutSlots.Services` and the other members of `LayoutSlots`, which [Layout Slots](?path=/docs/phoenix-layout-slots--docs) lists with what each shows, what feeds it, what it renders when the spec configures nothing for it and what it does in read mode.
477
546
  - `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).
478
- - 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`.
547
+ - 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 `phx.frontend.usePreview().isPreview` is set. Both previews frame the scene themselves, so the layout renders the component that calls `phx.frontend.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`.
479
548
  - 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).
480
549
  - 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. With a single preset it is the [Configurator Error Page](#configurator-error-page). A finalize action the layout runs with `phx.frontend.useFinalizeActions` or `phx.frontend.useFinalizeAction` reports its error as the action's `error` instead, neither as a message nor as a snackbar.
481
550
  - Any project layout element named `ConfiguratorLayout` takes over the page.
482
551
 
483
- `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, the built-in preset screen as a `PresetUi` registration, and the built-in error page as a `ConfiguratorErrorPage` 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.
552
+ `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, the built-in preset screen as a `PresetUi` registration, the built-in error page as a `ConfiguratorErrorPage` registration, the app loader's built-in content as an `AppLoaderContent` registration, and the built-in in-scene controls under the names of [In-Scene Controls](#in-scene-controls). 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.
484
553
 
485
554
  #### Hooks for a Project Layout
486
555
 
487
- A project layout reads the product and the configurator's state through hooks, and changes them through the functions the hooks return. Each row names where its hook comes from; the `phx.frontend` hooks in these tables are also exported from `@salsita-ai/sdk/react` under the same name. No hook looks at read mode: the control hooks and the navigation and fullscreen setters keep working while `accessInfo.viewMode` is `true`, so the layout decides what a viewer can change.
556
+ A project layout reads the product and the configurator's state through hooks, and changes them through the functions the hooks return. Every hook in these tables is a member of `phx.frontend` and is also exported from `@salsita-ai/sdk/react` under the same name, unless its row names another export. No hook looks at read mode: the control hooks and the navigation and fullscreen setters keep working while `accessInfo.viewMode` is `true`, so the layout decides what a viewer can change.
488
557
 
489
558
  **The product**
490
559
 
@@ -507,7 +576,7 @@ The other control hooks in [Controls](#controls) take the same two arguments, `u
507
576
 
508
577
  **Texts**
509
578
 
510
- Texts from the spec, such as option titles and the `detail` fields, are `Localizable`, not `string`. `useResolveLocalizable()` from `@salsita-ai/sdk/react` returns a function that turns one into a string in the current language, which suits a list; `useLocalizable(text)` and `<LocalizableMessage>` resolve a single text, see [Localization](?path=/docs/phoenix-localization--docs).
579
+ Texts from the spec, such as option titles and the `detail` fields, are `Localizable`, not `string`. `phx.frontend.useResolveLocalizable()` returns a function that turns one into a string in the current language, which suits a list; `phx.frontend.useLocalizable(text)` and `<LocalizableMessage>` from `@salsita-ai/sdk/react` resolve a single text, see [Localization](?path=/docs/phoenix-localization--docs).
511
580
 
512
581
  **Mode, fullscreen, preview and navigation**
513
582
 
@@ -515,7 +584,8 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
515
584
  | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
516
585
  | `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. |
517
586
  | `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it. |
518
- | `usePreview()` from `@salsita-ai/sdk/configurator` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. |
587
+ | `phx.frontend.usePreview()` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
588
+ | `phx.frontend.useCloseAction()` | The host's close action, `() => Promise<void>`, or `null` when the integration mode offers none. It dispatches the web component's `close` event and, depending on the integration mode, first leaves for the go-back URL of the page's query or puts the configurator on standby. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
519
589
  | `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`. |
520
590
  | `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. |
521
591
  | `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state. |
@@ -524,7 +594,7 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
524
594
  | `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. |
525
595
  | `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. |
526
596
  | `phx.frontend.useStepNavigation()` | `{ steps, activeStep, activeIndex, isFirst, isLast, canGoNext, canGoPrevious, next, previous, goTo }` for the spec's `navigationSteps`, read from the first active instance on the root entity, the one the built-in layout shows. Each step is `{ index, label, navigationStates, state, status }`, with the label resolved, `state` the navigation state that enters the step and `status` `'complete'`, `'active'` or `'upcoming'`; while the active navigation state belongs to no step, `activeStep` is `null`, `activeIndex` is -1, every step is `'upcoming'` and the next step is the first one. A single step is both first and last. `next()` and `previous()` enter the following or preceding step, as the built-in step buttons do, pushing the same analytics events; `goTo(index or navigation state)` enters any step, as the header's stepper does, pushing its event. Each does nothing without a step to enter. A step without navigation states is left out, with a warning in development. It enforces no order and does not check read mode. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
527
- | `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. |
597
+ | `phx.frontend.useActiveViewports()` | 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. |
528
598
 
529
599
  **Steps and tabs**
530
600
 
@@ -558,6 +628,18 @@ export const Wizard = () => {
558
628
  }
559
629
  ```
560
630
 
631
+ **Toolbar**
632
+
633
+ A layout builds its toolbar from `phx.frontend.useToolbarActions()`, which returns the entries of the spec's `toolbar`, or of the default toolbar without one, as data with the function that runs each; or it places single actions with the per-action hooks below. Each hook does what the built-in toolbar button of the same action does and pushes the same analytics events; opening and closing the layout's own dialogs and popovers push none. `useToolbarActions`, `useMeasurements` and `useAssistantPanel` are exported from `@salsita-ai/sdk/configurator` besides `phx.frontend`, not from `@salsita-ai/sdk/react`. `useToolbarState()` from `@salsita-ai/sdk/configurator` is deprecated in favour of `useToolbarActions()`, or of mounting `LayoutSlots.DesktopToolbar` to keep project element overrides, and `useShareData()` and `useShareSettings()` in favour of `useShareLinks()`. Undo and redo for a single button are `phx.frontend.useUndoRedo()` above, fullscreen is `useIsFullscreenActive()` with `useSetFullscreen()`, and the camera reset is `useResetCamera()`.
634
+
635
+ | Hook | Returns |
636
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
637
+ | `phx.frontend.useToolbarActions(placement?)` | `{ groups, actions }` for `placement` `'desktop'` (the default), `'mobile'` or `'mobileMenu'`, the `toolbar` component's list of that name. `groups` follows the spec's `toolbarButtonGroup`s, an element outside a group being a group of one, and `actions` is the same entries flattened. Each entry has `key`, `kind`, `label` (the built-in button's title in the current language), and `icon` and `showTitle` from the element's props. By `kind`: `undo` and `redo` with `disabled` and `run`; `fullscreen` with `active` and `run`, which toggles the fullscreen flag; `resetCamera` with `run`; `share`, for which the layout opens its own dialog over `useShareLinks`; `measurements` with `measurements`, the result of `useMeasurements` with the element's `showIndicatorSwitch` applied, and `icons`, the element's icon per unit; `language` with `language`, the result of `useLocaleSwitch`; `help` with `Content`, a component that renders the element's children or else the help registered with `createToolbarHelp`; `assistant` with `assistant`, the result of `useAssistantPanel`, and `run`, which opens or closes the panel; `custom` with `name` and `Element`, which renders the project element a `customLayoutElement` names. It leaves out an entry with nothing to offer, such as share with sharing disabled or language with one locale, and an element that is not a toolbar entry. A project element registered under a built-in kind, such as `undoToolbarButton`, replaces that button in `LayoutSlots.DesktopToolbar` but not here. As with `LayoutSlots.DesktopToolbar`, the default toolbar leaves out share, undo and redo for a viewer, and a configured `toolbar` is returned as written. Four differences from the built-in toolbar: the entries keep the spec's order for every placement, where `LayoutSlots.MobileToolbar` renders the `mobile` list reversed; a `toolbarButtonGroup` nested in a group is flattened into it, where the built-in toolbar nests it; the assistant's `showTitle` is the spec's, where the built-in button also hides its title on a phone or tablet-sized layout; and a `helpMenuAction` without children shows the registered help, where the built-in menu action shows an empty dialog. |
638
+ | `phx.frontend.useMeasurements(options?)` | `{ units, unit, setUnit, hasIndicatorSwitch, indicatorsShown, toggleIndicators, available }`: the units the dimension configuration offers, the one dimensions are shown in and a function that changes it; whether the dimension indicators can be switched (`options.showIndicatorSwitch`, else `applicationConfig.indicatorSwitch`, else `true`), whether they show and a function that switches them; and whether there is anything to set, more than one unit or the switch. |
639
+ | `phx.frontend.useLocaleSwitch()` | `{ locales, locale, setLocale, available }`: the spec's `availableLocales` as `{ locale, displayName }`, each named in its own language, the active locale, a function that activates one, and whether there is more than one to choose from. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
640
+ | `phx.frontend.useShareLinks(options?)` | `{ available, ownerLinkAvailable, readLinkAvailable, ownerLink, readLink, isLoading }`: whether sharing is offered and which kinds of link the application config enables, a link that opens the configuration for editing and one that opens its current revision for viewing, each `null` until generated, and whether one is still being generated. While enabled, each commit of the configuration generates a new read link. Generating a link saves the configuration, so a layout passes `{ enabled: open }` with whether its share dialog is open; `enabled` is `true` by default. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
641
+ | `phx.frontend.useAssistantPanel()` | `{ available, open, hasUnreadReply, setOpen }`: whether the spec has an `assistant`, whether its panel is open, whether it replied while closed, and a function that opens or closes it. The panel renders only inside `LayoutSlots.DesktopSidebar`, so a layout without that slot does not offer it. |
642
+
561
643
  **Violations**
562
644
 
563
645
  | Hook | Returns |
@@ -565,6 +647,7 @@ export const Wizard = () => {
565
647
  | `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. |
566
648
  | `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`. |
567
649
  | `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`. |
650
+ | `phx.frontend.useConfigurationSaveState()` | `{ revisionId, isSaving, failure }`, the save status of the configuration. An editable configuration saves every committed change on its own, three seconds after the last one, and a read-only one never saves. `revisionId` is the revision the configuration is at: the loaded one, then the one the latest save created. `isSaving` is `true` while a save runs. `failure` is `{ error, timestamp }` of the latest failed save, with `timestamp` in milliseconds since the epoch, and `null` once a later save succeeds or another configuration loads. A layout shows a save indicator with it; the messages of a failed save and of its recovery come from `useConfiguratorMessages`. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
568
651
  | `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, does nothing while `disabled` or `isPending`, and returns a promise that resolves once the handler has settled and never rejects; `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. |
569
652
  | `phx.frontend.useFinalizeActions()` | `{ actions, primary, secondary, disabled, hints }`: every active `finalizeAction` of the spec in spec order, `primary` the first `'primary'` one, or else the first, or `null`, and `secondary` the others; see the Finalize paragraph below. Each action is `{ key, handler, variant, label, title, icon, run, isPending, error, disabled }`, with `label` and `title` resolved; `run()` records the finalize-button click for analytics and runs the action's handler, does nothing while `disabled` or `isPending`, and returns a promise that never rejects. `disabled` and `hints` are those of `useFinalizeViolations()`. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
570
653
 
@@ -26,8 +26,8 @@ export const Toolbar = () => (
26
26
 
27
27
  - A slot takes no props unless its row names them, and reads what it shows from the spec and the configurator's state. It renders inside the configurator only, under a `ConfiguratorLayout`.
28
28
  - A slot brings its own look but not its place: it adds no padding or scrolling around itself, so the layout puts it in a container. Two members differ: `SharedCanvas` fills its nearest positioned ancestor, and `DesktopSidebar` brings the sidebar's spacing and scrolling with it.
29
- - Read mode means `useAccessService().accessInfo.viewMode` is `true`: a configuration shared for viewing is open, or the host sets the `readonly` property. "No change" in the read-mode column means the slot renders the same for a viewer, so the layout decides whether a viewer sees it.
30
- - `Desktop*` and `Mobile*` members are twins that differ in size and arrangement; [Phone layout](#phone-layout) lists the phone ones. The container's width, not the window's, decides which one fits: `useActiveViewports()` from `@salsita-ai/sdk/react` returns the breakpoints the container satisfies. The built-in layout uses the `Desktop*` members while `desktop` is active, for a container wider than 768 pixels, and the `Mobile*` members otherwise, while `mobile` is active; `phone` means narrower than 480 pixels and `tablet` between 480 and 1024.
29
+ - Read mode means `phx.frontend.useAccessService().accessInfo.viewMode` is `true`: a configuration shared for viewing is open, or the host sets the `readonly` property. "No change" in the read-mode column means the slot renders the same for a viewer, so the layout decides whether a viewer sees it.
30
+ - `Desktop*` and `Mobile*` members are twins that differ in size and arrangement; [Phone layout](#phone-layout) lists the phone ones. The container's width, not the window's, decides which one fits: `phx.frontend.useActiveViewports()`, also exported from `@salsita-ai/sdk/react`, returns the breakpoints the container satisfies. The built-in layout uses the `Desktop*` members while `desktop` is active, for a container wider than 768 pixels, and the `Mobile*` members otherwise, while `mobile` is active; `phone` means narrower than 480 pixels and `tablet` between 480 and 1024.
31
31
 
32
32
  ## Canvas and services
33
33
 
@@ -40,7 +40,7 @@ export const Toolbar = () => (
40
40
  | `SideEffects` | An info snackbar for each committed update that carries a hint | The hints of committed updates | Nothing | No change |
41
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
- - 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`.
43
+ - Mount `SharedCanvas`, not `Canvas`: `Canvas` suspends while it probes the GPU on its first mount, and `SharedCanvas` holds the boundary that catches it. `SharedCanvas` also tells the app loader to wait for the scene from the moment it mounts; `Canvas` alone does so only once the probe is done, so the loader can leave and come back. A layout with neither is not held by the loader for a scene.
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
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.useFinalizeActions` or `phx.frontend.useFinalizeAction` raises no snackbar; its error is the action's `error`.
46
46
 
@@ -79,7 +79,7 @@ Instead of `DesktopViewControls` or `MobileViewControls`, a layout can build its
79
79
  | `DesktopReadonlyBanner` | "You're viewing a read-only configuration" with an **Edit configuration** button that opens `CloneToEditDialog`. Props: the MUI `Alert` props and `icon`, also settable in the theme as `PhxDesktopReadonlyBanner` | `useIsCloningAllowed()`; its icon from the application config's `readonlyBannerIcon` | Nothing unless cloning is allowed | **It does not check read mode.** The layout renders it only in read mode, as the built-in layout does while not in fullscreen |
80
80
  | `MobileReadonlyBanner` | An **Edit** button that opens `CloneToEditDialog`. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxMobileReadonlyBanner` | As `DesktopReadonlyBanner` | Nothing unless cloning is allowed | As `DesktopReadonlyBanner` |
81
81
  | `CloneToEditDialog` | The "Edit configuration" dialog that creates an editable copy of the configuration and opens it. Props, typed `CloneToEditDialogProps`: `open` and `onClose` | The open configuration | — | — |
82
- | `useIsCloningAllowed()` | A hook, `boolean`: `true` when the application config sets `shareCloneToEdit` and the session's role may create configurations | The application config and the session | `false` | Does not check read mode |
82
+ | `useIsCloningAllowed()` | A hook, also `phx.frontend.useIsCloningAllowed()`, returning a `boolean`: `true` when the application config sets `shareCloneToEdit` and the session's role may create configurations | The application config and the session | `false` | Does not check read mode |
83
83
  | `DesktopPoweredBy` / `MobilePoweredBy` | The "Powered by Salsita" mark, a link | Its props `visible`, `variant` (`'black'` or `'white'`), `href`, `title`, `slots` and `slotProps`. All but `href` and `title` are also settable in the theme as `PhxDesktopBrandingPoweredBy` / `PhxMobileBrandingPoweredBy` | The mark | No change |
84
84
 
85
85
  ## Page footer
@@ -95,7 +95,7 @@ Tell us what is broken, confusing or missing, from wherever you are:
95
95
 
96
96
  - In Studio, **Send beta feedback** in the sidebar opens a short form.
97
97
  - In the terminal, `pnpm phoenix feedback "<message>" --type bug|idea|other` sends it with your sign-in, and `--file <path>` sends a text file instead of the argument. See the [CLI reference](?path=/docs/phoenix-phoenix-cli--docs).
98
- - On a build's page in Studio, **Download sources** downloads the archive `phoenix deploy` uploaded for that build, so a report only needs to name the build.
98
+ - On a build's page in Studio, **Download** → **Sources** downloads the archive `phoenix deploy` uploaded for that build, so a report only needs to name the build.
99
99
 
100
100
  Coding agents working in your project use the same command, guided by the generated `phoenix-feedback` skill. They never send on their own: an agent shows you a draft, right away when the problem is crucial and otherwise at the end of the task, and sends it only after you approve it. A report names the build with its Studio page link and the live configurator, says what the agent was doing, what happened and what it expected, quotes the exact error and ends with a condensed conversation log. Agents are told never to include secrets, credentials or personal data; parts of your project and your own words go in only through a draft you approved. When you are done with the configurator, the agent also asks you, once per conversation, to evaluate the run: minutes spent, then scores from 0 to 10 for the agent's guidance, functionality, UX, 3D quality, UI visuals and the overall result, and your comments. It asks one question at a time, never picks a score for you, tells you the answers go to the Phoenix team, shows the message and sends it only after you approve it.
101
101
 
@@ -34,8 +34,8 @@ handler, element and transformer; it reads the frontend environment when it is i
34
34
  uses it stubs `phx.frontendEnv` first.
35
35
 
36
36
  A test that mounts the configurator's UI builds from `configuratorConfig`: it carries the SDK's default
37
- elements, the built-in layout, preset screen, not-found, not-authenticated and error pages, controls, toolbar
38
- buttons and sidebar-footer pieces, which `configuratorBuilder` does not, so a test that mounts the UI from `createTestApplicationConfig(configuratorBuilder)`
37
+ elements, the built-in layout, preset screen, not-found, not-authenticated and error pages, app loader content,
38
+ controls, toolbar buttons and sidebar-footer pieces, which `configuratorBuilder` does not, so a test that mounts the UI from `createTestApplicationConfig(configuratorBuilder)`
39
39
  shows a "not found" box in place of every built-in element. Code that assembles a config without the
40
40
  generated files calls `.withDefaultElements(defaultConfiguratorElements)` on its configurator builder, with
41
41
  the list from `@salsita-ai/sdk/configurator`, or `defaultAdminElements` from `@salsita-ai/sdk/admin` for
@@ -56,6 +56,8 @@ sceneEntitySelectHotspot:
56
56
 
57
57
  The `title` supplies the tooltip text. Tooltips appear on hover by default; `tooltipVisibility` can instead keep them visible continuously, while the camera is still, or hide them. The `showWhen` property controls visibility based on selection state. The default `['unselected']` hides hotspots once entities are selected, keeping the view clean. Setting it to `['selected', 'unselected']` keeps hotspots always visible.
58
58
 
59
+ The hotspot is the entity element `SelectionHotspot`. A project replaces its look by registering its own with `phx.frontend.createSelectionHotspot`, which receives `onSelect`, the selection with its analytics event; the settings above still apply. See [Configurator Interface → In-Scene Controls](?path=/docs/phoenix-configurator-interface--docs#in-scene-controls).
60
+
59
61
  ## System Component Interfaces
60
62
 
61
63
  ```typescript
@@ -1,3 +1,4 @@
1
+ import type { AssistantFormElementDescriptor } from './form/index.js';
1
2
  /**
2
3
  * Where one exposed assistant GraphQL field reads its value on the `$` entity: a stored field name,
3
4
  * or an expression name including its `$`. `localized` marks a localizable value that resolves to a
@@ -35,6 +36,16 @@ export interface AssistantFieldOptions {
35
36
  readonly validate: boolean;
36
37
  readonly dryRuns: boolean;
37
38
  }
39
+ /**
40
+ * One view the assistant manifest lets the agent take a screenshot of an entity from: `view` is the
41
+ * value of the `<Type>ScreenshotView` enum the agent asks for it by, `instanceName` the
42
+ * `agentScreenshot` component instance that renders it, and `description` what the view shows.
43
+ */
44
+ export interface AssistantScreenshotView {
45
+ readonly view: string;
46
+ readonly instanceName: string;
47
+ readonly description: string;
48
+ }
38
49
  /**
39
50
  * Experimental assistant manifest generated from the application spec `assistant` section. `sdl` is
40
51
  * the GraphQL schema text rebuilt from the resolved manifest; `fields` maps each GraphQL object type's exposed
@@ -46,7 +57,8 @@ export interface AssistantFieldOptions {
46
57
  * the recipe groups `meta { childRecipes }` and `addChild` expose, in manifest order, and absent
47
58
  * means none; `replaceRecipes` lists the same for `meta { replaceRecipes }` and `replaceEntity`;
48
59
  * `fieldOptions` lists, per entity type, the options edges `meta` exposes for its
49
- * writable fields, and absent means none; `actions` maps each custom `Mutation` field to the
60
+ * writable fields, and absent means none; `screenshots` lists, per entity type, the views
61
+ * `meta { screenshot }` renders, and absent means none; `actions` maps each custom `Mutation` field to the
50
62
  * declared action it runs, and absent means none; `lenses` holds the generated lens fragments, and
51
63
  * absent means none: `declarations` is the text the prompt shows, each lens once with its relations
52
64
  * spreading the lens itself, and `definitions` the valid GraphQL injected into an operation that
@@ -63,9 +75,12 @@ export interface AssistantManifest {
63
75
  readonly childRecipes?: Readonly<Record<string, readonly AssistantRecipeGroup[]>>;
64
76
  readonly replaceRecipes?: Readonly<Record<string, readonly AssistantRecipeGroup[]>>;
65
77
  readonly fieldOptions?: Readonly<Record<string, readonly AssistantFieldOptions[]>>;
78
+ readonly screenshots?: Readonly<Record<string, readonly AssistantScreenshotView[]>>;
66
79
  readonly actions?: Readonly<Record<string, string>>;
67
80
  readonly lenses?: {
68
81
  readonly declarations: string;
69
82
  readonly definitions: string;
70
83
  };
84
+ /** The project's own form elements, in YAML order; absent means none. */
85
+ readonly formElements?: readonly AssistantFormElementDescriptor[];
71
86
  }
@@ -1,4 +1,5 @@
1
- import type { AssistantFieldOptions, AssistantRecipeGroup } from './assistant-manifest.js';
1
+ import type { AssistantFieldOptions, AssistantRecipeGroup, AssistantScreenshotView } from './assistant-manifest.js';
2
+ import type { AssistantFormElementDescriptor } from './form/index.js';
2
3
  /** JSON description of a scalar or recursively nested list, including nullability at each level. */
3
4
  export type AssistantValueType = {
4
5
  readonly kind: 'scalar';
@@ -50,12 +51,16 @@ export interface AssistantResolvedManifest {
50
51
  readonly childRecipes: Readonly<Record<string, AssistantResolvedRecipes>>;
51
52
  readonly replaceRecipes: Readonly<Record<string, AssistantResolvedRecipes>>;
52
53
  readonly optionsEdges: Readonly<Record<string, readonly AssistantResolvedOptionsEdge[]>>;
54
+ /** The screenshot views of each entity type with at least one, aliases expanded, in manifest order. */
55
+ readonly screenshots: Readonly<Record<string, readonly AssistantScreenshotView[]>>;
53
56
  readonly actionFields: readonly AssistantResolvedAction[];
54
57
  /** The declared lenses by name: when to use each, and how many levels its tagged relations nest. */
55
58
  readonly lenses: Readonly<Record<string, {
56
59
  readonly description: string;
57
60
  readonly maxDepth: number;
58
61
  }>>;
62
+ /** The project's own form elements, in YAML order, which the prompt lists after the built-ins and the panel renders with their registered components. */
63
+ readonly formElements: readonly AssistantFormElementDescriptor[];
59
64
  }
60
65
  /** Optional mutation input for one stored field, using its exposed name. */
61
66
  export interface AssistantWritableField {
@@ -113,6 +118,8 @@ export interface AssistantDeclarations {
113
118
  readonly writable: Readonly<Record<string, Readonly<Record<string, AssistantDeclaredWrite>>>>;
114
119
  readonly childRecipes: Readonly<Record<string, AssistantResolvedRecipes>>;
115
120
  readonly replaceRecipes: Readonly<Record<string, AssistantResolvedRecipes>>;
121
+ /** The screenshot views under the entity type or alias key that declares them. */
122
+ readonly screenshots: Readonly<Record<string, readonly AssistantScreenshotView[]>>;
116
123
  }
117
124
  /** An original field declaration, including metadata needed to reapply inheritance and enum descriptions. */
118
125
  export interface AssistantDeclaredField extends AssistantResolvedField {
@@ -1 +1 @@
1
- import{GraphQLBoolean as e,GraphQLID as t,GraphQLInputObjectType as n,GraphQLInterfaceType as i,GraphQLList as r,GraphQLNonNull as s,GraphQLObjectType as o,GraphQLSchema as a,GraphQLString as p,printSchema as d,validateSchema as c}from"graphql";import{GraphQLJSON as l,createAssistantValueType as h}from"./assistant-value-type.js";import{buildAssistantLenses as y}from"./build-assistant-lenses.js";import{createAssistantExecutionSchema as w}from"./create-assistant-execution-schema.js";let u="AnyDomainEntity",m="RootEntity",f=[["navigationState","Active navigation state.","navigationStates"],["viewState","Active view state.","viewStates"],["toolState","Active tool state.","toolStates"]],g={section:"childRecipes",mutation:"addChild"},b={section:"replaceRecipes",mutation:"replaceEntity"},j=(e,t)=>Object.entries(e).find(([e])=>e===t)?.[1],v=(e,t)=>{if(!t||0===t.length)return e;let n=`One of: ${t.map(e=>`"${e}"`).join(", ")}.`;return e?/[.!?]$/.test(e)?`${e} ${n}`:`${e}. ${n}`:n},$=({target:e,cardinality:t},n)=>{let i=n(e);switch(t){case"1-1":return new s(i);case"0-1":return i;case"0-N":case"1-N":return new s(new r(new s(i)))}},O=(e,n,i)=>{let r={id:{type:new s(t),description:i?`Unique id of the entity, assigned by the configurator, e.g. "${i}-1".`:void 0},type:{type:new s(p),description:i?`Entity type name, e.g. "${i}".`:void 0}};for(let t of e)r[t.name]={type:"edge"===t.type.kind?$(t.type,n):h(t.type.type),description:t.description};return r},E=e=>Object.fromEntries(e.map(({name:e,source:t,localized:n})=>[e,n?{source:t,localized:!0}:{source:t}])),R={sameForAllInstances:e=>`Same recipe groups and recipes for every ${e}.`,constantDuringSession:()=>"Fixed for the session."},k={sameForAllInstances:e=>`Same for every ${e}.`,constantDuringSession:()=>"Fixed for the session."},I=(e,t,n)=>e.map(e=>` ${t[e](n)}`).join(""),T=({section:e},{groups:t,hints:n},{entityType:i,purpose:o,recipeGroup:a})=>{let p=t.filter(({dryRuns:e})=>!e).map(({groupId:e})=>e);return 0===t.length?{}:{[e]:{type:new s(new r(new s(a))),description:`The recipe groups for ${o} this ${i}: ${t.map(({groupId:e})=>e).join(", ")}. A group is listed only while the configurator offers it on the entity.${p.length>0?` Recipes of these groups cannot be dry-run: ${p.join(", ")}.`:""}${I(n,R,i)}`}}},N=({entityType:e,childRecipes:t,replaceRecipes:n,optionsEdges:i},{recipeGroup:a,fieldOption:p})=>new o({name:`${e}Meta`,fields:Object.assign(T(g,t,{entityType:e,purpose:"adding children to",recipeGroup:a}),T(b,n,{entityType:e,purpose:"replacing",recipeGroup:a}),...i.map(({options:{edgeName:t,field:n,validate:i,dryRuns:o},hints:a})=>({[t]:{type:new r(new s(p)),description:`Values "${n}" accepts on this ${e}. Null when there is no choice to make.${i?"":" Not checked on write."}${o?"":" Cannot be dry-run."}${I(a,k,e)}`}})))}),A=e=>Object.entries(e).map(([e,t])=>`${e} (${t.map(({groupId:e})=>e).join(", ")})`);export const buildAssistantManifest=$=>{var R,k,I,T,S;let D,M,F,W,{entityTypes:x,aliases:C,exampleType:q,fields:G,states:U,deletableTypes:K,writableTypes:P,actionFields:V,optionsEdges:z}=$,Q=Object.fromEntries(Object.entries($.childRecipes).map(([e,t])=>[e,t.groups])),B=Object.fromEntries(Object.entries($.replaceRecipes).map(([e,t])=>[e,t.groups])),H=new Map,J=new Map,L=new Map,X=e=>{let[t,...n]=Object.entries(C).find(([t])=>t===e)?.[1]??[];return(t&&0===n.length?J.get(t):void 0)??L.get(e)??Z},Y=e=>J.get(e)??X(e),Z=new i({name:u,description:"Every entity of the configuration.",fields:()=>O(G[u]??[],Y,q)});for(let e of(L.set(u,Z),Object.keys(C)))e!==u&&L.set(e,new i({name:e,interfaces:[Z],fields:()=>O(G[e]??[],Y)}));let _={};for(let e of x){let t=[u,...Object.entries(C).filter(([t,n])=>t!==u&&n.includes(e)).map(([e])=>e)],n=G[e]??[];_[e]=E(n),J.set(e,new o({name:e,interfaces:t.flatMap(e=>L.get(e)??[]),fields:()=>{let t=H.get(e);return{...O(n,Y),...t?{meta:{type:new s(t),description:"What can be done to this entity right now."}}:{}}}}))}let ee=(R=()=>X(L.has(m)?m:u),k=()=>X(u),D=new o({name:"DryRunError",description:"One reason a change cannot be made right now.",fields:{type:{type:new s(p),description:"Kind of the problem."},entityId:{type:t,description:"The entity the problem is on, when it is on one."},message:{type:p,description:"The problem as the user would be told it."}}}),new o({name:"DryRun",description:"What using a recipe or writing an option would do, tried out on the configuration the query reads. Nothing is changed.",fields:()=>({ok:{type:new s(e),description:"Whether the change would succeed on the configuration the query reads."},root:{type:R(),description:"The root entity of the configuration as it would be after the change; null when ok is false. Everything selected under it reads that configuration."},changedEntities:{type:new s(new r(new s(k()))),description:"Entities the change would add or modify; empty when ok is false. One whose id is not in the configuration would be added, and its real id may differ. An entity written to is listed even when its values stay the same."},deletedEntityIds:{type:new s(new r(new s(t))),description:"The ids of the entities the change would remove; empty when ok is false."},sideEffects:{type:new s(new r(new s(p))),description:"The major or unexpected things the configurator would change along with it; empty when ok is false."},errors:{type:new s(new r(new s(D))),description:"Why the change cannot be made; empty when ok is true."}})})),et={recipeGroup:(I=[...Object.keys(Q).length>0?[g.mutation]:[],...Object.keys(B).length>0?[b.mutation]:[]],M=I.join(" or "),F=new o({name:"Recipe",description:`${I.includes("addChild")?"One thing that can be added to the entity (under childRecipes)":"One thing the entity can be replaced with (under replaceRecipes)"}${I.length>1?" or that it can be replaced with (under replaceRecipes)":""}.`,fields:{key:{type:new s(p),description:`Pass to ${M} as recipeKey. Valid only for the listing it came from.`},name:{type:new s(p),description:"Name shown to the user."},description:{type:p},imageUrl:{type:p,description:"Thumbnail shown to the user."},dryRun:{type:ee,description:"What using this recipe would do. Null when it cannot be tried."}}}),new o({name:"RecipeGroup",description:"A group of recipes for this entity.",fields:{groupId:{type:new s(p),description:`Pass to ${M} as recipeGroupId.`},name:{type:new s(p),description:"Name shown to the user."},description:{type:p},recipes:{type:new s(new r(new s(F)))}}})),fieldOption:new o({name:"FieldOption",description:"One value a field accepts. Writing a value outside the list is rejected unless the field description says it is not checked.",fields:{value:{type:new s(l),description:"The value to write to the field."},name:{type:p,description:"Name shown to the user."},description:{type:p},imageUrl:{type:p,description:"Image shown to the user."},color:{type:p,description:'Color shown to the user, as "#rrggbb".'},dryRun:{type:ee,description:"What writing this value would do. Null when it cannot be tried."}}})};for(let e of x){let t=j($.childRecipes,e)??{groups:[],hints:[]},n=j($.replaceRecipes,e)??{groups:[],hints:[]},i=j(z,e)??[];(t.groups.length>0||n.groups.length>0||i.length>0)&&H.set(e,N({entityType:e,childRecipes:t,replaceRecipes:n,optionsEdges:i.map(t=>({...t,hints:(C[m]??[]).includes(e)?t.hints.filter(e=>"sameForAllInstances"!==e):t.hints}))},et))}let en=new a({query:(T=X(L.has(m)?m:u),S=X(u),W=(e=>{let t={};for(let[n,i,r]of f){let o=e[r];o.length>1&&(t[n]={type:new s(p),description:v(i,o)})}if(0!==Object.keys(t).length)return new o({name:"Navigation",description:"The active UI states of the configurator.",fields:t})})(U),new o({name:"Query",fields:{root:{type:new s(T),description:"The root entity of the configuration."},entities:{type:new s(new r(S)),args:{ids:{type:new r(new s(t))}},description:"Every entity, or the given ids in order with null for an unknown id."},selectedEntity:{type:new s(S),description:"The selected entity, or the root entity when nothing is selected."},...W?{navigation:{type:new s(W)}}:{}}})),mutation:(({deletableTypes:e,writableTypes:i,childRecipes:a,replaceRecipes:d,actionFields:c,objectTypes:l,anyDomainEntity:y,targetOutputType:w})=>{let u,m;if(0!==e.length||0!==i.length||0!==Object.keys(a).length||0!==Object.keys(d).length||0!==c.length)return new o({name:"Mutation",fields:Object.assign(0===e.length?{}:{deleteEntity:{type:new s(new r(new s(t))),args:{id:{type:new s(t)}},description:`Deletes the entity and everything composed into it. Deletable types: ${e.join(", ")}. Returns the ids removed, the given one first.`}},0===(u=A(a)).length?{}:{addChild:{type:new s(new r(new s(y))),args:{parentId:{type:new s(t)},recipeGroupId:{type:new s(p)},recipeKey:{type:new s(p)}},description:`Adds a child to the entity parentId from a recipe listed under its meta { childRecipes }. Types with child recipes: ${u.join("; ")}. Returns the entities added, the recipe's own first; empty when they were removed again.`}},0===(m=A(d)).length?{}:{replaceEntity:{type:new s(new r(new s(y))),args:{entityId:{type:new s(t)},recipeGroupId:{type:new s(p)},recipeKey:{type:new s(p)}},description:`Replaces the entity entityId from a recipe listed under its meta { replaceRecipes }. Types with replace recipes: ${m.join("; ")}. The replacement is a new entity: it has a new id and the field values of its recipe, and entityId no longer exists. Returns the entities added, the replacement first; empty when they were removed again.`}},...i.flatMap(e=>{let i=l.get(e.entityType);return i?[(({entityType:e,fields:i},r)=>{let o=Object.fromEntries(i.map(({name:e,type:t,description:n})=>[e,{type:h(t),description:n}]));return{[`modify${e}`]:{type:r,args:{id:{type:new s(t)},delta:{type:new s(new n({name:`${e}Delta`,fields:o}))}},description:`Sets the fields named in delta on one ${e}. Returns the entity after the change, or null when the change removed it.`}}})(e,i)]:[]}),Object.fromEntries(c.map(e=>{var t;return[e.fieldName,{type:new s(new o({name:(t=e.fieldName,`${t.charAt(0).toUpperCase()+t.slice(1)}Result`),description:`What ${e.fieldName} returns, read from the configuration after the change.`,fields:()=>Object.fromEntries(Object.entries(e.result).map(([e,t])=>[e,{type:"edge"===t.kind?(({target:e,list:t,nullable:n},i)=>{let o=t?new r(new s(i(e))):i(e);return n?o:new s(o)})(t,w):h(t.type),description:t.description}]))})),args:Object.fromEntries(Object.entries(e.args).map(([e,t])=>[e,{type:h(t.type),description:t.description}])),description:e.description}]})))})})({deletableTypes:K,writableTypes:P,childRecipes:Q,replaceRecipes:B,actionFields:V,objectTypes:J,anyDomainEntity:X(u),targetOutputType:Y}),types:[...L.values(),...J.values()]}),ei=c(en);if(ei.length>0)throw Error(ei.map(e=>e.message).join(" "));let er=d(en);return{sdl:er,navigationFields:f.filter(([,,e])=>U[e].length>1).map(([e])=>e),fields:_,deletableTypes:K,writableFields:Object.fromEntries(P.map(({entityType:e,fields:t})=>[e,Object.fromEntries(t.map(({name:e,property:t})=>[e,t]))])),childRecipes:Q,replaceRecipes:B,fieldOptions:Object.fromEntries(x.flatMap(e=>{let t=j(z,e);return t?[[e,t.map(({options:e})=>e)]]:[]})),actions:Object.fromEntries(V.map(({fieldName:e,actionName:t})=>[e,t])),lenses:y($,w(er))}};
1
+ import{GraphQLBoolean as e,GraphQLEnumType as t,GraphQLID as n,GraphQLInputObjectType as i,GraphQLInterfaceType as s,GraphQLList as r,GraphQLNonNull as o,GraphQLObjectType as a,GraphQLSchema as p,GraphQLString as c,printSchema as d,validateSchema as l}from"graphql";import{GraphQLJSON as h,createAssistantValueType as w}from"./assistant-value-type.js";import{buildAssistantLenses as y}from"./build-assistant-lenses.js";import{createAssistantExecutionSchema as u}from"./create-assistant-execution-schema.js";let m="AnyDomainEntity",f="RootEntity",g=[["navigationState","Active navigation state.","navigationStates"],["viewState","Active view state.","viewStates"],["toolState","Active tool state.","toolStates"]],b={section:"childRecipes",mutation:"addChild"},v={section:"replaceRecipes",mutation:"replaceEntity"},j=(e,t)=>Object.entries(e).find(([e])=>e===t)?.[1],$=(e,t)=>{if(!t||0===t.length)return e;let n=`One of: ${t.map(e=>`"${e}"`).join(", ")}.`;return e?/[.!?]$/.test(e)?`${e} ${n}`:`${e}. ${n}`:n},O=({target:e,cardinality:t},n)=>{let i=n(e);switch(t){case"1-1":return new o(i);case"0-1":return i;case"0-N":case"1-N":return new o(new r(new o(i)))}},E=(e,t,i)=>{let s={id:{type:new o(n),description:i?`Unique id of the entity, assigned by the configurator, e.g. "${i}-1".`:void 0},type:{type:new o(c),description:i?`Entity type name, e.g. "${i}".`:void 0}};for(let n of e)s[n.name]={type:"edge"===n.type.kind?O(n.type,t):w(n.type.type),description:n.description};return s},R=e=>Object.fromEntries(e.map(({name:e,source:t,localized:n})=>[e,n?{source:t,localized:!0}:{source:t}])),k={sameForAllInstances:e=>`Same recipe groups and recipes for every ${e}.`,constantDuringSession:()=>"Fixed for the session."},T={sameForAllInstances:e=>`Same for every ${e}.`,constantDuringSession:()=>"Fixed for the session."},I=(e,t,n)=>e.map(e=>` ${t[e](n)}`).join(""),N=({section:e},{groups:t,hints:n},{entityType:i,purpose:s,recipeGroup:a})=>{let p=t.filter(({dryRuns:e})=>!e).map(({groupId:e})=>e);return 0===t.length?{}:{[e]:{type:new o(new r(new o(a))),description:`The recipe groups for ${s} this ${i}: ${t.map(({groupId:e})=>e).join(", ")}. A group is listed only while the configurator offers it on the entity.${p.length>0?` Recipes of these groups cannot be dry-run: ${p.join(", ")}.`:""}${I(n,k,i)}`}}},A=({entityType:e,childRecipes:n,replaceRecipes:i,optionsEdges:s,screenshots:p},{recipeGroup:c,fieldOption:d,screenshot:l})=>new a({name:`${e}Meta`,fields:Object.assign(N(b,n,{entityType:e,purpose:"adding children to",recipeGroup:c}),N(v,i,{entityType:e,purpose:"replacing",recipeGroup:c}),...s.map(({options:{edgeName:t,field:n,validate:i,dryRuns:s},hints:a})=>({[t]:{type:new r(new o(d)),description:`Values "${n}" accepts on this ${e}. Null when there is no choice to make.${i?"":" Not checked on write."}${s?"":" Cannot be dry-run."}${I(a,T,e)}`}})),0===p.length?{}:{screenshot:{type:l,args:{view:{type:new o(new t({name:`${e}ScreenshotView`,values:Object.fromEntries(p.map(({view:e,description:t})=>[e,{description:t}]))}))}},description:"Renders this entity from a predefined view. The picture follows the tool result as an image labelled with `image`. Each one costs tokens and blocks the user's configurator while it renders: ask only when seeing the design answers something the data cannot. The images of one call share a size budget; past it, further screenshots come back null. Null as well when the view does not apply to this entity right now."}})}),S=e=>Object.entries(e).map(([e,t])=>`${e} (${t.map(({groupId:e})=>e).join(", ")})`);export const buildAssistantManifest=t=>{var O,k,T,I,N;let M,D,F,W,{entityTypes:x,aliases:C,exampleType:q,fields:G,states:U,deletableTypes:K,writableTypes:V,actionFields:z,optionsEdges:P}=t,L=Object.fromEntries(Object.entries(t.childRecipes).map(([e,t])=>[e,t.groups])),Q=Object.fromEntries(Object.entries(t.replaceRecipes).map(([e,t])=>[e,t.groups])),B=new Map,H=new Map,J=new Map,X=e=>{let[t,...n]=Object.entries(C).find(([t])=>t===e)?.[1]??[];return(t&&0===n.length?H.get(t):void 0)??J.get(e)??Z},Y=e=>H.get(e)??X(e),Z=new s({name:m,description:"Every entity of the configuration.",fields:()=>E(G[m]??[],Y,q)});for(let e of(J.set(m,Z),Object.keys(C)))e!==m&&J.set(e,new s({name:e,interfaces:[Z],fields:()=>E(G[e]??[],Y)}));let _={};for(let e of x){let t=[m,...Object.entries(C).filter(([t,n])=>t!==m&&n.includes(e)).map(([e])=>e)],n=G[e]??[];_[e]=R(n),H.set(e,new a({name:e,interfaces:t.flatMap(e=>J.get(e)??[]),fields:()=>{let t=B.get(e);return{...E(n,Y),...t?{meta:{type:new o(t),description:"What can be done to this entity right now."}}:{}}}}))}let ee=(O=()=>X(J.has(f)?f:m),k=()=>X(m),M=new a({name:"DryRunError",description:"One reason a change cannot be made right now.",fields:{type:{type:new o(c),description:"Kind of the problem."},entityId:{type:n,description:"The entity the problem is on, when it is on one."},message:{type:c,description:"The problem as the user would be told it."}}}),new a({name:"DryRun",description:"What using a recipe or writing an option would do, tried out on the configuration the query reads. Nothing is changed.",fields:()=>({ok:{type:new o(e),description:"Whether the change would succeed on the configuration the query reads."},root:{type:O(),description:"The root entity of the configuration as it would be after the change; null when ok is false. Everything selected under it reads that configuration."},changedEntities:{type:new o(new r(new o(k()))),description:"Entities the change would add or modify; empty when ok is false. One whose id is not in the configuration would be added, and its real id may differ. An entity written to is listed even when its values stay the same."},deletedEntityIds:{type:new o(new r(new o(n))),description:"The ids of the entities the change would remove; empty when ok is false."},sideEffects:{type:new o(new r(new o(c))),description:"The major or unexpected things the configurator would change along with it; empty when ok is false."},errors:{type:new o(new r(new o(M))),description:"Why the change cannot be made; empty when ok is true."}})})),et={recipeGroup:(T=[...Object.keys(L).length>0?[b.mutation]:[],...Object.keys(Q).length>0?[v.mutation]:[]],D=T.join(" or "),F=new a({name:"Recipe",description:`${T.includes("addChild")?"One thing that can be added to the entity (under childRecipes)":"One thing the entity can be replaced with (under replaceRecipes)"}${T.length>1?" or that it can be replaced with (under replaceRecipes)":""}.`,fields:{key:{type:new o(c),description:`Pass to ${D} as recipeKey. Valid only for the listing it came from.`},name:{type:new o(c),description:"Name shown to the user."},description:{type:c},imageUrl:{type:c,description:"Thumbnail shown to the user."},dryRun:{type:ee,description:"What using this recipe would do. Null when it cannot be tried."}}}),new a({name:"RecipeGroup",description:"A group of recipes for this entity.",fields:{groupId:{type:new o(c),description:`Pass to ${D} as recipeGroupId.`},name:{type:new o(c),description:"Name shown to the user."},description:{type:c},recipes:{type:new o(new r(new o(F)))}}})),fieldOption:new a({name:"FieldOption",description:"One value a field accepts. Writing a value outside the list is rejected unless the field description says it is not checked.",fields:{value:{type:new o(h),description:"The value to write to the field."},name:{type:c,description:"Name shown to the user."},description:{type:c},imageUrl:{type:c,description:"Image shown to the user."},color:{type:c,description:'Color shown to the user, as "#rrggbb".'},dryRun:{type:ee,description:"What writing this value would do. Null when it cannot be tried."}}}),screenshot:new a({name:"Screenshot",description:"A screenshot of an entity. The picture itself follows the tool result.",fields:{image:{type:new o(n),description:"Label of the image that follows the tool result."}}})};for(let e of x){let n=j(t.childRecipes,e)??{groups:[],hints:[]},i=j(t.replaceRecipes,e)??{groups:[],hints:[]},s=j(P,e)??[],r=j(t.screenshots,e)??[];(n.groups.length>0||i.groups.length>0||s.length>0||r.length>0)&&B.set(e,A({entityType:e,childRecipes:n,replaceRecipes:i,optionsEdges:s.map(t=>({...t,hints:(C[f]??[]).includes(e)?t.hints.filter(e=>"sameForAllInstances"!==e):t.hints})),screenshots:r},et))}let en=new p({query:(I=X(J.has(f)?f:m),N=X(m),W=(e=>{let t={};for(let[n,i,s]of g){let r=e[s];r.length>1&&(t[n]={type:new o(c),description:$(i,r)})}if(0!==Object.keys(t).length)return new a({name:"Navigation",description:"The active UI states of the configurator.",fields:t})})(U),new a({name:"Query",fields:{root:{type:new o(I),description:"The root entity of the configuration."},entities:{type:new o(new r(N)),args:{ids:{type:new r(new o(n))}},description:"Every entity, or the given ids in order with null for an unknown id."},selectedEntity:{type:new o(N),description:"The selected entity, or the root entity when nothing is selected."},...W?{navigation:{type:new o(W)}}:{}}})),mutation:(({deletableTypes:e,writableTypes:t,childRecipes:s,replaceRecipes:p,actionFields:d,objectTypes:l,anyDomainEntity:h,targetOutputType:y})=>{let u,m;if(0!==e.length||0!==t.length||0!==Object.keys(s).length||0!==Object.keys(p).length||0!==d.length)return new a({name:"Mutation",fields:Object.assign(0===e.length?{}:{deleteEntity:{type:new o(new r(new o(n))),args:{id:{type:new o(n)}},description:`Deletes the entity and everything composed into it. Deletable types: ${e.join(", ")}. Returns the ids removed, the given one first.`}},0===(u=S(s)).length?{}:{addChild:{type:new o(new r(new o(h))),args:{parentId:{type:new o(n)},recipeGroupId:{type:new o(c)},recipeKey:{type:new o(c)}},description:`Adds a child to the entity parentId from a recipe listed under its meta { childRecipes }. Types with child recipes: ${u.join("; ")}. Returns the entities added, the recipe's own first; empty when they were removed again.`}},0===(m=S(p)).length?{}:{replaceEntity:{type:new o(new r(new o(h))),args:{entityId:{type:new o(n)},recipeGroupId:{type:new o(c)},recipeKey:{type:new o(c)}},description:`Replaces the entity entityId from a recipe listed under its meta { replaceRecipes }. Types with replace recipes: ${m.join("; ")}. The replacement is a new entity: it has a new id and the field values of its recipe, and entityId no longer exists. Returns the entities added, the replacement first; empty when they were removed again.`}},...t.flatMap(e=>{let t=l.get(e.entityType);return t?[(({entityType:e,fields:t},s)=>{let r=Object.fromEntries(t.map(({name:e,type:t,description:n})=>[e,{type:w(t),description:n}]));return{[`modify${e}`]:{type:s,args:{id:{type:new o(n)},delta:{type:new o(new i({name:`${e}Delta`,fields:r}))}},description:`Sets the fields named in delta on one ${e}. Returns the entity after the change, or null when the change removed it.`}}})(e,t)]:[]}),Object.fromEntries(d.map(e=>{var t;return[e.fieldName,{type:new o(new a({name:(t=e.fieldName,`${t.charAt(0).toUpperCase()+t.slice(1)}Result`),description:`What ${e.fieldName} returns, read from the configuration after the change.`,fields:()=>Object.fromEntries(Object.entries(e.result).map(([e,t])=>[e,{type:"edge"===t.kind?(({target:e,list:t,nullable:n},i)=>{let s=t?new r(new o(i(e))):i(e);return n?s:new o(s)})(t,y):w(t.type),description:t.description}]))})),args:Object.fromEntries(Object.entries(e.args).map(([e,t])=>[e,{type:w(t.type),description:t.description}])),description:e.description}]})))})})({deletableTypes:K,writableTypes:V,childRecipes:L,replaceRecipes:Q,actionFields:z,objectTypes:H,anyDomainEntity:X(m),targetOutputType:Y}),types:[...J.values(),...H.values()]}),ei=l(en);if(ei.length>0)throw Error(ei.map(e=>e.message).join(" "));let es=d(en);return{sdl:es,navigationFields:g.filter(([,,e])=>U[e].length>1).map(([e])=>e),fields:_,deletableTypes:K,writableFields:Object.fromEntries(V.map(({entityType:e,fields:t})=>[e,Object.fromEntries(t.map(({name:e,property:t})=>[e,t]))])),childRecipes:L,replaceRecipes:Q,fieldOptions:Object.fromEntries(x.flatMap(e=>{let t=j(P,e);return t?[[e,t.map(({options:e})=>e)]]:[]})),screenshots:Object.fromEntries(x.flatMap(e=>{let n=j(t.screenshots,e);return n?[[e,n]]:[]})),actions:Object.fromEntries(z.map(({fieldName:e,actionName:t})=>[e,t])),lenses:y(t,u(es)),formElements:t.formElements}};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The shape of a form element's tag, and of its attributes' names: the panel reads form markup
3
+ * with lowercase tag and attribute names, so a name with an upper-case letter would never match.
4
+ */
5
+ export declare const ASSISTANT_FORM_NAME_PATTERN: RegExp;
6
+ /**
7
+ * Whether a tag is an HTML element name, which no form element may take, so that markup in a form
8
+ * is never mistaken for HTML. The spec rejects a project's element by it and the panel's registry
9
+ * refuses one, so both read the same list.
10
+ */
11
+ export declare const isHtmlElementName: (name: string) => boolean;
@@ -0,0 +1,8 @@
1
+ let e=new Set(`a abbr acronym address applet area article aside audio b base basefont bdi bdo big blink blockquote body br
2
+ button canvas caption center cite code col colgroup data datalist dd del details dfn dialog dir div dl dt em
3
+ embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html
4
+ i iframe image img input ins kbd keygen label legend li link main map mark marquee math menu menuitem meta
5
+ meter nav nobr noembed noframes noscript object ol optgroup option output p param picture plaintext pre
6
+ progress q rb rp rt rtc ruby s samp script search section select slot small source span strike strong style
7
+ sub summary sup svg table tbody td template textarea tfoot th thead time title tr track tt u ul var video wbr
8
+ xmp`.split(/\s+/));export const ASSISTANT_FORM_NAME_PATTERN=/^[a-z][a-z0-9-]*$/;export const isHtmlElementName=t=>e.has(t);