@salsita-ai/sdk 0.50.0-develop.f101f4c3c9 → 0.50.0-develop.f354d3576d

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 (1542) hide show
  1. package/README.md +8 -2
  2. package/dist/admin/AdminShell.js +1 -1
  3. package/dist/admin/common/admin-interface.d.ts +1 -1
  4. package/dist/admin/legacy-admin/Application.js +1 -1
  5. package/dist/application/index.d.ts +1 -0
  6. package/dist/application/index.js +1 -1
  7. package/dist/application/mount-application.d.ts +1 -1
  8. package/dist/application/mount-application.js +1 -1
  9. package/dist/application/mount-configurator.d.ts +19 -0
  10. package/dist/application/mount-configurator.js +1 -0
  11. package/dist/application/mounted-application-runtime.d.ts +4 -0
  12. package/dist/application/mounted-application-runtime.js +1 -0
  13. package/dist/application/register-web-component.d.ts +2 -0
  14. package/dist/application/register-web-component.js +1 -0
  15. package/dist/application-spec/caching/index.d.ts +6 -1
  16. package/dist/application-spec/caching/index.js +1 -1
  17. package/dist/application-spec/core/enhanced-json-value.d.ts +2 -31
  18. package/dist/application-spec/core/enhanced-json-value.js +1 -1
  19. package/dist/application-spec/core/index.d.ts +1 -3
  20. package/dist/application-spec/core/index.js +1 -1
  21. package/dist/application-spec/core/json-schema.d.ts +2 -52
  22. package/dist/application-spec/core/markers/index.d.ts +1 -1
  23. package/dist/application-spec/core/markers/index.js +1 -1
  24. package/dist/application-spec/core/markers/msg-marker.d.ts +2 -28
  25. package/dist/application-spec/core/markers/msg-marker.js +1 -1
  26. package/dist/application-spec/core/markers/raw-eval-write-marker.d.ts +1 -1
  27. package/dist/application-spec/core/markers/raw-eval-write-marker.js +1 -1
  28. package/dist/application-spec/core/markers/raw-read-write-marker.js +1 -1
  29. package/dist/application-spec/errors/diagnostic-codes.d.ts +18 -1
  30. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  31. package/dist/application-spec/errors/diagnostic-messages.d.ts +26 -2
  32. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  33. package/dist/application-spec/generate-application/code/domain-runtime-files.d.ts +0 -19
  34. package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
  35. package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
  36. package/dist/application-spec/generate-application/code/templates/application-types.js +6 -6
  37. package/dist/application-spec/generate-application/code/templates/common/doc-comment.d.ts +2 -0
  38. package/dist/application-spec/generate-application/code/templates/common/doc-comment.js +1 -0
  39. package/dist/application-spec/generate-application/code/templates/configurator-globals.d.ts +2 -1
  40. package/dist/application-spec/generate-application/code/templates/configurator-globals.js +19 -6
  41. package/dist/application-spec/generate-application/code/templates/gitignore.js +2 -1
  42. package/dist/application-spec/generate-application/code/templates/global-definition/generate-action-types.js +7 -7
  43. package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.d.ts +12 -0
  44. package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.js +7 -0
  45. package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.js +24 -22
  46. package/dist/application-spec/generate-application/code/templates/global-definition/index.d.ts +1 -0
  47. package/dist/application-spec/generate-application/code/templates/global-definition/index.js +1 -1
  48. package/dist/application-spec/generate-application/code/templates/index.d.ts +2 -6
  49. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  50. package/dist/application-spec/generate-application/code/templates/lingui-rc.js +5 -4
  51. package/dist/application-spec/generate-application/code/templates/skills/core/cli.d.ts +2 -0
  52. package/dist/application-spec/generate-application/code/templates/skills/core/cli.js +1 -0
  53. package/dist/application-spec/generate-application/code/templates/skills/core/index.d.ts +3 -0
  54. package/dist/application-spec/generate-application/code/templates/skills/core/index.js +1 -0
  55. package/dist/application-spec/generate-application/code/templates/skills/core/sdk.d.ts +2 -0
  56. package/dist/application-spec/generate-application/code/templates/skills/core/sdk.js +118 -0
  57. package/dist/application-spec/generate-application/code/templates/skills/core/workflow.d.ts +2 -0
  58. package/dist/application-spec/generate-application/code/templates/skills/core/workflow.js +44 -0
  59. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/application-states.d.ts +3 -0
  60. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/application-states.js +3 -0
  61. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/index.d.ts +1 -0
  62. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/index.js +1 -0
  63. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/navigation.d.ts +3 -0
  64. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/navigation.js +48 -0
  65. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/presets.d.ts +3 -0
  66. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/presets.js +38 -0
  67. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/selection.d.ts +3 -0
  68. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/selection.js +35 -0
  69. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/views-and-tools.d.ts +3 -0
  70. package/dist/application-spec/generate-application/code/templates/skills/features/application-states/views-and-tools.js +61 -0
  71. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/custom-layout.d.ts +3 -0
  72. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/custom-layout.js +7 -0
  73. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/finalization-actions.d.ts +3 -0
  74. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/finalization-actions.js +79 -0
  75. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/index.d.ts +1 -0
  76. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/index.js +1 -0
  77. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/messages.d.ts +3 -0
  78. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/messages.js +52 -0
  79. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/pages.d.ts +3 -0
  80. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/pages.js +49 -0
  81. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/scene-and-safe-area.d.ts +3 -0
  82. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/scene-and-safe-area.js +62 -0
  83. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/slots.d.ts +3 -0
  84. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/slots.js +40 -0
  85. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/tabs-and-steps.d.ts +3 -0
  86. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/tabs-and-steps.js +65 -0
  87. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/toolbar-actions.d.ts +3 -0
  88. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/toolbar-actions.js +58 -0
  89. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/view-mode.d.ts +3 -0
  90. package/dist/application-spec/generate-application/code/templates/skills/features/custom-layout/view-mode.js +41 -0
  91. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/control-components.d.ts +3 -0
  92. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/control-components.js +24 -0
  93. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/control-hooks.d.ts +3 -0
  94. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/control-hooks.js +44 -0
  95. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/custom-controls.d.ts +3 -0
  96. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/custom-controls.js +74 -0
  97. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/domain-controls.d.ts +3 -0
  98. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/domain-controls.js +8 -0
  99. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/index.d.ts +1 -0
  100. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/index.js +1 -0
  101. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/scene-controls.d.ts +3 -0
  102. package/dist/application-spec/generate-application/code/templates/skills/features/domain-controls/scene-controls.js +48 -0
  103. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/actions.d.ts +3 -0
  104. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/actions.js +64 -0
  105. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/components.d.ts +3 -0
  106. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/components.js +62 -0
  107. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/domain-model.d.ts +3 -0
  108. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/domain-model.js +1 -0
  109. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/entities-and-relations.d.ts +3 -0
  110. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/entities-and-relations.js +59 -0
  111. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/expressions.d.ts +3 -0
  112. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/expressions.js +46 -0
  113. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/index.d.ts +1 -0
  114. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/index.js +1 -0
  115. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/transformers.d.ts +3 -0
  116. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/transformers.js +43 -0
  117. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/validation-and-repair.d.ts +3 -0
  118. package/dist/application-spec/generate-application/code/templates/skills/features/domain-model/validation-and-repair.js +30 -0
  119. package/dist/application-spec/generate-application/code/templates/skills/features/features.d.ts +3 -0
  120. package/dist/application-spec/generate-application/code/templates/skills/features/features.js +1 -0
  121. package/dist/application-spec/generate-application/code/templates/skills/features/index.d.ts +1 -0
  122. package/dist/application-spec/generate-application/code/templates/skills/features/index.js +1 -0
  123. package/dist/application-spec/generate-application/code/templates/skills/features/inspection.d.ts +2 -0
  124. package/dist/application-spec/generate-application/code/templates/skills/features/inspection.js +1 -0
  125. package/dist/application-spec/generate-application/code/templates/skills/features/localization.d.ts +3 -0
  126. package/dist/application-spec/generate-application/code/templates/skills/features/localization.js +87 -0
  127. package/dist/application-spec/generate-application/code/templates/skills/features/scene-objects.d.ts +3 -0
  128. package/dist/application-spec/generate-application/code/templates/skills/features/scene-objects.js +43 -0
  129. package/dist/application-spec/generate-application/code/templates/skills/features/state/index.d.ts +1 -0
  130. package/dist/application-spec/generate-application/code/templates/skills/features/state/index.js +1 -0
  131. package/dist/application-spec/generate-application/code/templates/skills/features/state/slices.d.ts +3 -0
  132. package/dist/application-spec/generate-application/code/templates/skills/features/state/slices.js +48 -0
  133. package/dist/application-spec/generate-application/code/templates/skills/features/state/state.d.ts +3 -0
  134. package/dist/application-spec/generate-application/code/templates/skills/features/state/state.js +1 -0
  135. package/dist/application-spec/generate-application/code/templates/skills/features/state/updates.d.ts +3 -0
  136. package/dist/application-spec/generate-application/code/templates/skills/features/state/updates.js +39 -0
  137. package/dist/application-spec/generate-application/code/templates/skills/features/state/violations.d.ts +3 -0
  138. package/dist/application-spec/generate-application/code/templates/skills/features/state/violations.js +36 -0
  139. package/dist/application-spec/generate-application/code/templates/skills/features/variants.d.ts +2 -0
  140. package/dist/application-spec/generate-application/code/templates/skills/features/variants.js +1 -0
  141. package/dist/application-spec/generate-application/code/templates/skills/features/webhooks.d.ts +2 -0
  142. package/dist/application-spec/generate-application/code/templates/skills/features/webhooks.js +1 -0
  143. package/dist/application-spec/generate-application/code/templates/skills/guide.d.ts +68 -0
  144. package/dist/application-spec/generate-application/code/templates/skills/guide.js +18 -0
  145. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill-bundle.d.ts +10 -0
  146. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill-bundle.js +14 -0
  147. package/dist/application-spec/generate-application/code/templates/skills/phx-members-without-guide.d.ts +9 -0
  148. package/dist/application-spec/generate-application/code/templates/skills/phx-members-without-guide.js +1 -0
  149. package/dist/application-spec/generate-application/code/templates/skills/processes/asset-conversion.d.ts +2 -0
  150. package/dist/application-spec/generate-application/code/templates/skills/processes/asset-conversion.js +23 -0
  151. package/dist/application-spec/generate-application/code/templates/skills/{phoenix-skill.d.ts → processes/beta.d.ts} +2 -1
  152. package/dist/application-spec/generate-application/code/templates/skills/processes/beta.js +4 -0
  153. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/evaluate.d.ts +2 -0
  154. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/evaluate.js +38 -0
  155. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/feedback.d.ts +3 -0
  156. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/feedback.js +1 -0
  157. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/index.d.ts +1 -0
  158. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/index.js +1 -0
  159. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/report.d.ts +2 -0
  160. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/report.js +7 -0
  161. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/send.d.ts +2 -0
  162. package/dist/application-spec/generate-application/code/templates/skills/processes/feedback/send.js +2 -0
  163. package/dist/application-spec/generate-application/code/templates/skills/processes/finalization.d.ts +2 -0
  164. package/dist/application-spec/generate-application/code/templates/skills/processes/finalization.js +2 -0
  165. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/build.d.ts +2 -0
  166. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/build.js +25 -0
  167. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/direction.d.ts +2 -0
  168. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/direction.js +35 -0
  169. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/first-draft.d.ts +3 -0
  170. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/first-draft.js +3 -0
  171. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/handoff.d.ts +2 -0
  172. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/handoff.js +9 -0
  173. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/index.d.ts +1 -0
  174. package/dist/application-spec/generate-application/code/templates/skills/processes/first-draft/index.js +1 -0
  175. package/dist/application-spec/generate-application/code/templates/skills/processes/index.d.ts +8 -0
  176. package/dist/application-spec/generate-application/code/templates/skills/processes/index.js +1 -0
  177. package/dist/application-spec/generate-application/code/templates/skills/processes/model-migration.d.ts +2 -0
  178. package/dist/application-spec/generate-application/code/templates/skills/processes/model-migration.js +1 -0
  179. package/dist/application-spec/generate-application/code/templates/skills/processes/review/appearance.d.ts +2 -0
  180. package/dist/application-spec/generate-application/code/templates/skills/processes/review/appearance.js +46 -0
  181. package/dist/application-spec/generate-application/code/templates/skills/processes/review/code.d.ts +2 -0
  182. package/dist/application-spec/generate-application/code/templates/skills/processes/review/code.js +2 -0
  183. package/dist/application-spec/generate-application/code/templates/skills/processes/review/hand-over.d.ts +2 -0
  184. package/dist/application-spec/generate-application/code/templates/skills/processes/review/hand-over.js +6 -0
  185. package/dist/application-spec/generate-application/code/templates/skills/processes/review/index.d.ts +1 -0
  186. package/dist/application-spec/generate-application/code/templates/skills/processes/review/index.js +1 -0
  187. package/dist/application-spec/generate-application/code/templates/skills/processes/review/review.d.ts +3 -0
  188. package/dist/application-spec/generate-application/code/templates/skills/processes/review/review.js +1 -0
  189. package/dist/application-spec/generate-application/code/templates/skills/processes/review/visual-inspection.d.ts +2 -0
  190. package/dist/application-spec/generate-application/code/templates/skills/processes/review/visual-inspection.js +13 -0
  191. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/branding.d.ts +2 -0
  192. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/branding.js +20 -0
  193. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/catalog.d.ts +2 -0
  194. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/catalog.js +1 -0
  195. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/index.d.ts +1 -0
  196. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/index.js +1 -0
  197. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/product-definition.d.ts +2 -0
  198. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/product-definition.js +22 -0
  199. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/specification.d.ts +3 -0
  200. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/specification.js +1 -0
  201. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/ux-guidelines.d.ts +2 -0
  202. package/dist/application-spec/generate-application/code/templates/skills/processes/specification/ux-guidelines.js +25 -0
  203. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +51 -251
  204. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +3 -58
  205. package/dist/application-spec/generate-application/code/templates/skills/sdk-subpath-imports.d.ts +9 -0
  206. package/dist/application-spec/generate-application/code/templates/skills/sdk-subpath-imports.js +1 -0
  207. package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.d.ts +1 -1
  208. package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.js +1 -1
  209. package/dist/application-spec/generate-application/code/templates/story/wrapper.js +2 -4
  210. package/dist/application-spec/generate-application/code/templates/tsconfig.js +2 -1
  211. package/dist/application-spec/generate-application/index.d.ts +1 -6
  212. package/dist/application-spec/generate-application/index.js +1 -1
  213. package/dist/application-spec/parse-application-files/merge/merged-assistant.d.ts +13 -4
  214. package/dist/application-spec/parse-application-files/merge/merged-assistant.js +1 -1
  215. package/dist/application-spec/parse-application-files/merge/merged-infra.d.ts +4 -11
  216. package/dist/application-spec/parse-application-files/merge/merged-transformers.d.ts +2 -4
  217. package/dist/application-spec/parse-application-files/parse-application-files.js +1 -1
  218. package/dist/application-spec/parse-application-files/raw-spec/raw-acl.d.ts +2 -2
  219. package/dist/application-spec/parse-application-files/raw-spec/raw-acl.js +1 -1
  220. package/dist/application-spec/parse-application-files/raw-spec/raw-admin-pages.d.ts +3 -24
  221. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.d.ts +58 -23
  222. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
  223. package/dist/application-spec/parse-application-files/raw-spec/raw-expressions.d.ts +1 -1
  224. package/dist/application-spec/parse-application-files/raw-spec/raw-expressions.js +1 -1
  225. package/dist/application-spec/parse-application-files/raw-spec/raw-resources.d.ts +19 -64
  226. package/dist/application-spec/parse-application-files/spec/application/admin-spec/admin-pages.d.ts +3 -20
  227. package/dist/application-spec/parse-application-files/spec/application/admin-spec/states.d.ts +3 -5
  228. package/dist/application-spec/parse-application-files/spec/application/configurator-spec/states.d.ts +3 -8
  229. package/dist/application-spec/parse-application-files/spec/components/bindings/read-binding.d.ts +3 -13
  230. package/dist/application-spec/parse-application-files/spec/components/bindings/write-binding.d.ts +4 -32
  231. package/dist/application-spec/parse-application-files/spec/components/component-filter.d.ts +4 -10
  232. package/dist/application-spec/parse-application-files/spec/components/components.d.ts +3 -4
  233. package/dist/application-spec/parse-application-files/spec/components/index.d.ts +1 -2
  234. package/dist/application-spec/parse-application-files/spec/components/index.js +1 -1
  235. package/dist/application-spec/parse-application-files/spec/components/sdk-component-entry.d.ts +1 -1
  236. package/dist/application-spec/parse-application-files/spec/components/user-component-entry.d.ts +2 -3
  237. package/dist/application-spec/parse-application-files/spec/components/user-component-entry.js +1 -1
  238. package/dist/application-spec/parse-application-files/spec/components/user-components.d.ts +2 -3
  239. package/dist/application-spec/parse-application-files/spec/expressions/expression-read-types.d.ts +2 -2
  240. package/dist/application-spec/parse-application-files/spec/expressions/expressions.d.ts +7 -29
  241. package/dist/application-spec/parse-application-files/spec/expressions/index.d.ts +0 -1
  242. package/dist/application-spec/parse-application-files/spec/expressions/memo-eval-plan.d.ts +4 -71
  243. package/dist/application-spec/parse-application-files/spec/expressions/memo-eval-plan.js +1 -1
  244. package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.d.ts +2 -2
  245. package/dist/application-spec/parse-application-files/spec/invariants/invariant.d.ts +5 -21
  246. package/dist/application-spec/parse-application-files/spec/invariants/invariants.d.ts +4 -14
  247. package/dist/application-spec/parse-application-files/spec/model/aliases.d.ts +3 -9
  248. package/dist/application-spec/parse-application-files/spec/model/common-types.d.ts +6 -14
  249. package/dist/application-spec/parse-application-files/spec/model/defaults.d.ts +6 -31
  250. package/dist/application-spec/parse-application-files/spec/model/entity-types.d.ts +3 -8
  251. package/dist/application-spec/parse-application-files/spec/model/model-version-key.d.ts +3 -3
  252. package/dist/application-spec/parse-application-files/spec/model/names/action-name.d.ts +3 -7
  253. package/dist/application-spec/parse-application-files/spec/model/names/alias-name.d.ts +3 -7
  254. package/dist/application-spec/parse-application-files/spec/model/names/entity-or-alias-name.d.ts +3 -8
  255. package/dist/application-spec/parse-application-files/spec/model/names/entity-type-name.d.ts +3 -8
  256. package/dist/application-spec/parse-application-files/spec/model/names/expression-name.d.ts +3 -6
  257. package/dist/application-spec/parse-application-files/spec/model/names/instance-name.d.ts +3 -7
  258. package/dist/application-spec/parse-application-files/spec/model/names/invariant-name.d.ts +3 -3
  259. package/dist/application-spec/parse-application-files/spec/model/names/shape-property-name.d.ts +3 -11
  260. package/dist/application-spec/parse-application-files/spec/model/names/violation-type-name.d.ts +3 -3
  261. package/dist/application-spec/parse-application-files/spec/model/presets.d.ts +6 -29
  262. package/dist/application-spec/parse-application-files/spec/sections/actions.d.ts +6 -23
  263. package/dist/application-spec/parse-application-files/spec/sections/app-metadata.d.ts +3 -18
  264. package/dist/application-spec/parse-application-files/spec/sections/application-version.d.ts +3 -7
  265. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.d.ts +9 -0
  266. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.js +1 -0
  267. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.d.ts +4 -1
  268. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.js +1 -1
  269. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant.js +1 -1
  270. package/dist/application-spec/parse-application-files/spec/sections/data-spec/chunks.d.ts +6 -23
  271. package/dist/application-spec/parse-application-files/spec/sections/data-spec/custom-data-spec.d.ts +1 -16
  272. package/dist/application-spec/parse-application-files/spec/sections/data-spec/data-entry-key.d.ts +3 -9
  273. package/dist/application-spec/parse-application-files/spec/sections/data-spec/data-spec.d.ts +1 -10
  274. package/dist/application-spec/parse-application-files/spec/sections/data-spec/swatch-data-spec.d.ts +1 -14
  275. package/dist/application-spec/parse-application-files/spec/sections/state-value.d.ts +3 -7
  276. package/dist/application-spec/parse-application-files/spec/sections/theme.d.ts +3 -10
  277. package/dist/application-spec/parse-application-files/spec/sections/transformers.d.ts +4 -11
  278. package/dist/application-spec/parse-application-files/spec/sections/ui.d.ts +3 -12
  279. package/dist/client/authorized-client.d.ts +4 -2
  280. package/dist/client/authorized-client.js +1 -1
  281. package/dist/client/contract.d.ts +34 -4
  282. package/dist/client/contract.js +1 -1
  283. package/dist/client/index.d.ts +1 -1
  284. package/dist/client/local-authorized-client.d.ts +3 -1
  285. package/dist/client/local-authorized-client.js +1 -1
  286. package/dist/client/memory-authorized-client.d.ts +4 -1
  287. package/dist/client/memory-authorized-client.js +1 -1
  288. package/dist/client/phoenix-api.d.ts +279 -2
  289. package/dist/configurator/ConfiguratorApp.js +1 -1
  290. package/dist/configurator/app-loader-content/index.d.ts +0 -1
  291. package/dist/configurator/app-loader-content/index.js +1 -1
  292. package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
  293. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-annotation.d.ts +31 -0
  294. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +140 -27
  295. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
  296. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.d.ts +52 -9
  297. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -1
  298. package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.d.ts +43 -0
  299. package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.js +1 -1
  300. package/dist/configurator/application-runtime/extensions/systems/canvas-system.d.ts +8 -0
  301. package/dist/configurator/application-runtime/extensions/systems/canvas-system.js +1 -1
  302. package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.d.ts +8 -0
  303. package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.js +1 -0
  304. package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.d.ts +23 -1
  305. package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.js +1 -1
  306. package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.d.ts +82 -55
  307. package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.js +1 -1
  308. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +73 -5
  309. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
  310. package/dist/configurator/application-runtime/extensions/systems/graphql-system/violations.d.ts +40 -0
  311. package/dist/configurator/application-runtime/extensions/systems/graphql-system/violations.js +1 -0
  312. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/capture-next-frame.d.ts +10 -0
  313. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/capture-next-frame.js +1 -0
  314. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.d.ts +22 -5
  315. package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.js +1 -1
  316. package/dist/configurator/application-runtime/extensions/systems/index.d.ts +3 -2
  317. package/dist/configurator/application-runtime/extensions/systems/index.js +1 -1
  318. package/dist/configurator/common/configurator-interface.d.ts +10 -7
  319. package/dist/configurator/common/configurator-interface.js +1 -1
  320. package/dist/configurator/configuration-not-found-page/BuiltInConfigurationNotFoundPage.d.ts +5 -0
  321. package/dist/configurator/configuration-not-found-page/BuiltInConfigurationNotFoundPage.js +1 -0
  322. package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.d.ts +0 -6
  323. package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.js +1 -1
  324. package/dist/configurator/configuration-not-found-page/index.d.ts +2 -2
  325. package/dist/configurator/configuration-not-found-page/index.js +1 -1
  326. package/dist/configurator/configurator-context/PreviewContextProvider.js +1 -1
  327. package/dist/configurator/configurator-context/configurator-hooks/index.d.ts +0 -1
  328. package/dist/configurator/configurator-context/configurator-hooks/index.js +1 -1
  329. package/dist/configurator/configurator-context/configurator-hooks/layout-hooks.js +1 -1
  330. package/dist/configurator/configurator-context/index.d.ts +0 -5
  331. package/dist/configurator/configurator-context/index.js +1 -1
  332. package/dist/configurator/configurator-error-page/BuiltInConfiguratorErrorPage.d.ts +3 -3
  333. package/dist/configurator/configurator-error-page/BuiltInConfiguratorErrorPage.js +1 -1
  334. package/dist/configurator/configurator-error-page/ConfiguratorErrorPageElement.d.ts +0 -5
  335. package/dist/configurator/configurator-error-page/ConfiguratorErrorPageElement.js +1 -1
  336. package/dist/configurator/configurator-error-page/index.d.ts +1 -2
  337. package/dist/configurator/configurator-error-page/index.js +1 -1
  338. package/dist/configurator/index.d.ts +9 -2
  339. package/dist/configurator/index.js +1 -1
  340. package/dist/configurator/pages/ConfiguratorNotFoundPage.js +1 -1
  341. package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
  342. package/dist/configurator/pages/NewConfiguratorPage.js +1 -1
  343. package/dist/configurator/pages/default-configurator-elements.d.ts +6 -2
  344. package/dist/configurator/pages/default-configurator-elements.js +1 -1
  345. package/dist/configurator/pages/error-boundaries/BuiltInGeneralErrorFallback.d.ts +5 -0
  346. package/dist/configurator/pages/error-boundaries/BuiltInGeneralErrorFallback.js +1 -0
  347. package/dist/configurator/pages/error-boundaries/DevContextFallback.js +1 -1
  348. package/dist/configurator/pages/error-boundaries/DevLoadingFallback.js +1 -1
  349. package/dist/configurator/pages/error-boundaries/LoadingErrorBoundary.js +1 -1
  350. package/dist/configurator/pages/error-boundaries/index.d.ts +1 -0
  351. package/dist/configurator/pages/error-boundaries/index.js +1 -1
  352. package/dist/configurator/{configurator-context → pages/provider-slots}/ConfiguratorResponsiveProvider.d.ts +3 -3
  353. package/dist/configurator/pages/provider-slots/ConfiguratorResponsiveProvider.js +1 -0
  354. package/dist/configurator/pages/provider-slots/ContainerSlotProvider.d.ts +3 -0
  355. package/dist/configurator/pages/provider-slots/ContainerSlotProvider.js +1 -0
  356. package/dist/configurator/pages/provider-slots/LoaderSlotProvider.d.ts +3 -0
  357. package/dist/configurator/pages/provider-slots/LoaderSlotProvider.js +1 -0
  358. package/dist/configurator/pages/provider-slots/MessageHostSlotProvider.d.ts +3 -0
  359. package/dist/configurator/pages/provider-slots/MessageHostSlotProvider.js +1 -0
  360. package/dist/configurator/pages/provider-slots/SceneResourcesSlotProvider.d.ts +3 -0
  361. package/dist/configurator/pages/provider-slots/SceneResourcesSlotProvider.js +1 -0
  362. package/dist/configurator/pages/provider-slots/ThemeSlotProvider.d.ts +3 -0
  363. package/dist/configurator/pages/provider-slots/ThemeSlotProvider.js +1 -0
  364. package/dist/configurator/pages/provider-slots/configurator-provider-slot-elements.d.ts +3 -0
  365. package/dist/configurator/pages/provider-slots/configurator-provider-slot-elements.js +1 -0
  366. package/dist/configurator/pages/provider-slots/index.d.ts +6 -0
  367. package/dist/configurator/pages/provider-slots/index.js +1 -0
  368. package/dist/configurator/pages/ui-elements/BuiltInInvalidDeploymentReload.d.ts +2 -0
  369. package/dist/configurator/pages/ui-elements/BuiltInInvalidDeploymentReload.js +1 -0
  370. package/dist/configurator/pages/ui-elements/configurator-ui-elements.d.ts +3 -0
  371. package/dist/configurator/pages/ui-elements/configurator-ui-elements.js +1 -0
  372. package/dist/configurator/pages/ui-elements/index.d.ts +2 -0
  373. package/dist/configurator/pages/ui-elements/index.js +1 -0
  374. package/dist/configurator/scene/cameras/CameraPadding.js +1 -1
  375. package/dist/configurator/ui/assistant/AssistantChips.d.ts +13 -2
  376. package/dist/configurator/ui/assistant/AssistantChips.js +1 -1
  377. package/dist/configurator/ui/assistant/AssistantComposer.d.ts +6 -0
  378. package/dist/configurator/ui/assistant/AssistantComposer.js +2 -1
  379. package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -1
  380. package/dist/configurator/ui/assistant/AssistantThread.d.ts +4 -3
  381. package/dist/configurator/ui/assistant/AssistantThread.js +1 -1
  382. package/dist/configurator/ui/assistant/alert/AssistantAlert.d.ts +4 -1
  383. package/dist/configurator/ui/assistant/alert/AssistantAlert.js +1 -1
  384. package/dist/configurator/ui/assistant/annotation/AnnotationBadge.d.ts +9 -0
  385. package/dist/configurator/ui/assistant/annotation/AnnotationBadge.js +1 -0
  386. package/dist/configurator/ui/assistant/annotation/AnnotationOverlay.d.ts +6 -0
  387. package/dist/configurator/ui/assistant/annotation/AnnotationOverlay.js +1 -0
  388. package/dist/configurator/ui/assistant/annotation/AnnotationPin.d.ts +14 -0
  389. package/dist/configurator/ui/assistant/annotation/AnnotationPin.js +1 -0
  390. package/dist/configurator/ui/assistant/annotation/AnnotationViewer.d.ts +12 -0
  391. package/dist/configurator/ui/assistant/annotation/AnnotationViewer.js +1 -0
  392. package/dist/configurator/ui/assistant/annotation/MessageAnnotations.d.ts +11 -0
  393. package/dist/configurator/ui/assistant/annotation/MessageAnnotations.js +1 -0
  394. package/dist/configurator/ui/assistant/annotation/annotation-host-context.d.ts +5 -0
  395. package/dist/configurator/ui/assistant/annotation/annotation-host-context.js +1 -0
  396. package/dist/configurator/ui/assistant/annotation/annotation-layout.d.ts +26 -0
  397. package/dist/configurator/ui/assistant/annotation/annotation-layout.js +1 -0
  398. package/dist/configurator/ui/assistant/annotation/editor/AnnotationComment.d.ts +15 -0
  399. package/dist/configurator/ui/assistant/annotation/editor/AnnotationComment.js +1 -0
  400. package/dist/configurator/ui/assistant/annotation/editor/AnnotationEditor.d.ts +18 -0
  401. package/dist/configurator/ui/assistant/annotation/editor/AnnotationEditor.js +1 -0
  402. package/dist/configurator/ui/assistant/annotation/editor/annotation-drawing.d.ts +22 -0
  403. package/dist/configurator/ui/assistant/annotation/editor/annotation-drawing.js +1 -0
  404. package/dist/configurator/ui/assistant/annotation/editor/annotation-pins.d.ts +22 -0
  405. package/dist/configurator/ui/assistant/annotation/editor/annotation-pins.js +1 -0
  406. package/dist/configurator/ui/assistant/annotation/editor/flatten-annotation.d.ts +17 -0
  407. package/dist/configurator/ui/assistant/annotation/editor/flatten-annotation.js +1 -0
  408. package/dist/configurator/ui/assistant/annotation/editor/index.d.ts +1 -0
  409. package/dist/configurator/ui/assistant/annotation/editor/index.js +1 -0
  410. package/dist/configurator/ui/assistant/annotation/index.d.ts +4 -0
  411. package/dist/configurator/ui/assistant/annotation/index.js +1 -0
  412. package/dist/configurator/ui/assistant/annotation/prefers-reduced-motion.d.ts +2 -0
  413. package/dist/configurator/ui/assistant/annotation/prefers-reduced-motion.js +1 -0
  414. package/dist/configurator/ui/assistant/annotation/use-end-on-unmount.d.ts +6 -0
  415. package/dist/configurator/ui/assistant/annotation/use-end-on-unmount.js +1 -0
  416. package/dist/configurator/ui/assistant/assistant-failure.d.ts +15 -7
  417. package/dist/configurator/ui/assistant/assistant-failure.js +1 -1
  418. package/dist/configurator/ui/assistant/attachments/MessageImages.d.ts +19 -0
  419. package/dist/configurator/ui/assistant/attachments/MessageImages.js +1 -0
  420. package/dist/configurator/ui/assistant/attachments/index.d.ts +2 -0
  421. package/dist/configurator/ui/assistant/attachments/index.js +1 -0
  422. package/dist/configurator/ui/assistant/attachments/prepare-image.d.ts +24 -0
  423. package/dist/configurator/ui/assistant/attachments/prepare-image.js +1 -0
  424. package/dist/configurator/ui/assistant/composer/composer-frame.d.ts +15 -0
  425. package/dist/configurator/ui/assistant/composer/composer-frame.js +1 -0
  426. package/dist/configurator/ui/assistant/composer/index.d.ts +1 -0
  427. package/dist/configurator/ui/assistant/composer/index.js +1 -0
  428. package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.js +1 -1
  429. package/dist/configurator/ui/assistant/form/AssistantForm.js +1 -1
  430. package/dist/configurator/ui/assistant/form/compose-answer.d.ts +1 -2
  431. package/dist/configurator/ui/assistant/form/compose-answer.js +1 -1
  432. package/dist/configurator/ui/assistant/form/form-model.d.ts +1 -3
  433. package/dist/configurator/ui/assistant/form/form-state.d.ts +1 -1
  434. package/dist/configurator/ui/assistant/form/index.d.ts +2 -0
  435. package/dist/configurator/ui/assistant/form/index.js +1 -1
  436. package/dist/configurator/ui/assistant/form/parse-form-block.d.ts +1 -1
  437. package/dist/configurator/ui/assistant/form/parse-form-block.js +1 -1
  438. package/dist/configurator/ui/assistant/form/read-form-attributes.d.ts +1 -1
  439. package/dist/configurator/ui/assistant/form/read-form-attributes.js +1 -1
  440. package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.d.ts +29 -0
  441. package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.js +1 -0
  442. package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.d.ts +31 -0
  443. package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.js +1 -0
  444. package/dist/configurator/ui/assistant/form/{form-element-definition.d.ts → registry/form-element-definition.d.ts} +3 -1
  445. package/dist/configurator/ui/assistant/form/registry/form-element-definition.js +1 -0
  446. package/dist/configurator/ui/assistant/form/{form-registry.d.ts → registry/form-registry.d.ts} +1 -1
  447. package/dist/configurator/ui/assistant/form/registry/form-registry.js +1 -0
  448. package/dist/configurator/ui/assistant/form/registry/index.d.ts +6 -0
  449. package/dist/configurator/ui/assistant/form/registry/index.js +1 -0
  450. package/dist/configurator/ui/assistant/form/registry/use-form-registry.d.ts +8 -0
  451. package/dist/configurator/ui/assistant/form/registry/use-form-registry.js +1 -0
  452. package/dist/configurator/ui/assistant/form-elements/FormCard.d.ts +1 -1
  453. package/dist/configurator/ui/assistant/form-elements/FormCard.js +1 -1
  454. package/dist/configurator/ui/assistant/form-elements/FormNumber.js +1 -1
  455. package/dist/configurator/ui/assistant/form-elements/FormReply.d.ts +1 -1
  456. package/dist/configurator/ui/assistant/form-elements/FormReply.js +1 -1
  457. package/dist/configurator/ui/assistant/form-elements/FormSwatch.d.ts +1 -1
  458. package/dist/configurator/ui/assistant/form-elements/FormSwatch.js +1 -1
  459. package/dist/configurator/ui/assistant/form-elements/form-element-props.d.ts +5 -5
  460. package/dist/configurator/ui/assistant/index.d.ts +4 -0
  461. package/dist/configurator/ui/assistant/index.js +1 -1
  462. package/dist/configurator/ui/assistant/popover/AssistantPopover.d.ts +24 -0
  463. package/dist/configurator/ui/assistant/popover/AssistantPopover.js +1 -0
  464. package/dist/configurator/ui/assistant/popover/index.d.ts +1 -0
  465. package/dist/configurator/ui/assistant/popover/index.js +1 -0
  466. package/dist/configurator/ui/assistant/reference-link/AssistantReferenceLink.js +1 -1
  467. package/dist/configurator/ui/assistant/reference-link/reference-styles.js +1 -1
  468. package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.d.ts +9 -0
  469. package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.js +1 -0
  470. package/dist/configurator/ui/assistant/screenshots/index.d.ts +3 -0
  471. package/dist/configurator/ui/assistant/screenshots/index.js +1 -0
  472. package/dist/configurator/ui/assistant/screenshots/tool-screenshots.d.ts +16 -0
  473. package/dist/configurator/ui/assistant/screenshots/tool-screenshots.js +1 -0
  474. package/dist/configurator/ui/assistant/screenshots/use-screenshot-src.d.ts +8 -0
  475. package/dist/configurator/ui/assistant/screenshots/use-screenshot-src.js +1 -0
  476. package/dist/configurator/ui/assistant/streamed-markdown.d.ts +11 -0
  477. package/dist/configurator/ui/assistant/streamed-markdown.js +2 -2
  478. package/dist/configurator/ui/assistant/thread-items.d.ts +5 -0
  479. package/dist/configurator/ui/assistant/thread-items.js +1 -1
  480. package/dist/configurator/ui/assistant/tool-chip.d.ts +19 -9
  481. package/dist/configurator/ui/assistant/tool-chip.js +1 -1
  482. package/dist/configurator/ui/assistant/use-load-older-on-top.d.ts +22 -0
  483. package/dist/configurator/ui/assistant/use-load-older-on-top.js +1 -0
  484. package/dist/configurator/ui/configurator-layout/slots/canvas/GPUAccelerationDisabled.js +1 -1
  485. package/dist/configurator/ui/configurator-layout/slots/canvas/LayoutCanvas.js +1 -1
  486. package/dist/configurator/ui/configurator-layout/slots/canvas/SharedCanvas.js +1 -1
  487. package/dist/configurator/ui/configurator-layout/slots/canvas/WebGLNotSupported.js +1 -1
  488. package/dist/configurator/ui/configurator-layout/slots/readonly-banner/CloneToEditDialog.js +1 -1
  489. package/dist/configurator/ui/configurator-layout/universal/DesktopPreview.js +1 -1
  490. package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.d.ts +5 -0
  491. package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.js +1 -0
  492. package/dist/configurator/ui/configurator-layout/universal/Layout.js +1 -1
  493. package/dist/configurator/ui/configurator-layout/universal/MobilePreview.js +1 -1
  494. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -1
  495. package/dist/configurator/ui/index.d.ts +1 -0
  496. package/dist/configurator/ui/index.js +1 -1
  497. package/dist/configurator/ui/legacy/chat/MobileChat.js +1 -1
  498. package/dist/configurator/ui/legacy/layout/common/CommonTopNavigation.js +1 -1
  499. package/dist/configurator/ui/legacy/layout/common/ReactiveInterfaceLayout.js +1 -1
  500. package/dist/configurator/ui/legacy/layout/simple/SimpleSidebar.js +1 -1
  501. package/dist/configurator/ui/legacy/layout/simple/SimpleTopNavigation.js +1 -1
  502. package/dist/configurator/ui/legacy/layout/steps/StepsNavigation.js +1 -1
  503. package/dist/configurator/ui/legacy/layout/steps/StepsOwnerLayout.js +1 -1
  504. package/dist/configurator/ui/legacy/layout/steps/StepsSideBar.js +1 -1
  505. package/dist/configurator/ui/legacy/layout/tabs/TabsSidebar.js +1 -1
  506. package/dist/configurator/ui/phoenix-layout-elements/AppLoader.d.ts +0 -7
  507. package/dist/configurator/ui/phoenix-layout-elements/AppLoader.js +2 -2
  508. package/dist/configurator/ui/phoenix-layout-elements/header/common/back-button/use-go-back-action.js +1 -1
  509. package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/StepsNavigation.js +1 -1
  510. package/dist/configurator/ui/phoenix-layout-elements/index.d.ts +8 -1
  511. package/dist/configurator/ui/phoenix-layout-elements/index.js +1 -1
  512. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/basic/use-basic-sidebar-footer.js +1 -1
  513. package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/PreviousStepButton.js +1 -1
  514. package/dist/configurator/ui/preset-layout/CategoryPresetUi.js +1 -1
  515. package/dist/configurator/ui/preset-layout/PresetUi.d.ts +0 -2
  516. package/dist/configurator/ui/preset-layout/PresetUi.js +1 -1
  517. package/dist/configurator/ui/preset-layout/index.d.ts +6 -1
  518. package/dist/configurator/ui/preset-layout/index.js +1 -1
  519. package/dist/configurator/ui/save-message-snackbars/SaveMessageSnackbars.js +1 -1
  520. package/dist/docs/3d-scene/animations-basic.mdx +0 -4
  521. package/dist/docs/3d-scene/camera-based-hiding.mdx +0 -2
  522. package/dist/docs/3d-scene/cameras/custom.mdx +4 -4
  523. package/dist/docs/3d-scene/cameras/fit-during-drag.mdx +0 -4
  524. package/dist/docs/3d-scene/cameras/locks.mdx +0 -4
  525. package/dist/docs/3d-scene/cameras/transitions.mdx +0 -2
  526. package/dist/docs/3d-scene/engraving.mdx +0 -4
  527. package/dist/docs/3d-scene/materials/custom.mdx +1 -2
  528. package/dist/docs/3d-scene/materials/inherit.mdx +0 -4
  529. package/dist/docs/3d-scene/materials/multilevel.mdx +0 -47
  530. package/dist/docs/3d-scene/materials/override.mdx +0 -4
  531. package/dist/docs/3d-scene/materials/simple.mdx +0 -4
  532. package/dist/docs/3d-scene/materials/transformer.mdx +0 -4
  533. package/dist/docs/3d-scene/materials/transition.mdx +0 -4
  534. package/dist/docs/3d-scene/materials/transmissive.mdx +0 -4
  535. package/dist/docs/3d-scene/postprocessing.mdx +0 -2
  536. package/dist/docs/3d-scene/rendering/basic.mdx +5 -9
  537. package/dist/docs/3d-scene/rendering/custom-entity.mdx +0 -6
  538. package/dist/docs/3d-scene/rendering/lights/ambient-light.mdx +0 -2
  539. package/dist/docs/3d-scene/rendering/lights/directional-light.mdx +0 -2
  540. package/dist/docs/3d-scene/rendering/lights/point-light.mdx +0 -2
  541. package/dist/docs/3d-scene/rendering/lights/rect-area-light.mdx +0 -2
  542. package/dist/docs/3d-scene/rendering/lights/spot-light.mdx +0 -2
  543. package/dist/docs/3d-scene/rendering/object-groups.mdx +0 -2
  544. package/dist/docs/3d-scene/rendering/objects.mdx +0 -4
  545. package/dist/docs/3d-scene/rendering/scene-entity-object.mdx +0 -43
  546. package/dist/docs/3d-scene/rendering/smma.mdx +0 -2
  547. package/dist/docs/3d-scene/rendering/soft-shadows.mdx +0 -2
  548. package/dist/docs/3d-scene/rendering/transformer.mdx +1 -3
  549. package/dist/docs/complex-use-cases/custom-ui-on-root-level.mdx +0 -1
  550. package/dist/docs/complex-use-cases/filtered-controls-with-fix-dialog.mdx +19 -19
  551. package/dist/docs/complex-use-cases/navigation-guard.mdx +81 -19
  552. package/dist/docs/complex-use-cases/navigation-reset-selection.mdx +28 -65
  553. package/dist/docs/complex-use-cases/navigation-reset-undo.mdx +16 -20
  554. package/dist/docs/complex-use-cases/sketching.mdx +2 -10
  555. package/dist/docs/controls/custom.mdx +88 -87
  556. package/dist/docs/controls/manage-child-entities/entitychildrenlist.mdx +0 -4
  557. package/dist/docs/controls/nullable-attributes.mdx +0 -2
  558. package/dist/docs/controls/scene/custom-controls.mdx +7 -8
  559. package/dist/docs/controls/scene/html-hotspots.mdx +0 -4
  560. package/dist/docs/controls/scene/object-html-hotspots.mdx +0 -4
  561. package/dist/docs/controls/select-attribute/cardselect.mdx +0 -4
  562. package/dist/docs/controls/select-attribute/listselect.mdx +0 -4
  563. package/dist/docs/controls/select-attribute/select.mdx +0 -4
  564. package/dist/docs/controls/select-attribute/swatchselect.mdx +0 -4
  565. package/dist/docs/controls/select-attribute/switchselect.mdx +0 -4
  566. package/dist/docs/controls/select-attribute/toggleselect.mdx +0 -4
  567. package/dist/docs/controls/select-child-entity/cardselect.mdx +2 -6
  568. package/dist/docs/controls/select-child-entity/listselect.mdx +5 -11
  569. package/dist/docs/controls/select-child-entity/toggleselect.mdx +4 -10
  570. package/dist/docs/controls/text-input.mdx +90 -19
  571. package/dist/docs/demos/glass.mdx +0 -4
  572. package/dist/docs/demos/heater.mdx +0 -10
  573. package/dist/docs/demos/office-chair.mdx +0 -8
  574. package/dist/docs/demos/office-desk.mdx +0 -8
  575. package/dist/docs/demos/ring.mdx +0 -9
  576. package/dist/docs/dimensions/3d-scene-integration/angle-indicators.mdx +0 -2
  577. package/dist/docs/dimensions/3d-scene-integration/editable-angle-indicator.mdx +0 -2
  578. package/dist/docs/dimensions/3d-scene-integration/editable-distance-dimension-indicator.mdx +0 -2
  579. package/dist/docs/dimensions/3d-scene-integration/editable-offset-indicator.mdx +0 -6
  580. package/dist/docs/dimensions/3d-scene-integration/editable-size-indicator.mdx +0 -4
  581. package/dist/docs/dimensions/3d-scene-integration/list-selector-dimension-indicator.mdx +0 -4
  582. package/dist/docs/dimensions/3d-scene-integration/offset-indicator.mdx +0 -6
  583. package/dist/docs/dimensions/3d-scene-integration/overlap-offset-demo.mdx +0 -6
  584. package/dist/docs/dimensions/input-controls/distance-slider.mdx +6 -6
  585. package/dist/docs/dimensions/selection-controls/list-selector.mdx +0 -2
  586. package/dist/docs/dimensions/selection-controls/toggle-selector.mdx +0 -2
  587. package/dist/docs/drag-and-drop/drag-handle.mdx +0 -1
  588. package/dist/docs/drag-and-drop/hotspot-handle.mdx +0 -1
  589. package/dist/docs/drag-and-drop/no-selection.mdx +0 -1
  590. package/dist/docs/drag-and-drop/planes.mdx +0 -3
  591. package/dist/docs/drag-and-drop/room-planner.mdx +1 -7
  592. package/dist/docs/drag-and-drop/rotatable.mdx +0 -1
  593. package/dist/docs/drag-and-drop/snapping/auto-rotation-demo.mdx +0 -2
  594. package/dist/docs/drag-and-drop/snapping/label-matching-demo.mdx +0 -2
  595. package/dist/docs/drag-and-drop/snapping/multi-snap-composition-demo.mdx +0 -2
  596. package/dist/docs/drag-and-drop/snapping/nested-entity-demo.mdx +0 -2
  597. package/dist/docs/drag-and-drop/snapping/priority-demo.mdx +0 -2
  598. package/dist/docs/drag-and-drop/snapping/snap-axis-demo.mdx +0 -2
  599. package/dist/docs/drag-and-drop/snapping/snap-edge-demo.mdx +0 -2
  600. package/dist/docs/drag-and-drop/snapping/snap-plane-demo.mdx +0 -5
  601. package/dist/docs/drag-and-drop/snapping/snap-point-demo.mdx +0 -2
  602. package/dist/docs/drag-and-drop/spatial-constraints/collision-system.mdx +0 -2
  603. package/dist/docs/drag-and-drop/spatial-constraints/disjoint-and-within.mdx +0 -6
  604. package/dist/docs/drag-and-drop/spatial-constraints/geometry-override.mdx +0 -4
  605. package/dist/docs/drag-and-drop/spatial-constraints/multiple-collision-meshes.mdx +0 -4
  606. package/dist/docs/drag-and-drop/spatial-constraints/touches.mdx +0 -4
  607. package/dist/docs/drag-and-drop/swap.mdx +0 -4
  608. package/dist/docs/fabs/basic.mdx +8 -12
  609. package/dist/docs/fabs/layout.mdx +0 -7
  610. package/dist/docs/index.json +98 -4
  611. package/dist/docs/integrations/lifecycle-providers.mdx +0 -12
  612. package/dist/docs/integrations/onloadprovider.mdx +0 -3
  613. package/dist/docs/integrations/webcomponent-api/basics.mdx +4 -2
  614. package/dist/docs/integrations/webcomponent-api/preset-tags.mdx +0 -1
  615. package/dist/docs/layouts/custom-layout.mdx +309 -189
  616. package/dist/docs/layouts/custom-price.mdx +0 -2
  617. package/dist/docs/layouts/custom-theme-handler.mdx +0 -4
  618. package/dist/docs/layouts/gallery/ardent-compressor.mdx +3485 -0
  619. package/dist/docs/layouts/gallery/corvid-drone.mdx +3399 -0
  620. package/dist/docs/layouts/gallery/fenwick-window.mdx +2778 -0
  621. package/dist/docs/layouts/gallery/ferrule-bar-station.mdx +6540 -0
  622. package/dist/docs/layouts/gallery/kestrel-kettle.mdx +1829 -0
  623. package/dist/docs/layouts/gallery/nightjar-guitar.mdx +3665 -0
  624. package/dist/docs/layouts/gallery/sunfold-solar.mdx +2657 -0
  625. package/dist/docs/layouts/gallery/tidepool-aquascape.mdx +5157 -0
  626. package/dist/docs/layouts/header/custom.mdx +0 -2
  627. package/dist/docs/layouts/icon-sets.mdx +0 -6
  628. package/dist/docs/layouts/phoenix-elements.mdx +0 -4
  629. package/dist/docs/layouts/phoenix-handlers.mdx +19 -28
  630. package/dist/docs/layouts/presets/action-and-overlay.mdx +0 -8
  631. package/dist/docs/layouts/presets/custom.mdx +6 -13
  632. package/dist/docs/layouts/preview/always-expand.mdx +4 -14
  633. package/dist/docs/layouts/preview/always.mdx +4 -14
  634. package/dist/docs/layouts/preview/auto-presets.mdx +4 -14
  635. package/dist/docs/layouts/preview/auto.mdx +4 -14
  636. package/dist/docs/layouts/preview/never.mdx +4 -14
  637. package/dist/docs/layouts/sidebar/in-a-custom-layout.mdx +18 -13
  638. package/dist/docs/layouts/sidebar/property-page-stack.mdx +0 -2
  639. package/dist/docs/layouts/sidebar/property-page.mdx +0 -2
  640. package/dist/docs/layouts/sidebar/tabs.mdx +0 -4
  641. package/dist/docs/layouts/sidebar-footer/custom.mdx +0 -2
  642. package/dist/docs/layouts/sidebar-footer/universal.mdx +0 -10
  643. package/dist/docs/layouts/tool-controls.mdx +0 -1
  644. package/dist/docs/layouts/toolbar.mdx +0 -4
  645. package/dist/docs/layouts/universal.mdx +2 -10
  646. package/dist/docs/other-features/client-side-rendering/camera-padding.mdx +0 -2
  647. package/dist/docs/other-features/client-side-rendering/custom.mdx +0 -6
  648. package/dist/docs/other-features/client-side-rendering/declarative.mdx +1 -5
  649. package/dist/docs/other-features/client-side-rendering/postprocessing.mdx +0 -6
  650. package/dist/docs/other-features/component-filters.mdx +0 -7
  651. package/dist/docs/other-features/currency.mdx +5 -6
  652. package/dist/docs/other-features/domain-actions.mdx +0 -5
  653. package/dist/docs/other-features/dynamic-component-attachments.mdx +75 -46
  654. package/dist/docs/other-features/error-5xx.mdx +0 -2
  655. package/dist/docs/other-features/navigation-hotspot.mdx +0 -5
  656. package/dist/docs/other-features/not-found-404.mdx +0 -2
  657. package/dist/docs/other-features/projected-entities.mdx +0 -5
  658. package/dist/docs/other-features/side-effects.mdx +0 -7
  659. package/dist/docs/other-features/transformers.mdx +101 -96
  660. package/dist/docs/other-features/violation-messages.mdx +0 -4
  661. package/dist/docs/other-features/violation-severity.mdx +2 -3
  662. package/dist/docs/performance/clone-highlight.mdx +0 -7
  663. package/dist/docs/performance/dynamic-components.mdx +0 -6
  664. package/dist/docs/performance/editable-dimension-stress-test.mdx +0 -2
  665. package/dist/docs/performance/fast-highlight.mdx +0 -7
  666. package/dist/docs/performance/general.mdx +0 -7
  667. package/dist/docs/performance/render-isolation.mdx +0 -6
  668. package/dist/docs/phoenix/admin.mdx +75 -100
  669. package/dist/docs/phoenix/application-states.mdx +45 -27
  670. package/dist/docs/phoenix/components.mdx +53 -46
  671. package/dist/docs/phoenix/configurator-interface.mdx +205 -123
  672. package/dist/docs/phoenix/deployments.mdx +11 -1
  673. package/dist/docs/phoenix/domain-model.mdx +24 -16
  674. package/dist/docs/phoenix/expression-context.mdx +3 -29
  675. package/dist/docs/phoenix/getting-started.mdx +0 -2
  676. package/dist/docs/phoenix/layout-slots.mdx +21 -0
  677. package/dist/docs/phoenix/localization.mdx +64 -54
  678. package/dist/docs/phoenix/migration-guide.mdx +10 -11
  679. package/dist/docs/phoenix/phoenix-cli.mdx +57 -15
  680. package/dist/docs/phoenix/project-structure.mdx +52 -55
  681. package/dist/docs/phoenix/quickstart.mdx +1 -3
  682. package/dist/docs/phoenix/sdk-builder-workflow.mdx +7 -11
  683. package/dist/docs/phoenix/sessions-and-access.mdx +2 -0
  684. package/dist/docs/phoenix/state-management.mdx +18 -16
  685. package/dist/docs/phoenix/testing.mdx +6 -1
  686. package/dist/docs/phoenix/webhooks.mdx +9 -1
  687. package/dist/docs/recipes-and-delete/delete-entity.mdx +3 -6
  688. package/dist/docs/recipes-and-delete/dynamic-root-entity.mdx +0 -2
  689. package/dist/docs/recipes-and-delete/grouped-button.mdx +0 -3
  690. package/dist/docs/recipes-and-delete/grouped-panel.mdx +0 -3
  691. package/dist/docs/recipes-and-delete/horizontal.mdx +3 -6
  692. package/dist/docs/recipes-and-delete/recipe-hierarchy-lists.mdx +0 -3
  693. package/dist/docs/recipes-and-delete/recipe-hierarchy-selection.mdx +0 -5
  694. package/dist/docs/recipes-and-delete/replace-dropdown.mdx +3 -6
  695. package/dist/docs/recipes-and-delete/replace-panel.mdx +0 -7
  696. package/dist/docs/recipes-and-delete/simple.mdx +3 -6
  697. package/dist/docs/recipes-and-delete/vertical.mdx +3 -6
  698. package/dist/docs/relations/aliases.mdx +0 -2
  699. package/dist/docs/relations/all-of-type.mdx +0 -4
  700. package/dist/docs/relations/child-nullable.mdx +0 -4
  701. package/dist/docs/relations/child-required.mdx +0 -2
  702. package/dist/docs/relations/collection-direct.mdx +0 -4
  703. package/dist/docs/relations/collection-indirect.mdx +0 -4
  704. package/dist/docs/relations/collection-multiple.mdx +0 -2
  705. package/dist/docs/relations/cross-reference.mdx +0 -2
  706. package/dist/docs/relations/hierarchy.mdx +0 -2
  707. package/dist/docs/selection-and-highlight/canvas.mdx +0 -1
  708. package/dist/docs/selection-and-highlight/disable-highlight-per-object.mdx +0 -1
  709. package/dist/docs/selection-and-highlight/disable-highlight.mdx +0 -1
  710. package/dist/docs/selection-and-highlight/effects/outline.mdx +0 -3
  711. package/dist/docs/selection-and-highlight/effects/overlay.mdx +0 -3
  712. package/dist/docs/selection-and-highlight/fast-emissive-highlight.mdx +0 -3
  713. package/dist/docs/selection-and-highlight/group-highlighting.mdx +0 -1
  714. package/dist/docs/selection-and-highlight/hierarchy.mdx +100 -151
  715. package/dist/docs/selection-and-highlight/hotspots.mdx +0 -1
  716. package/dist/domain/application/GetApplicationWrapperHandler.d.ts +1 -1
  717. package/dist/domain/application/GetThemeHandler.d.ts +1 -1
  718. package/dist/domain/application/GetWebComponentOptions.d.ts +1 -1
  719. package/dist/domain/application/application-config.d.ts +6 -26
  720. package/dist/domain/application/assistant/assistant-fields.d.ts +1 -1
  721. package/dist/domain/application/assistant/assistant-value-type.d.ts +1 -1
  722. package/dist/domain/application/assistant/build-assistant-lenses.d.ts +1 -2
  723. package/dist/domain/application/assistant/build-assistant-lenses.js +1 -1
  724. package/dist/domain/application/assistant/build-assistant-manifest.d.ts +1 -2
  725. package/dist/domain/application/assistant/build-assistant-manifest.js +1 -1
  726. package/dist/domain/application/assistant/generate-assistant-prompt.d.ts +2 -3
  727. package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -1
  728. package/dist/domain/application/assistant/index.d.ts +0 -4
  729. package/dist/domain/application/assistant/index.js +1 -1
  730. package/dist/domain/application/assistant/session-setup/AssistantSetupHandler.d.ts +1 -1
  731. package/dist/domain/application/assistant/session-setup/apply-assistant-setup.d.ts +1 -1
  732. package/dist/domain/application/assistant/session-setup/apply-assistant-setup.js +1 -1
  733. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.d.ts +5 -5
  734. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.js +1 -1
  735. package/dist/domain/application/assistant/session-setup/create-session-assistant.d.ts +1 -2
  736. package/dist/domain/application/assistant-field-options.d.ts +1 -1
  737. package/dist/domain/application/assistant-mutations.d.ts +1 -1
  738. package/dist/domain/application/assistant-mutations.js +1 -1
  739. package/dist/domain/application/assistant-recipe-groups.d.ts +1 -1
  740. package/dist/domain/application/configurator-builder.d.ts +7 -2
  741. package/dist/domain/application/configurator-builder.js +1 -1
  742. package/dist/domain/application/create-initial-published-data.d.ts +2 -1
  743. package/dist/domain/application/index.d.ts +1 -1
  744. package/dist/domain/application/index.js +1 -1
  745. package/dist/domain/application/published-data/DataRowValidatorHandler.d.ts +1 -1
  746. package/dist/domain/application/published-data/FilterPublishedChunksHandler.d.ts +1 -1
  747. package/dist/domain/application/published-data/MapPublishedChunkedDataHandler.d.ts +1 -1
  748. package/dist/domain/application/published-data/MapPublishedRevisionDataHandler.d.ts +1 -1
  749. package/dist/domain/application/published-data/SheetValidatorHandler.d.ts +1 -1
  750. package/dist/domain/application/standard-properties.js +1 -1
  751. package/dist/domain/application/web-component-property.d.ts +3 -1
  752. package/dist/domain/application/web-component-property.js +1 -1
  753. package/dist/domain/available-system-components/available-system-components.d.ts +11 -0
  754. package/dist/domain/available-system-components/available-system-components.js +1 -0
  755. package/dist/domain/available-system-components/index.d.ts +1 -0
  756. package/dist/domain/available-system-components/index.js +1 -0
  757. package/dist/domain/core/invariant/violation.d.ts +3 -2
  758. package/dist/domain/{domain-builder.d.ts → domain-builder/domain-builder.d.ts} +11 -5
  759. package/dist/domain/domain-builder/domain-builder.js +1 -0
  760. package/dist/domain/domain-builder/index.d.ts +1 -0
  761. package/dist/domain/domain-builder/index.js +1 -0
  762. package/dist/domain/domain-context/store-system/store-system.d.ts +5 -0
  763. package/dist/domain/domain-context/store-system/store-system.js +1 -1
  764. package/dist/domain/domain-context/transformers/context.d.ts +4 -2
  765. package/dist/domain/domain-context/transformers/when-builder.d.ts +2 -1
  766. package/dist/domain/domain-runtime/application.d.ts +39 -0
  767. package/dist/domain/domain-runtime/application.js +1 -0
  768. package/dist/domain/domain-runtime/domain-context/selectors.d.ts +1 -0
  769. package/dist/domain/domain-runtime/domain-context/selectors.js +1 -1
  770. package/dist/domain/domain-runtime/domain-runtime.d.ts +68 -28
  771. package/dist/domain/domain-runtime/domain-runtime.js +1 -1
  772. package/dist/domain/domain-runtime/environment/initialize.d.ts +1 -1
  773. package/dist/domain/domain-runtime/environment/types.d.ts +1 -1
  774. package/dist/domain/domain-runtime/index.d.ts +1 -1
  775. package/dist/domain/domain-runtime/index.js +1 -1
  776. package/dist/domain/domain-runtime/spec/create-violation-expressions.d.ts +1 -1
  777. package/dist/domain/domain-runtime/spec/create-violation-expressions.js +1 -1
  778. package/dist/domain/domain-runtime/spec/index.d.ts +1 -1
  779. package/dist/domain/domain-runtime/spec/index.js +1 -1
  780. package/dist/domain/domain-runtime/spec/initialize.js +1 -1
  781. package/dist/domain/domain-runtime/spec/{module-checks/resolve-imported-transformers.d.ts → resolve-imported-transformers.d.ts} +3 -3
  782. package/dist/domain/domain-runtime/spec/resolve-imported-transformers.js +1 -0
  783. package/dist/domain/domain-runtime/spec/selectors.d.ts +8 -6
  784. package/dist/domain/domain-runtime/spec/selectors.js +1 -1
  785. package/dist/domain/domain-runtime/spec/types.d.ts +2 -2
  786. package/dist/domain/domain-runtime/web-component/actions.d.ts +3 -0
  787. package/dist/domain/domain-runtime/web-component/actions.js +1 -1
  788. package/dist/domain/domain-runtime/web-component/initialize.d.ts +1 -0
  789. package/dist/domain/domain-runtime/web-component/initialize.js +1 -1
  790. package/dist/domain/domain-runtime/web-component/selectors.d.ts +2 -0
  791. package/dist/domain/domain-runtime/web-component/selectors.js +1 -1
  792. package/dist/domain/domain-runtime/web-component/types.d.ts +6 -3
  793. package/dist/domain/extensions/export-images/agent-screenshot.d.ts +30 -0
  794. package/dist/domain/extensions/export-images/agent-screenshot.js +1 -0
  795. package/dist/domain/extensions/{client-side-image.d.ts → export-images/client-side-image.d.ts} +2 -2
  796. package/dist/domain/extensions/export-images/client-side-image.js +1 -0
  797. package/dist/domain/extensions/export-images/index.d.ts +3 -0
  798. package/dist/domain/extensions/export-images/index.js +1 -0
  799. package/dist/domain/extensions/{save-template-thumbnail.d.ts → export-images/save-template-thumbnail.d.ts} +2 -2
  800. package/dist/domain/extensions/export-images/save-template-thumbnail.js +1 -0
  801. package/dist/domain/extensions/index.d.ts +1 -2
  802. package/dist/domain/extensions/index.js +1 -1
  803. package/dist/domain/index.d.ts +15 -2
  804. package/dist/domain/index.js +1 -1
  805. package/dist/domain/mocks/create-mock-domain-runtime.d.ts +9 -0
  806. package/dist/domain/mocks/create-test-application-config.d.ts +1 -1
  807. package/dist/domain/spec/application/admin-pages-section.d.ts +29 -0
  808. package/dist/domain/spec/application/admin-pages.d.ts +26 -0
  809. package/dist/domain/spec/application/admin-states.d.ts +5 -0
  810. package/dist/domain/spec/application/configurator-states.d.ts +8 -0
  811. package/dist/domain/spec/application/index.d.ts +5 -0
  812. package/dist/domain/spec/application/index.js +1 -0
  813. package/dist/domain/spec/components/bindings/index.d.ts +2 -0
  814. package/dist/domain/spec/components/bindings/read-binding.d.ts +13 -0
  815. package/dist/domain/spec/components/bindings/write-binding.d.ts +32 -0
  816. package/dist/{application-spec/parse-application-files → domain}/spec/components/component-entry.d.ts +3 -3
  817. package/dist/domain/spec/components/component-entry.js +1 -0
  818. package/dist/{application-spec/core → domain/spec}/components/component-filter.d.ts +3 -2
  819. package/dist/domain/spec/components/component-filters.d.ts +10 -0
  820. package/dist/domain/spec/components/component-filters.js +1 -0
  821. package/dist/domain/spec/components/components.d.ts +4 -0
  822. package/dist/domain/spec/components/components.js +1 -0
  823. package/dist/domain/spec/components/index.d.ts +6 -0
  824. package/dist/domain/spec/enhanced-json/enhanced-json-value.d.ts +30 -0
  825. package/dist/domain/spec/enhanced-json/enhanced-json-value.js +1 -0
  826. package/dist/domain/spec/enhanced-json/index.d.ts +1 -0
  827. package/dist/domain/spec/enhanced-json/index.js +1 -0
  828. package/dist/domain/{domain-runtime → spec}/errors/implementation-error-collector.d.ts +2 -0
  829. package/dist/domain/{domain-runtime → spec}/errors/implementation-error-collector.js +1 -1
  830. package/dist/{application-spec/parse-application-files → domain}/spec/expressions/expression.d.ts +9 -3
  831. package/dist/domain/spec/expressions/expression.js +1 -0
  832. package/dist/domain/spec/expressions/expressions.d.ts +27 -0
  833. package/dist/domain/spec/expressions/expressions.js +1 -0
  834. package/dist/domain/spec/expressions/index.d.ts +5 -0
  835. package/dist/domain/spec/expressions/memo-eval-plan.d.ts +70 -0
  836. package/dist/domain/spec/expressions/memo-eval-plan.js +1 -0
  837. package/dist/domain/spec/expressions/relation-expression-params.js +1 -0
  838. package/dist/domain/spec/index.d.ts +4 -0
  839. package/dist/domain/spec/index.js +1 -0
  840. package/dist/domain/spec/invariants/index.d.ts +2 -0
  841. package/dist/domain/spec/invariants/index.js +1 -0
  842. package/dist/domain/spec/invariants/invariant.d.ts +24 -0
  843. package/dist/domain/spec/invariants/invariant.js +1 -0
  844. package/dist/domain/spec/invariants/invariants.d.ts +14 -0
  845. package/dist/domain/spec/invariants/invariants.js +1 -0
  846. package/dist/domain/spec/json-schema.d.ts +52 -0
  847. package/dist/domain/spec/json-schema.js +1 -0
  848. package/dist/domain/spec/markers/index.d.ts +3 -0
  849. package/dist/domain/spec/markers/index.js +1 -0
  850. package/dist/domain/spec/markers/msg-marker.d.ts +28 -0
  851. package/dist/domain/spec/markers/msg-marker.js +1 -0
  852. package/dist/domain/spec/model/aliases.d.ts +9 -0
  853. package/dist/domain/spec/model/aliases.js +1 -0
  854. package/dist/domain/spec/model/common-types.d.ts +15 -0
  855. package/dist/domain/spec/model/common-types.js +1 -0
  856. package/dist/domain/spec/model/defaults.d.ts +33 -0
  857. package/dist/domain/spec/model/defaults.js +1 -0
  858. package/dist/domain/spec/model/entity-types.d.ts +8 -0
  859. package/dist/domain/spec/model/entity-types.js +1 -0
  860. package/dist/domain/spec/model/index.d.ts +7 -0
  861. package/dist/domain/spec/model/index.js +1 -0
  862. package/dist/domain/spec/model/model-version-key.d.ts +3 -0
  863. package/dist/domain/spec/model/model-version-key.js +1 -0
  864. package/dist/domain/spec/model/names/action-name.d.ts +7 -0
  865. package/dist/domain/spec/model/names/action-name.js +1 -0
  866. package/dist/domain/spec/model/names/alias-name.d.ts +7 -0
  867. package/dist/domain/spec/model/names/alias-name.js +1 -0
  868. package/dist/domain/spec/model/names/entity-or-alias-name.d.ts +8 -0
  869. package/dist/domain/spec/model/names/entity-or-alias-name.js +1 -0
  870. package/dist/domain/spec/model/names/entity-type-name.d.ts +8 -0
  871. package/dist/domain/spec/model/names/entity-type-name.js +1 -0
  872. package/dist/domain/spec/model/names/expression-name.d.ts +6 -0
  873. package/dist/domain/spec/model/names/expression-name.js +1 -0
  874. package/dist/domain/spec/model/names/index.d.ts +9 -0
  875. package/dist/domain/spec/model/names/index.js +1 -0
  876. package/dist/domain/spec/model/names/instance-name.d.ts +7 -0
  877. package/dist/domain/spec/model/names/instance-name.js +1 -0
  878. package/dist/domain/spec/model/names/invariant-name.d.ts +3 -0
  879. package/dist/domain/spec/model/names/invariant-name.js +1 -0
  880. package/dist/domain/spec/model/names/shape-property-name.d.ts +11 -0
  881. package/dist/domain/spec/model/names/shape-property-name.js +1 -0
  882. package/dist/domain/spec/model/names/violation-type-name.d.ts +3 -0
  883. package/dist/domain/spec/model/names/violation-type-name.js +1 -0
  884. package/dist/domain/spec/model/presets.d.ts +30 -0
  885. package/dist/domain/spec/model/presets.js +1 -0
  886. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/element-registration.d.ts +16 -2
  887. package/dist/domain/spec/modules/element-registration.js +1 -0
  888. package/dist/domain/spec/modules/find-assistant-form-element-errors.d.ts +12 -0
  889. package/dist/domain/spec/modules/find-assistant-form-element-errors.js +1 -0
  890. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/index.d.ts +4 -3
  891. package/dist/domain/spec/modules/index.js +1 -0
  892. package/dist/domain/spec/modules/merge-expression-modules.d.ts +23 -0
  893. package/dist/domain/spec/modules/merge-expression-modules.js +1 -0
  894. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/project-modules.d.ts +1 -1
  895. package/dist/domain/spec/modules/project-modules.js +1 -0
  896. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-action-modules.d.ts +1 -1
  897. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-component-optic-modules.d.ts +1 -1
  898. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-expression-modules.d.ts +4 -5
  899. package/dist/domain/spec/modules/resolve-expression-modules.js +1 -0
  900. package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-invariant-modules.d.ts +1 -1
  901. package/dist/domain/spec/phx/index.d.ts +1 -0
  902. package/dist/domain/spec/phx/index.js +1 -0
  903. package/dist/domain/spec/phx/phx-application-types.d.ts +25 -0
  904. package/dist/domain/spec/phx/phx-application-types.js +1 -0
  905. package/dist/domain/spec/phx/phx-domain.d.ts +36 -0
  906. package/dist/domain/spec/phx/phx-domain.js +1 -0
  907. package/dist/domain/spec/phx/phx-model.d.ts +24 -0
  908. package/dist/domain/spec/phx/phx-model.js +1 -0
  909. package/dist/domain/spec/phx/phx.d.ts +21 -0
  910. package/dist/domain/spec/phx/phx.js +1 -0
  911. package/dist/domain/spec/runtime/admin-runtime/admin-runtime.d.ts +32 -0
  912. package/dist/domain/spec/runtime/admin-runtime/admin-runtime.js +1 -0
  913. package/dist/domain/spec/runtime/admin-runtime/admin-spec-runtime.d.ts +16 -0
  914. package/dist/domain/spec/runtime/admin-runtime/admin-spec-runtime.js +1 -0
  915. package/dist/domain/spec/runtime/admin-runtime/index.d.ts +2 -0
  916. package/dist/domain/spec/runtime/admin-runtime/index.js +1 -0
  917. package/dist/{application-spec/generate-application → domain/spec}/runtime/admin-runtime/states-runtime.d.ts +1 -1
  918. package/dist/{application-spec/generate-application → domain/spec}/runtime/application-runtime.d.ts +23 -8
  919. package/dist/domain/spec/runtime/application-runtime.js +1 -0
  920. package/dist/domain/spec/runtime/application-spec-slice.d.ts +73 -0
  921. package/dist/domain/spec/runtime/application-spec-slice.js +1 -0
  922. package/dist/domain/spec/runtime/configurator-runtime/configurator-runtime.d.ts +37 -0
  923. package/dist/domain/spec/runtime/configurator-runtime/configurator-runtime.js +1 -0
  924. package/dist/domain/spec/runtime/configurator-runtime/configurator-spec-runtime.d.ts +16 -0
  925. package/dist/domain/spec/runtime/configurator-runtime/configurator-spec-runtime.js +1 -0
  926. package/dist/domain/spec/runtime/configurator-runtime/index.d.ts +2 -0
  927. package/dist/domain/spec/runtime/configurator-runtime/index.js +1 -0
  928. package/dist/{application-spec/generate-application → domain/spec}/runtime/configurator-runtime/states-runtime.d.ts +1 -1
  929. package/dist/domain/spec/runtime/create-application.d.ts +58 -0
  930. package/dist/domain/spec/runtime/create-application.js +1 -0
  931. package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/expressions}/expression-runtime.d.ts +13 -5
  932. package/dist/domain/spec/runtime/expressions/expression-runtime.js +1 -0
  933. package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/expressions}/generate-memo-eval-plan-runtime.d.ts +2 -2
  934. package/dist/domain/spec/runtime/expressions/generate-memo-eval-plan-runtime.js +1 -0
  935. package/dist/domain/spec/runtime/expressions/index.d.ts +3 -0
  936. package/dist/domain/spec/runtime/expressions/index.js +1 -0
  937. package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/expressions}/reflection-constants-runtime.d.ts +2 -1
  938. package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/factory-data-runtime.d.ts +4 -3
  939. package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/recipe-defaults.d.ts +1 -1
  940. package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/recipe-defaults.js +1 -1
  941. package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/static-violations.d.ts +2 -2
  942. package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/static-violations.js +1 -1
  943. package/dist/{application-spec/generate-application → domain/spec}/runtime/generate-runtime-instance.d.ts +1 -1
  944. package/dist/domain/spec/runtime/generate-runtime-instance.js +1 -0
  945. package/dist/domain/spec/runtime/index.d.ts +13 -0
  946. package/dist/domain/spec/runtime/index.js +1 -0
  947. package/dist/{application-spec/generate-application → domain/spec}/runtime/migrations/migration-runtime.d.ts +2 -2
  948. package/dist/domain/spec/runtime/migrations/migration-runtime.js +1 -0
  949. package/dist/{application-spec/generate-application/runtime/admin-runtime → domain/spec/runtime/model}/admin-model-runtime.d.ts +6 -5
  950. package/dist/domain/spec/runtime/model/admin-model-runtime.js +1 -0
  951. package/dist/{application-spec/generate-application/runtime/configurator-runtime → domain/spec/runtime/model}/configurator-model-runtime.d.ts +6 -5
  952. package/dist/domain/spec/runtime/model/configurator-model-runtime.js +1 -0
  953. package/dist/domain/spec/runtime/model/index.d.ts +2 -0
  954. package/dist/domain/spec/runtime/model/index.js +1 -0
  955. package/dist/domain/spec/runtime/optics/build-sdk-optic.d.ts +7 -0
  956. package/dist/domain/spec/runtime/optics/build-sdk-optic.js +1 -0
  957. package/dist/domain/spec/runtime/optics/build-user-bound-optic.d.ts +7 -0
  958. package/dist/domain/spec/runtime/optics/build-user-bound-optic.js +1 -0
  959. package/dist/domain/spec/runtime/optics/built-component-optic.d.ts +11 -0
  960. package/dist/domain/spec/runtime/optics/built-component-optic.js +1 -0
  961. package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/optics}/component-optic.d.ts +4 -14
  962. package/dist/domain/spec/runtime/optics/index.d.ts +2 -0
  963. package/dist/domain/spec/runtime/optics/index.js +1 -0
  964. package/dist/{application-spec/caching → domain/spec/runtime}/runtime-reference-store.d.ts +1 -5
  965. package/dist/domain/spec/runtime/runtime-reference-store.js +1 -0
  966. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/actions-runtime.d.ts +3 -3
  967. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/assistant-runtime.d.ts +2 -2
  968. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/data-runtime.d.ts +1 -1
  969. package/dist/domain/spec/runtime/sections/index.d.ts +6 -0
  970. package/dist/domain/spec/runtime/sections/index.js +1 -0
  971. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/preset-runtime.d.ts +4 -4
  972. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/theme-runtime.d.ts +1 -1
  973. package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/transformers-runtime.d.ts +2 -2
  974. package/dist/{application-spec/generate-application → domain/spec}/runtime/spec-code/resolve-spec-code.d.ts +1 -1
  975. package/dist/domain/spec/runtime/spec-code/spec-code-module.js +1 -0
  976. package/dist/domain/spec/runtime/spec-runtime.d.ts +21 -0
  977. package/dist/domain/spec/runtime/spec-runtime.js +1 -0
  978. package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/encoded-runtime-spec.d.ts +3 -2
  979. package/dist/domain/spec/runtime-spec/index.d.ts +4 -0
  980. package/dist/domain/spec/runtime-spec/index.js +1 -0
  981. package/dist/domain/spec/runtime-spec/runtime-spec.d.ts +174 -0
  982. package/dist/domain/spec/runtime-spec/runtime-spec.js +1 -0
  983. package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/spec-code-id.d.ts +1 -1
  984. package/dist/domain/spec/sections/actions.d.ts +23 -0
  985. package/dist/domain/spec/sections/actions.js +1 -0
  986. package/dist/domain/spec/sections/app-metadata.d.ts +18 -0
  987. package/dist/domain/spec/sections/app-metadata.js +1 -0
  988. package/dist/domain/spec/sections/application-version.d.ts +7 -0
  989. package/dist/domain/spec/sections/application-version.js +1 -0
  990. package/dist/domain/{application → spec/sections}/assistant/assistant-manifest.d.ts +29 -4
  991. package/dist/domain/{application → spec/sections}/assistant/assistant-resolved-manifest.d.ts +12 -9
  992. package/dist/domain/spec/sections/assistant/assistant-resolved-manifest.js +1 -0
  993. package/dist/domain/spec/sections/assistant/form/assistant-form-element-name.d.ts +11 -0
  994. package/dist/domain/spec/sections/assistant/form/assistant-form-element-name.js +8 -0
  995. package/dist/domain/{application → spec/sections}/assistant/form/assistant-form-elements.d.ts +39 -28
  996. package/dist/domain/spec/sections/assistant/form/assistant-form-elements.js +1 -0
  997. package/dist/domain/{application → spec/sections}/assistant/form/index.d.ts +1 -1
  998. package/dist/domain/spec/sections/assistant/form/index.js +1 -0
  999. package/dist/domain/spec/sections/assistant/index.d.ts +4 -0
  1000. package/dist/domain/spec/sections/assistant/index.js +1 -0
  1001. package/dist/domain/spec/sections/data-spec/chunks.d.ts +23 -0
  1002. package/dist/domain/spec/sections/data-spec/chunks.js +1 -0
  1003. package/dist/domain/spec/sections/data-spec/custom-data-spec-entry.d.ts +16 -0
  1004. package/dist/domain/spec/sections/data-spec/custom-data-spec-entry.js +1 -0
  1005. package/dist/domain/spec/sections/data-spec/data-entry-key.d.ts +9 -0
  1006. package/dist/domain/spec/sections/data-spec/data-entry-key.js +1 -0
  1007. package/dist/domain/spec/sections/data-spec/index.d.ts +5 -0
  1008. package/dist/domain/spec/sections/data-spec/index.js +1 -0
  1009. package/dist/domain/spec/sections/data-spec/parsed-data-spec-entries.d.ts +10 -0
  1010. package/dist/domain/spec/sections/data-spec/parsed-data-spec-entries.js +1 -0
  1011. package/dist/domain/spec/sections/data-spec/swatch-data-spec-entry.d.ts +14 -0
  1012. package/dist/domain/spec/sections/data-spec/swatch-data-spec-entry.js +1 -0
  1013. package/dist/domain/spec/sections/index.d.ts +10 -0
  1014. package/dist/domain/spec/sections/index.js +1 -0
  1015. package/dist/domain/spec/sections/infra/acl.d.ts +12 -0
  1016. package/dist/domain/spec/sections/infra/acl.js +1 -0
  1017. package/dist/domain/spec/sections/infra/index.d.ts +3 -0
  1018. package/dist/domain/spec/sections/infra/index.js +1 -0
  1019. package/dist/domain/spec/sections/infra/resource-entry.d.ts +46 -0
  1020. package/dist/domain/spec/sections/infra/resource-entry.js +1 -0
  1021. package/dist/domain/spec/sections/infra/resources-config.d.ts +10 -0
  1022. package/dist/domain/spec/sections/infra/resources-config.js +1 -0
  1023. package/dist/domain/spec/sections/state-value.d.ts +7 -0
  1024. package/dist/domain/spec/sections/state-value.js +1 -0
  1025. package/dist/domain/spec/sections/theme.d.ts +10 -0
  1026. package/dist/domain/spec/sections/theme.js +1 -0
  1027. package/dist/domain/spec/sections/transformers.d.ts +15 -0
  1028. package/dist/domain/spec/sections/transformers.js +1 -0
  1029. package/dist/domain/spec/sections/ui.d.ts +12 -0
  1030. package/dist/domain/spec/sections/ui.js +1 -0
  1031. package/dist/domain/system-components/phoenix-entity-elements/child-entity-card-selector.d.ts +2 -3
  1032. package/dist/domain/system-components/phoenix-entity-elements/child-entity-list-selector.d.ts +2 -3
  1033. package/dist/domain/system-components/phoenix-entity-elements/child-entity-toggle-selector.d.ts +2 -3
  1034. package/dist/domain/system-definitions.js +1 -1
  1035. package/dist/domain/utils/index.d.ts +2 -0
  1036. package/dist/domain/utils/index.js +1 -0
  1037. package/dist/editor/domain-editors/DomainSpecContext.js +1 -1
  1038. package/dist/editor/domain-editors/apply-spec-update.d.ts +10 -2
  1039. package/dist/editor/domain-editors/apply-spec-update.js +1 -1
  1040. package/dist/editor/domain-editors/use-implementation-files.d.ts +6 -14
  1041. package/dist/editor/domain-editors/use-implementation-files.js +1 -1
  1042. package/dist/editor/graphql/GraphQLPanel.js +1 -1
  1043. package/dist/editor/screenshot/ScreenshotHandler.js +1 -1
  1044. package/dist/i18n/locales/cs/messages.js +1 -1
  1045. package/dist/i18n/locales/da/messages.js +1 -1
  1046. package/dist/i18n/locales/de/messages.js +1 -1
  1047. package/dist/i18n/locales/de-AT/messages.js +1 -1
  1048. package/dist/i18n/locales/en/messages.js +1 -1
  1049. package/dist/i18n/locales/en-AU/messages.js +1 -1
  1050. package/dist/i18n/locales/en-GB/messages.js +1 -1
  1051. package/dist/i18n/locales/es/messages.js +1 -1
  1052. package/dist/i18n/locales/fr/messages.js +1 -1
  1053. package/dist/i18n/locales/hu/messages.js +1 -1
  1054. package/dist/i18n/locales/it/messages.js +1 -1
  1055. package/dist/i18n/locales/nl/messages.js +1 -1
  1056. package/dist/i18n/locales/pl/messages.js +1 -1
  1057. package/dist/i18n/locales/pt/messages.js +1 -1
  1058. package/dist/i18n/locales/ro/messages.js +1 -1
  1059. package/dist/i18n/locales/sk/messages.js +1 -1
  1060. package/dist/i18n/locales/tr/messages.js +1 -1
  1061. package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
  1062. package/dist/legacy/domain-cli/utils/cache.js +1 -1
  1063. package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +16 -0
  1064. package/dist/legacy/domain-spec/spec/domain-spec.js +1 -1
  1065. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
  1066. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
  1067. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
  1068. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +11 -5
  1069. package/dist/legacy/phoenix-cli/services/webhook-service/transport.js +1 -1
  1070. package/dist/legacy/phoenix-cli/services/webhook-service/tunnel.d.ts +1 -0
  1071. package/dist/legacy/phoenix-cli/services/webhook-service/tunnel.js +1 -1
  1072. package/dist/legacy/webhook-cli/lambda-local/index.js +1 -1
  1073. package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
  1074. package/dist/phoenix/application-spec/generate-application-spec-project.d.ts +1 -0
  1075. package/dist/phoenix/application-spec/generate-application-spec-project.js +2 -2
  1076. package/dist/phoenix/application-spec/generate-project-files.d.ts +1 -0
  1077. package/dist/phoenix/application-spec/index.d.ts +2 -0
  1078. package/dist/phoenix/application-spec/index.js +1 -1
  1079. package/dist/phoenix/application-spec/project-files.d.ts +8 -2
  1080. package/dist/phoenix/application-spec/project-files.js +1 -1
  1081. package/dist/phoenix/application-spec/resolve-deployment-data.d.ts +11 -0
  1082. package/dist/phoenix/application-spec/resolve-deployment-data.js +2 -2
  1083. package/dist/phoenix/bin.js +1 -1
  1084. package/dist/phoenix/cli.js +1 -1
  1085. package/dist/phoenix/commands/activate-deployment.js +1 -1
  1086. package/dist/phoenix/commands/build.js +1 -1
  1087. package/dist/phoenix/commands/changelog.js +1 -1
  1088. package/dist/phoenix/commands/check.js +1 -1
  1089. package/dist/phoenix/commands/clean.js +1 -1
  1090. package/dist/phoenix/commands/cleanup-environment.js +2 -2
  1091. package/dist/phoenix/commands/create-environment.js +1 -1
  1092. package/dist/phoenix/commands/data.js +1 -1
  1093. package/dist/phoenix/commands/deploy.js +3 -3
  1094. package/dist/phoenix/commands/deployment-build-envs-command.js +1 -1
  1095. package/dist/phoenix/commands/dev.js +4 -5
  1096. package/dist/phoenix/commands/docs.js +1 -1
  1097. package/dist/phoenix/commands/finish-deployment-command.js +1 -1
  1098. package/dist/phoenix/commands/fix.js +1 -1
  1099. package/dist/phoenix/commands/index.d.ts +1 -1
  1100. package/dist/phoenix/commands/index.js +1 -1
  1101. package/dist/phoenix/commands/inspect-assets.js +1 -1
  1102. package/dist/phoenix/commands/list-environments.js +1 -1
  1103. package/dist/phoenix/commands/login.d.ts +4 -1
  1104. package/dist/phoenix/commands/login.js +2 -2
  1105. package/dist/phoenix/commands/prepare-deployment-command.js +2 -2
  1106. package/dist/phoenix/commands/register-deployment.js +1 -1
  1107. package/dist/phoenix/commands/scene.js +1 -1
  1108. package/dist/phoenix/commands/screenshot.js +1 -1
  1109. package/dist/phoenix/commands/state.js +1 -1
  1110. package/dist/phoenix/commands/story-build.js +3 -3
  1111. package/dist/phoenix/commands/story-create.js +1 -1
  1112. package/dist/phoenix/commands/story-dev.js +2 -2
  1113. package/dist/phoenix/commands/sync.js +1 -1
  1114. package/dist/phoenix/commands/variants.js +1 -1
  1115. package/dist/phoenix/editor-server/commands-router.js +1 -1
  1116. package/dist/phoenix/editor-server/editor-server.d.ts +2 -13
  1117. package/dist/phoenix/editor-server/editor-server.js +1 -1
  1118. package/dist/phoenix/editor-server/spec-router.js +1 -1
  1119. package/dist/phoenix/editor-server/trpc.d.ts +2 -12
  1120. package/dist/phoenix/lingui/index.d.ts +1 -0
  1121. package/dist/phoenix/lingui/index.js +1 -0
  1122. package/dist/phoenix/lingui/lingui.d.ts +18 -0
  1123. package/dist/phoenix/lingui/lingui.js +1 -0
  1124. package/dist/phoenix/scripts/docs-utils.js +1 -1
  1125. package/dist/phoenix/services/application-spec-project-watcher.d.ts +11 -6
  1126. package/dist/phoenix/services/application-spec-project-watcher.js +2 -2
  1127. package/dist/phoenix/services/cli-credentials.d.ts +3 -0
  1128. package/dist/phoenix/services/cli-credentials.js +1 -1
  1129. package/dist/phoenix/services/cli-exit.d.ts +5 -0
  1130. package/dist/phoenix/services/cli-exit.js +1 -0
  1131. package/dist/phoenix/services/deployment/deployment-preparation-service.d.ts +28 -0
  1132. package/dist/phoenix/services/deployment/deployment-preparation-service.js +1 -0
  1133. package/dist/phoenix/{commands → services/deployment}/generated-env-markers.d.ts +1 -0
  1134. package/dist/phoenix/services/deployment/generated-env-markers.js +1 -0
  1135. package/dist/phoenix/services/deployment/index.d.ts +2 -0
  1136. package/dist/phoenix/services/deployment/index.js +1 -0
  1137. package/dist/phoenix/services/index.d.ts +5 -1
  1138. package/dist/phoenix/services/index.js +1 -1
  1139. package/dist/phoenix/services/lingui-watcher.js +1 -1
  1140. package/dist/phoenix/services/phoenix-store.d.ts +3 -0
  1141. package/dist/phoenix/services/phoenix-store.js +1 -1
  1142. package/dist/phoenix/services/prepare-development-environment.js +1 -1
  1143. package/dist/phoenix/services/session/editor-server-handle.d.ts +5 -0
  1144. package/dist/phoenix/services/session/editor-server-handle.js +1 -0
  1145. package/dist/phoenix/services/session/index.d.ts +2 -0
  1146. package/dist/phoenix/services/session/index.js +1 -0
  1147. package/dist/phoenix/services/session/phoenix-application.d.ts +82 -0
  1148. package/dist/phoenix/services/session/phoenix-application.js +2 -0
  1149. package/dist/phoenix/services/source-archive.d.ts +2 -0
  1150. package/dist/phoenix/services/source-archive.js +1 -1
  1151. package/dist/phoenix/services/swc.d.ts +5 -4
  1152. package/dist/phoenix/services/swc.js +2 -2
  1153. package/dist/phoenix/services/telemetry/amplitude-telemetry-transport.d.ts +9 -0
  1154. package/dist/phoenix/services/telemetry/amplitude-telemetry-transport.js +1 -0
  1155. package/dist/phoenix/services/telemetry/index.d.ts +3 -0
  1156. package/dist/phoenix/services/telemetry/index.js +1 -0
  1157. package/dist/phoenix/services/telemetry/telemetry-client.d.ts +25 -0
  1158. package/dist/phoenix/services/telemetry/telemetry-client.js +1 -0
  1159. package/dist/phoenix/services/telemetry/telemetry-command.d.ts +2 -0
  1160. package/dist/phoenix/services/telemetry/telemetry-command.js +1 -0
  1161. package/dist/phoenix/services/telemetry/telemetry-event.d.ts +44 -0
  1162. package/dist/phoenix/services/telemetry/telemetry-event.js +1 -0
  1163. package/dist/phoenix/services/telemetry/telemetry-state.d.ts +25 -0
  1164. package/dist/phoenix/services/telemetry/telemetry-state.js +2 -0
  1165. package/dist/phoenix/services/vite-config.js +1 -1
  1166. package/dist/phoenix/services/webhook-service/transport.js +1 -1
  1167. package/dist/phoenix/services/webhook-service/tunnel.d.ts +1 -0
  1168. package/dist/phoenix/services/webhook-service/tunnel.js +1 -1
  1169. package/dist/react-sdk/application-runtime/common-services/analytics/analytics-service.d.ts +1 -1
  1170. package/dist/react-sdk/application-runtime/common-services/initialize.d.ts +2 -2
  1171. package/dist/react-sdk/application-runtime/domain-application-context/chat-system.d.ts +1 -1
  1172. package/dist/react-sdk/application-runtime/hooks/index.d.ts +2 -0
  1173. package/dist/react-sdk/application-runtime/hooks/index.js +1 -1
  1174. package/dist/react-sdk/application-runtime/hooks/loading/index.d.ts +5 -0
  1175. package/dist/react-sdk/application-runtime/hooks/loading/index.js +1 -0
  1176. package/dist/react-sdk/application-runtime/hooks/loading/loader-host-context.d.ts +11 -0
  1177. package/dist/react-sdk/application-runtime/hooks/loading/loader-host-context.js +1 -0
  1178. package/dist/react-sdk/application-runtime/hooks/loading/use-is-awaiting-loading-phase.d.ts +8 -0
  1179. package/dist/react-sdk/application-runtime/hooks/loading/use-is-awaiting-loading-phase.js +1 -0
  1180. package/dist/react-sdk/application-runtime/hooks/loading/use-is-loader-hosted.d.ts +7 -0
  1181. package/dist/react-sdk/application-runtime/hooks/loading/use-is-loader-hosted.js +1 -0
  1182. package/dist/react-sdk/application-runtime/hooks/loading/use-loading-actions.d.ts +9 -0
  1183. package/dist/react-sdk/application-runtime/hooks/loading/use-loading-actions.js +1 -0
  1184. package/dist/react-sdk/application-runtime/hooks/loading/use-loading-application.d.ts +24 -0
  1185. package/dist/react-sdk/application-runtime/hooks/loading/use-loading-application.js +1 -0
  1186. package/dist/react-sdk/application-runtime/hooks/responsive/index.d.ts +3 -0
  1187. package/dist/react-sdk/application-runtime/hooks/responsive/index.js +1 -0
  1188. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.d.ts +9 -0
  1189. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.js +1 -0
  1190. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.d.ts +26 -0
  1191. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.js +1 -0
  1192. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.d.ts +10 -0
  1193. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.js +1 -0
  1194. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.d.ts +24 -0
  1195. package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.js +1 -0
  1196. package/dist/react-sdk/application-runtime/hooks/use-access-service.d.ts +5 -0
  1197. package/dist/react-sdk/application-runtime/hooks/use-access-service.js +1 -1
  1198. package/dist/react-sdk/application-runtime/hooks/use-phoenix-element-slots.d.ts +2 -1
  1199. package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.d.ts +4 -11
  1200. package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.js +1 -1
  1201. package/dist/react-sdk/application-runtime/hooks/use-web-component.d.ts +5 -0
  1202. package/dist/react-sdk/application-runtime/hooks/use-web-component.js +1 -1
  1203. package/dist/react-sdk/application-runtime/host-properties/actions.d.ts +15 -0
  1204. package/dist/react-sdk/application-runtime/host-properties/actions.js +1 -0
  1205. package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.d.ts +30 -0
  1206. package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.js +1 -0
  1207. package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.d.ts +7 -0
  1208. package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.js +1 -0
  1209. package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.d.ts +21 -0
  1210. package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.js +1 -0
  1211. package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.d.ts +42 -0
  1212. package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.js +1 -0
  1213. package/dist/react-sdk/application-runtime/host-properties/index.d.ts +8 -0
  1214. package/dist/react-sdk/application-runtime/host-properties/index.js +1 -0
  1215. package/dist/react-sdk/application-runtime/host-properties/initialize.d.ts +7 -0
  1216. package/dist/react-sdk/application-runtime/host-properties/initialize.js +1 -0
  1217. package/dist/react-sdk/application-runtime/host-properties/selectors.d.ts +54 -0
  1218. package/dist/react-sdk/application-runtime/host-properties/selectors.js +1 -0
  1219. package/dist/react-sdk/application-runtime/host-properties/types.d.ts +18 -0
  1220. package/dist/react-sdk/application-runtime/host-properties/types.js +1 -0
  1221. package/dist/react-sdk/application-runtime/index.d.ts +3 -0
  1222. package/dist/react-sdk/application-runtime/integration/initialize.d.ts +7 -6
  1223. package/dist/react-sdk/application-runtime/integration/initialize.js +1 -1
  1224. package/dist/react-sdk/application-runtime/integration/selectors.d.ts +11 -2
  1225. package/dist/react-sdk/application-runtime/integration/selectors.js +1 -1
  1226. package/dist/react-sdk/application-runtime/integration/types.d.ts +5 -2
  1227. package/dist/react-sdk/application-runtime/loading/actions.d.ts +45 -0
  1228. package/dist/react-sdk/application-runtime/loading/actions.js +1 -0
  1229. package/dist/react-sdk/application-runtime/loading/index.d.ts +4 -0
  1230. package/dist/react-sdk/application-runtime/loading/index.js +1 -0
  1231. package/dist/react-sdk/application-runtime/loading/initialize.d.ts +3 -0
  1232. package/dist/react-sdk/application-runtime/loading/initialize.js +1 -0
  1233. package/dist/react-sdk/application-runtime/loading/selectors.d.ts +18 -0
  1234. package/dist/react-sdk/application-runtime/loading/selectors.js +1 -0
  1235. package/dist/react-sdk/application-runtime/loading/types.d.ts +59 -0
  1236. package/dist/react-sdk/application-runtime/loading/types.js +1 -0
  1237. package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.js +1 -1
  1238. package/dist/react-sdk/application-runtime/provider/ApplicationRuntime.d.ts +6 -0
  1239. package/dist/react-sdk/application-runtime/provider/ApplicationRuntime.js +1 -1
  1240. package/dist/react-sdk/application-runtime/provider/ApplicationType.d.ts +7 -0
  1241. package/dist/react-sdk/application-runtime/provider/ApplicationType.js +1 -1
  1242. package/dist/react-sdk/application-runtime/responsive/actions.d.ts +47 -0
  1243. package/dist/react-sdk/application-runtime/responsive/actions.js +1 -0
  1244. package/dist/react-sdk/application-runtime/responsive/index.d.ts +4 -0
  1245. package/dist/react-sdk/application-runtime/responsive/index.js +1 -0
  1246. package/dist/react-sdk/application-runtime/responsive/initialize.d.ts +3 -0
  1247. package/dist/react-sdk/application-runtime/responsive/initialize.js +1 -0
  1248. package/dist/react-sdk/application-runtime/responsive/selectors.d.ts +20 -0
  1249. package/dist/react-sdk/application-runtime/responsive/selectors.js +1 -0
  1250. package/dist/react-sdk/application-runtime/responsive/types.d.ts +104 -0
  1251. package/dist/react-sdk/application-runtime/responsive/types.js +1 -0
  1252. package/dist/react-sdk/application-runtime/routing/routing-service.d.ts +34 -2
  1253. package/dist/react-sdk/application-runtime/routing/routing-service.js +1 -1
  1254. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +313 -10
  1255. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.js +1 -1
  1256. package/dist/{configurator/configurator-context → react-sdk}/configurator-routing/ConfiguratorRoutingProvider.d.ts +1 -1
  1257. package/dist/react-sdk/configurator-routing/ConfiguratorRoutingProvider.js +1 -0
  1258. package/dist/{configurator/configurator-context → react-sdk}/configurator-routing/OnGoToIndexHandler.d.ts +1 -2
  1259. package/dist/react-sdk/configurator-routing/OnGoToIndexHandler.js +1 -0
  1260. package/dist/react-sdk/configurator-routing/index.d.ts +3 -0
  1261. package/dist/{configurator/configurator-context → react-sdk}/configurator-routing/index.js +1 -1
  1262. package/dist/{configurator/configurator-context → react-sdk}/configurator-routing/use-power-routing-lifecycle.d.ts +2 -0
  1263. package/dist/react-sdk/configurator-routing/use-power-routing-lifecycle.js +1 -0
  1264. package/dist/react-sdk/configurator-ui-elements/ConfiguratorUiElementHost.d.ts +7 -0
  1265. package/dist/react-sdk/configurator-ui-elements/ConfiguratorUiElementHost.js +1 -0
  1266. package/dist/react-sdk/configurator-ui-elements/ErrorFallbackElement.d.ts +14 -0
  1267. package/dist/react-sdk/configurator-ui-elements/ErrorFallbackElement.js +1 -0
  1268. package/dist/react-sdk/configurator-ui-elements/ErrorFallbackPropsProvider.d.ts +18 -0
  1269. package/dist/react-sdk/configurator-ui-elements/ErrorFallbackPropsProvider.js +1 -0
  1270. package/dist/react-sdk/configurator-ui-elements/PlainErrorFallback.d.ts +5 -0
  1271. package/dist/react-sdk/configurator-ui-elements/PlainErrorFallback.js +1 -0
  1272. package/dist/react-sdk/configurator-ui-elements/configurator-ui-element.d.ts +17 -0
  1273. package/dist/react-sdk/configurator-ui-elements/configurator-ui-element.js +1 -0
  1274. package/dist/react-sdk/configurator-ui-elements/create-configurator-ui-element.d.ts +5 -0
  1275. package/dist/react-sdk/configurator-ui-elements/create-configurator-ui-element.js +1 -0
  1276. package/dist/react-sdk/configurator-ui-elements/index.d.ts +6 -0
  1277. package/dist/react-sdk/configurator-ui-elements/index.js +1 -0
  1278. package/dist/react-sdk/domain-application-context/BackendDataProvider.js +1 -1
  1279. package/dist/react-sdk/domain-application-context/ConfigurationPersistSystemFailureHandler.js +1 -1
  1280. package/dist/react-sdk/domain-application-context/FrontendDataProvider.js +1 -1
  1281. package/dist/react-sdk/domain-application-context/OnPresetLoadedHandler.js +1 -0
  1282. package/dist/react-sdk/domain-application-context/PublishedDataProvider.js +1 -1
  1283. package/dist/react-sdk/domain-application-context/chat-hooks.d.ts +4 -0
  1284. package/dist/react-sdk/domain-application-context/chat-hooks.js +1 -0
  1285. package/dist/react-sdk/domain-application-context/index.d.ts +2 -0
  1286. package/dist/react-sdk/domain-application-context/index.js +1 -1
  1287. package/dist/react-sdk/domain-context/common-hooks/index.d.ts +1 -0
  1288. package/dist/react-sdk/domain-context/common-hooks/index.js +1 -1
  1289. package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.d.ts +23 -0
  1290. package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.js +1 -0
  1291. package/dist/react-sdk/domain-context/common-hooks/use-domain-context.d.ts +1 -0
  1292. package/dist/react-sdk/domain-context/common-hooks/use-state-validity.d.ts +7 -1
  1293. package/dist/react-sdk/domain-context/index.d.ts +1 -0
  1294. package/dist/react-sdk/domain-context/index.js +1 -1
  1295. package/dist/react-sdk/domain-controls/ChildEntityCardSelector.d.ts +2 -3
  1296. package/dist/react-sdk/domain-controls/ChildEntityListSelector.d.ts +2 -3
  1297. package/dist/react-sdk/domain-controls/ChildEntityToggleSelector.d.ts +2 -3
  1298. package/dist/react-sdk/domain-controls/hooks/use-cook-replace-recipes.d.ts +2 -2
  1299. package/dist/react-sdk/elements/PhoenixEntityElement.js +1 -1
  1300. package/dist/react-sdk/elements/PhoenixLayoutElement.js +1 -1
  1301. package/dist/react-sdk/elements/index.d.ts +0 -1
  1302. package/dist/react-sdk/index.d.ts +8 -1
  1303. package/dist/react-sdk/index.js +1 -1
  1304. package/dist/react-sdk/integration/IntegrationSubscription.js +1 -0
  1305. package/dist/react-sdk/integration/index.d.ts +1 -0
  1306. package/dist/react-sdk/integration/index.js +1 -0
  1307. package/dist/react-sdk/lifecycle-providers/ConfiguratorDataProvider.d.ts +8 -0
  1308. package/dist/react-sdk/lifecycle-providers/ConfiguratorDataProvider.js +1 -0
  1309. package/dist/react-sdk/lifecycle-providers/LifecycleProviders.js +1 -0
  1310. package/dist/react-sdk/lifecycle-providers/index.d.ts +2 -0
  1311. package/dist/react-sdk/lifecycle-providers/index.js +1 -0
  1312. package/dist/react-sdk/loading-state/LoaderHost.d.ts +20 -0
  1313. package/dist/react-sdk/loading-state/LoaderHost.js +1 -0
  1314. package/dist/react-sdk/loading-state/built-in-loading-phase.d.ts +9 -0
  1315. package/dist/react-sdk/loading-state/built-in-loading-phase.js +1 -0
  1316. package/dist/react-sdk/loading-state/create-app-loader.d.ts +18 -0
  1317. package/dist/react-sdk/loading-state/create-app-loader.js +1 -0
  1318. package/dist/react-sdk/loading-state/index.d.ts +4 -2
  1319. package/dist/react-sdk/loading-state/index.js +1 -1
  1320. package/dist/react-sdk/loading-state/{LoadingStateProvider.d.ts → loading-state-hooks.d.ts} +3 -12
  1321. package/dist/react-sdk/loading-state/loading-state-hooks.js +1 -0
  1322. package/dist/react-sdk/notifications/index.d.ts +1 -0
  1323. package/dist/react-sdk/notifications/index.js +1 -0
  1324. package/dist/react-sdk/notifications/snackbar-action-button.d.ts +30 -0
  1325. package/dist/react-sdk/notifications/snackbar-action-button.js +1 -0
  1326. package/dist/react-sdk/page-elements/ConfigurationNotFoundPagePropsProvider.d.ts +17 -0
  1327. package/dist/react-sdk/page-elements/ConfigurationNotFoundPagePropsProvider.js +1 -0
  1328. package/dist/react-sdk/page-elements/ConfiguratorErrorPagePropsProvider.d.ts +17 -0
  1329. package/dist/react-sdk/page-elements/ConfiguratorErrorPagePropsProvider.js +1 -0
  1330. package/dist/react-sdk/page-elements/configurator-error-page-props.js +1 -0
  1331. package/dist/{configurator/configuration-not-found-page → react-sdk/page-elements}/create-configuration-not-found-page.d.ts +7 -1
  1332. package/dist/react-sdk/page-elements/create-configuration-not-found-page.js +1 -0
  1333. package/dist/{configurator/configurator-error-page → react-sdk/page-elements}/create-configurator-error-page.d.ts +6 -1
  1334. package/dist/react-sdk/page-elements/create-configurator-error-page.js +1 -0
  1335. package/dist/react-sdk/page-elements/create-preset-ui.d.ts +9 -0
  1336. package/dist/react-sdk/page-elements/create-preset-ui.js +1 -0
  1337. package/dist/react-sdk/page-elements/index.d.ts +6 -0
  1338. package/dist/react-sdk/page-elements/index.js +1 -0
  1339. package/dist/react-sdk/preset-selection/PresetSelectionProvider.js +1 -0
  1340. package/dist/react-sdk/preset-selection/index.d.ts +1 -0
  1341. package/dist/react-sdk/preset-selection/index.js +1 -0
  1342. package/dist/react-sdk/provider-slots/ConfiguratorProviderSlotHost.d.ts +11 -0
  1343. package/dist/react-sdk/provider-slots/ConfiguratorProviderSlotHost.js +1 -0
  1344. package/dist/react-sdk/provider-slots/configurator-provider-slot.d.ts +24 -0
  1345. package/dist/react-sdk/provider-slots/configurator-provider-slot.js +1 -0
  1346. package/dist/react-sdk/provider-slots/create-configurator-provider-slot.d.ts +5 -0
  1347. package/dist/react-sdk/provider-slots/create-configurator-provider-slot.js +1 -0
  1348. package/dist/react-sdk/provider-slots/index.d.ts +3 -0
  1349. package/dist/react-sdk/provider-slots/index.js +1 -0
  1350. package/dist/react-sdk/session/SessionServiceProvider.d.ts +5 -0
  1351. package/dist/react-sdk/session/SessionServiceProvider.js +1 -1
  1352. package/dist/react-sdk/web-component/create-web-component.js +1 -1
  1353. package/dist/react-sdk/web-component/god-mode/god-mode-registry.d.ts +30 -0
  1354. package/dist/react-sdk/web-component/god-mode/god-mode-registry.js +1 -0
  1355. package/dist/react-sdk/web-component/god-mode/index.d.ts +1 -0
  1356. package/dist/react-sdk/web-component/god-mode/index.js +1 -0
  1357. package/dist/ui-sdk/components/numerical-input/InputField.d.ts +2 -0
  1358. package/dist/ui-sdk/components/numerical-input/InputField.js +1 -1
  1359. package/dist/ui-sdk/components/numerical-input/NumericalInput.d.ts +2 -2
  1360. package/dist/ui-sdk/components/numerical-input/Slider.d.ts +3 -1
  1361. package/dist/ui-sdk/components/numerical-input/Slider.js +1 -1
  1362. package/dist/ui-sdk/components/snackbar/Snackbar.d.ts +24 -3
  1363. package/dist/ui-sdk/components/snackbar/Snackbar.js +1 -1
  1364. package/dist/ui-sdk/icons/__generated/ChatCircleText.d.ts +3 -0
  1365. package/dist/ui-sdk/icons/__generated/ChatCircleText.js +1 -0
  1366. package/dist/ui-sdk/icons/__generated/Paperclip.d.ts +3 -0
  1367. package/dist/ui-sdk/icons/__generated/Paperclip.js +1 -0
  1368. package/dist/ui-sdk/icons/__generated/PencilSimpleLine.d.ts +3 -0
  1369. package/dist/ui-sdk/icons/__generated/PencilSimpleLine.js +1 -0
  1370. package/dist/ui-sdk/icons/__generated/index.d.ts +6 -0
  1371. package/dist/ui-sdk/icons/__generated/index.js +1 -1
  1372. package/dist/ui-sdk/icons/icons-registry.js +1 -1
  1373. package/dist/ui-sdk/responsive-application/ResponsiveApplicationProvider.d.ts +2 -2
  1374. package/dist/ui-sdk/responsive-application/responsive-state-store.d.ts +11 -9
  1375. package/dist/ui-sdk/responsive-application/use-store-responsive-info.js +1 -1
  1376. package/dist/ui-sdk/theme/types.d.ts +5 -1
  1377. package/dist/watch/glob-matcher.d.ts +4 -4
  1378. package/dist/watch/glob-matcher.js +1 -1
  1379. package/dist/watch/watch.d.ts +4 -4
  1380. package/dist/watch/watch.js +1 -1
  1381. package/package.json +13 -3
  1382. package/schema/domain-spec.json +158 -0
  1383. package/dist/application-spec/caching/runtime-reference-store.js +0 -1
  1384. package/dist/application-spec/core/components/index.d.ts +0 -2
  1385. package/dist/application-spec/core/expressions/index.d.ts +0 -2
  1386. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.d.ts +0 -1
  1387. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +0 -1
  1388. package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.d.ts +0 -3
  1389. package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.js +0 -166
  1390. package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.d.ts +0 -1
  1391. package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.js +0 -54
  1392. package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.d.ts +0 -1
  1393. package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.js +0 -115
  1394. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +0 -1
  1395. package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.d.ts +0 -1
  1396. package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.js +0 -6
  1397. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.js +0 -1
  1398. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.d.ts +0 -28
  1399. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.js +0 -1
  1400. package/dist/application-spec/generate-application/runtime/application-runtime.js +0 -1
  1401. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.js +0 -1
  1402. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +0 -37
  1403. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +0 -1
  1404. package/dist/application-spec/generate-application/runtime/generate-runtime-instance.js +0 -1
  1405. package/dist/application-spec/generate-application/runtime/migrations/migration-runtime.js +0 -1
  1406. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.d.ts +0 -8
  1407. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.js +0 -1
  1408. package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.d.ts +0 -8
  1409. package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.js +0 -1
  1410. package/dist/application-spec/generate-application/runtime/sections/expression-runtime.js +0 -1
  1411. package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.js +0 -1
  1412. package/dist/application-spec/generate-application/runtime/sections/index.d.ts +0 -10
  1413. package/dist/application-spec/generate-application/runtime/sections/index.js +0 -1
  1414. package/dist/application-spec/generate-application/runtime-spec/index.d.ts +0 -5
  1415. package/dist/application-spec/generate-application/runtime-spec/index.js +0 -1
  1416. package/dist/application-spec/generate-application/runtime-spec/runtime-spec.d.ts +0 -68
  1417. package/dist/application-spec/generate-application/runtime-spec/runtime-spec.js +0 -1
  1418. package/dist/configurator/app-loader-content/app-loader-content-name.d.ts +0 -5
  1419. package/dist/configurator/app-loader-content/app-loader-content-name.js +0 -1
  1420. package/dist/configurator/application-runtime/extensions/systems/assistant-system/browser-details.d.ts +0 -17
  1421. package/dist/configurator/application-runtime/extensions/systems/assistant-system/browser-details.js +0 -1
  1422. package/dist/configurator/configuration-not-found-page/create-configuration-not-found-page.js +0 -1
  1423. package/dist/configurator/configurator-context/ConfiguratorDataProvider.d.ts +0 -2
  1424. package/dist/configurator/configurator-context/ConfiguratorDataProvider.js +0 -1
  1425. package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.js +0 -1
  1426. package/dist/configurator/configurator-context/IntegrationSubscription.js +0 -1
  1427. package/dist/configurator/configurator-context/LifecycleProviders.js +0 -1
  1428. package/dist/configurator/configurator-context/OnPresetLoadedHandler.js +0 -1
  1429. package/dist/configurator/configurator-context/PresetSelectionProvider.js +0 -1
  1430. package/dist/configurator/configurator-context/configurator-hooks/chat-hooks.d.ts +0 -1
  1431. package/dist/configurator/configurator-context/configurator-hooks/chat-hooks.js +0 -1
  1432. package/dist/configurator/configurator-context/configurator-routing/ConfiguratorRoutingProvider.js +0 -1
  1433. package/dist/configurator/configurator-context/configurator-routing/OnGoToIndexHandler.js +0 -1
  1434. package/dist/configurator/configurator-context/configurator-routing/index.d.ts +0 -2
  1435. package/dist/configurator/configurator-context/configurator-routing/use-power-routing-lifecycle.js +0 -1
  1436. package/dist/configurator/configurator-error-page/create-configurator-error-page.js +0 -1
  1437. package/dist/configurator/ui/assistant/form/built-in-form-registry.d.ts +0 -10
  1438. package/dist/configurator/ui/assistant/form/built-in-form-registry.js +0 -1
  1439. package/dist/configurator/ui/assistant/form/form-registry.js +0 -8
  1440. package/dist/domain/application/assistant/form/assistant-form-elements.js +0 -1
  1441. package/dist/domain/application/assistant/form/get-assistant-form-value-attribute.d.ts +0 -6
  1442. package/dist/domain/application/assistant/form/get-assistant-form-value-attribute.js +0 -1
  1443. package/dist/domain/application/assistant/form/index.js +0 -1
  1444. package/dist/domain/available-system-components.d.ts +0 -11
  1445. package/dist/domain/available-system-components.js +0 -1
  1446. package/dist/domain/domain-builder.js +0 -1
  1447. package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +0 -1
  1448. package/dist/domain/domain-runtime/spec/module-checks/index.js +0 -1
  1449. package/dist/domain/domain-runtime/spec/module-checks/project-modules.js +0 -1
  1450. package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.js +0 -1
  1451. package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.js +0 -1
  1452. package/dist/domain/extensions/client-side-image.js +0 -1
  1453. package/dist/domain/extensions/save-template-thumbnail.js +0 -1
  1454. package/dist/phoenix/commands/generated-env-markers.js +0 -1
  1455. package/dist/phoenix/services/lingui.d.ts +0 -15
  1456. package/dist/phoenix/services/lingui.js +0 -1
  1457. package/dist/phoenix/services/phoenix-application.d.ts +0 -21
  1458. package/dist/phoenix/services/phoenix-application.js +0 -1
  1459. package/dist/react-sdk/application-runtime/runtime/god-mode.d.ts +0 -19
  1460. package/dist/react-sdk/application-runtime/runtime/god-mode.js +0 -1
  1461. package/dist/react-sdk/loading-state/LoadingStateProvider.js +0 -1
  1462. package/dist/react-sdk/loading-state/loading-state-manager.d.ts +0 -37
  1463. package/dist/react-sdk/loading-state/loading-state-manager.js +0 -1
  1464. package/dist/react-sdk/responsive-state/ResponsiveStateProvider.d.ts +0 -25
  1465. package/dist/react-sdk/responsive-state/ResponsiveStateProvider.js +0 -1
  1466. package/dist/react-sdk/responsive-state/canvas-padding.d.ts +0 -14
  1467. package/dist/react-sdk/responsive-state/index.d.ts +0 -5
  1468. package/dist/react-sdk/responsive-state/index.js +0 -1
  1469. package/dist/react-sdk/responsive-state/responsive-state-manager.d.ts +0 -111
  1470. package/dist/react-sdk/responsive-state/responsive-state-manager.js +0 -1
  1471. package/dist/react-sdk/responsive-state/use-responsive-state-manager.d.ts +0 -10
  1472. package/dist/react-sdk/responsive-state/use-responsive-state-manager.js +0 -1
  1473. package/dist/react-sdk/responsive-state/use-responsive-state.d.ts +0 -11
  1474. package/dist/react-sdk/responsive-state/use-responsive-state.js +0 -1
  1475. /package/dist/{application-spec/core/expressions/relation-expression-params.js → configurator/application-runtime/extensions/systems/assistant-system/assistant-annotation.js} +0 -0
  1476. /package/dist/{application-spec/generate-application/runtime/spec-code/spec-code-module.js → domain/spec/application/admin-pages-section.js} +0 -0
  1477. /package/dist/{application-spec/parse-application-files/spec/components/component-entry.js → domain/spec/application/admin-pages.js} +0 -0
  1478. /package/dist/{application-spec/parse-application-files/spec/expressions/expression.js → domain/spec/application/admin-states.js} +0 -0
  1479. /package/dist/{application-spec/core → domain/spec/application}/application-instance.d.ts +0 -0
  1480. /package/dist/{application-spec/core → domain/spec/application}/application-instance.js +0 -0
  1481. /package/dist/{configurator/ui/assistant/form/form-element-definition.js → domain/spec/application/configurator-states.js} +0 -0
  1482. /package/dist/domain/{application/assistant/assistant-manifest.js → spec/components/bindings/index.js} +0 -0
  1483. /package/dist/{react-sdk/elements/configurator-error-page-props.js → domain/spec/components/bindings/read-binding.js} +0 -0
  1484. /package/dist/{react-sdk/responsive-state/canvas-padding.js → domain/spec/components/bindings/write-binding.js} +0 -0
  1485. /package/dist/{application-spec/core → domain/spec}/components/component-filter.js +0 -0
  1486. /package/dist/{application-spec/core → domain/spec}/components/index.js +0 -0
  1487. /package/dist/{application-spec/core → domain/spec}/components/sdk-code-ref.d.ts +0 -0
  1488. /package/dist/{application-spec/core → domain/spec}/components/sdk-code-ref.js +0 -0
  1489. /package/dist/domain/{domain-runtime → spec}/errors/implementation-error.d.ts +0 -0
  1490. /package/dist/domain/{domain-runtime → spec}/errors/implementation-error.js +0 -0
  1491. /package/dist/domain/{domain-runtime → spec}/errors/implementation-errors.d.ts +0 -0
  1492. /package/dist/domain/{domain-runtime → spec}/errors/implementation-errors.js +0 -0
  1493. /package/dist/domain/{domain-runtime → spec}/errors/index.d.ts +0 -0
  1494. /package/dist/domain/{domain-runtime → spec}/errors/index.js +0 -0
  1495. /package/dist/domain/{domain-runtime → spec}/errors/to-implementation-errors.d.ts +0 -0
  1496. /package/dist/domain/{domain-runtime → spec}/errors/to-implementation-errors.js +0 -0
  1497. /package/dist/{application-spec/core → domain/spec}/expressions/index.js +0 -0
  1498. /package/dist/{application-spec/core → domain/spec}/expressions/relation-expression-params.d.ts +0 -0
  1499. /package/dist/{application-spec/core → domain/spec}/expressions/sdk-expression-implementation.d.ts +0 -0
  1500. /package/dist/{application-spec/core → domain/spec}/expressions/sdk-expression-implementation.js +0 -0
  1501. /package/dist/{application-spec/parse-application-files/spec/components → domain/spec/markers}/component-prop-target.d.ts +0 -0
  1502. /package/dist/{application-spec/parse-application-files/spec/components → domain/spec/markers}/component-prop-target.js +0 -0
  1503. /package/dist/{application-spec/core → domain/spec}/markers/eval-marker.d.ts +0 -0
  1504. /package/dist/{application-spec/core → domain/spec}/markers/eval-marker.js +0 -0
  1505. /package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/handler-registration.d.ts +0 -0
  1506. /package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/handler-registration.js +0 -0
  1507. /package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-action-modules.js +0 -0
  1508. /package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-component-optic-modules.js +0 -0
  1509. /package/dist/domain/{domain-runtime/spec/module-checks → spec/modules}/resolve-invariant-modules.js +0 -0
  1510. /package/dist/{application-spec/generate-application → domain/spec}/runtime/admin-runtime/states-runtime.js +0 -0
  1511. /package/dist/{application-spec/generate-application → domain/spec}/runtime/configurator-runtime/states-runtime.js +0 -0
  1512. /package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/expressions}/reflection-constants-runtime.js +0 -0
  1513. /package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/factory-data-runtime.js +0 -0
  1514. /package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/index.d.ts +0 -0
  1515. /package/dist/{application-spec/generate-application → domain/spec}/runtime/factory-data/index.js +0 -0
  1516. /package/dist/{application-spec/generate-application → domain/spec}/runtime/migrations/index.d.ts +0 -0
  1517. /package/dist/{application-spec/generate-application → domain/spec}/runtime/migrations/index.js +0 -0
  1518. /package/dist/{application-spec/generate-application/runtime/sections → domain/spec/runtime/optics}/component-optic.js +0 -0
  1519. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/actions-runtime.js +0 -0
  1520. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/assistant-runtime.js +0 -0
  1521. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/data-runtime.js +0 -0
  1522. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/preset-runtime.js +0 -0
  1523. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/theme-runtime.js +0 -0
  1524. /package/dist/{application-spec/generate-application → domain/spec}/runtime/sections/transformers-runtime.js +0 -0
  1525. /package/dist/{application-spec/generate-application → domain/spec}/runtime/spec-code/index.d.ts +0 -0
  1526. /package/dist/{application-spec/generate-application → domain/spec}/runtime/spec-code/index.js +0 -0
  1527. /package/dist/{application-spec/generate-application → domain/spec}/runtime/spec-code/resolve-spec-code.js +0 -0
  1528. /package/dist/{application-spec/generate-application → domain/spec}/runtime/spec-code/spec-code-module.d.ts +0 -0
  1529. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/decode-runtime-spec.d.ts +0 -0
  1530. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/decode-runtime-spec.js +0 -0
  1531. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/encode-runtime-spec.d.ts +0 -0
  1532. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/encode-runtime-spec.js +0 -0
  1533. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/encoded-runtime-spec.js +0 -0
  1534. /package/dist/{application-spec/generate-application → domain/spec}/runtime-spec/spec-code-id.js +0 -0
  1535. /package/dist/domain/{application/assistant/assistant-resolved-manifest.js → spec/sections/assistant/assistant-manifest.js} +0 -0
  1536. /package/dist/domain/{application → spec/sections}/assistant/form/assistant-resource-path.d.ts +0 -0
  1537. /package/dist/domain/{application → spec/sections}/assistant/form/assistant-resource-path.js +0 -0
  1538. /package/dist/{configurator/configurator-context → react-sdk/domain-application-context}/OnPresetLoadedHandler.d.ts +0 -0
  1539. /package/dist/{configurator/configurator-context → react-sdk/integration}/IntegrationSubscription.d.ts +0 -0
  1540. /package/dist/{configurator/configurator-context → react-sdk/lifecycle-providers}/LifecycleProviders.d.ts +0 -0
  1541. /package/dist/react-sdk/{elements → page-elements}/configurator-error-page-props.d.ts +0 -0
  1542. /package/dist/{configurator/configurator-context → react-sdk/preset-selection}/PresetSelectionProvider.d.ts +0 -0
@@ -0,0 +1,3665 @@
1
+ # Nightjar Guitar
2
+
3
+ Walk a customer through a product one step at a time and end in an order. A custom guitar shop, Northfret, sells its Nightjar electric guitar through a five-step wizard: the body, the neck, the electronics, a signature on the headstock and a summary with **Place order**. A stepper across the top shows where the customer is, each step's choices slide in beside the guitar, the camera moves to what the step changes, and a bar at the bottom keeps the price, **Back** and **Next** in reach. The project writes the wizard as one layout element; the spec holds the steps, which step asks each choice, the cameras, the rules and the order action.
4
+
5
+ - [Try it](#try-it)
6
+ - [What the example demonstrates](#what-the-example-demonstrates)
7
+ - [Steps from the spec](#steps-from-the-spec)
8
+ - [Each step shows the choices the spec gives it](#each-step-shows-the-choices-the-spec-gives-it)
9
+ - [The camera follows the wizard](#the-camera-follows-the-wizard)
10
+ - [Finish swatches](#finish-swatches)
11
+ - [A signature on the headstock](#a-signature-on-the-headstock)
12
+ - [Summary and conflicts](#summary-and-conflicts)
13
+ - [Place the order](#place-the-order)
14
+ - [Pages that slide in](#pages-that-slide-in)
15
+ - [On a phone](#on-a-phone)
16
+ - [The stage and the model](#the-stage-and-the-model)
17
+ - [Test cases](#test-cases)
18
+ - [What this layout leaves out](#what-this-layout-leaves-out)
19
+ - [Related pages](#related-pages)
20
+ - [Complete Implementation](#complete-implementation)
21
+
22
+ ## Try it
23
+
24
+ The phone version is under [On a phone](#on-a-phone).
25
+
26
+ ## What the example demonstrates
27
+
28
+ [Custom Layout](?path=/docs/layouts-custom-layout--docs) explains how a project layout works: the element name, the scene, the safe area and the hooks. This page is a finished design built the same way, for a product sold in ordered steps, and it shows the pieces Custom Layout does not use:
29
+
30
+ | Piece | Where |
31
+ | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
32
+ | `phx.frontend.useStepNavigation()` for a stepper and **Back** and **Next** from the spec's `navigationSteps` | `StepHeader`, `OrderBar`, [Steps from the spec](#steps-from-the-spec) |
33
+ | A step's fields from the components a `$filter` leaves active, read with `phx.frontend.useComponents()` | `use-active-choices`, [Each step shows…](#each-step-shows-the-choices-the-spec-gives-it) |
34
+ | Cameras filtered by navigation state, so the camera follows the wizard with no camera code | the spec, [The camera follows the wizard](#the-camera-follows-the-wizard) |
35
+ | `phx.frontend.useSwatchSelection()` for a `materialSwatch` | `FinishField`, [Finish swatches](#finish-swatches) |
36
+ | `phx.frontend.useTextValue()` with a `sceneEntityObjectTextDecal`, and a `fixable` rule on the text | `SignatureField`, [A signature on the headstock](#a-signature-on-the-headstock) |
37
+ | A summary read with the control hooks, and warnings from `phx.frontend.useFinalizeViolations()` | `SummaryPanel`, `SummaryRows`, [Summary and conflicts](#summary-and-conflicts) |
38
+ | `phx.frontend.useFinalizeActions()` with its pending state and error, and an `ordered` confirmation | `OrderBar`, `SummaryPanel`, [Place the order](#place-the-order) |
39
+ | `phx.frontend.useSceneSafeArea().update()` once a page has slid in | `DesktopPage`, `PhonePage`, [Pages that slide in](#pages-that-slide-in) |
40
+ | `phx.frontend.useAnalytics()` with `FrontendAnalyticsFactory.SummaryButtonClicked()` | `OrderBar`, [Steps from the spec](#steps-from-the-spec) |
41
+ | `sceneEntityObjectLocalMaterialOverride` with a clearcoat, and a `canvasCustomEnvironment` stage | the spec, `NightjarStage`, [The stage and the model](#the-stage-and-the-model) |
42
+
43
+ ## Steps from the spec
44
+
45
+ The steps are product structure, so they live in the spec. Each navigation state is a page of the wizard; `navigationSteps` groups them into the steps the stepper shows, with their labels and their order. The summary step holds two states: `summary`, and `ordered` for the confirmation after the order.
46
+
47
+ ```yaml
48
+ navigationStates:
49
+ - body
50
+ - neck
51
+ - electronics
52
+ - details
53
+ - summary
54
+ - ordered
55
+ ```
56
+
57
+ ```yaml
58
+ components:
59
+ Guitar:
60
+ navigationSteps:
61
+ main:
62
+ steps:
63
+ - label: Body
64
+ navigationStates:
65
+ - body
66
+ - label: Neck
67
+ navigationStates:
68
+ - neck
69
+ - label: Electronics
70
+ navigationStates:
71
+ - electronics
72
+ - label: Details
73
+ navigationStates:
74
+ - details
75
+ - label: Summary
76
+ navigationStates:
77
+ - summary
78
+ - ordered
79
+ ```
80
+
81
+ `phx.frontend.useStepNavigation()` reads them and returns every step with its label resolved and its `status`, `'complete'`, `'active'` or `'upcoming'`, with `next()`, `previous()` and `goTo(index)` to move. `StepHeader` draws the stepper from `steps`, and `OrderBar` the buttons from `canGoPrevious`, `isLast` and the label of the step after the active one:
82
+
83
+ - **A wizard that remembers.** The hook enforces no order: `goTo` enters any step. `StepHeader` lets the customer go back to any step they have reached and never ahead of it, so after changing the body from the summary they return to the summary in one click. The furthest step is the layout's own state, kept in `ConfiguratorLayout` with `useReachedStep()` so it survives the switch between the desktop and the phone page.
84
+ - **Analytics come with the moves.** `next()` and `previous()` record the built-in step buttons' events and `goTo()` the header stepper's, so the stepper and the bar need no analytics code. The button that opens the summary is the exception: like the built-in **Summary** button, it records `FrontendAnalyticsFactory.SummaryButtonClicked()` from `@salsita-ai/sdk/configurator` through `phx.frontend.useAnalytics().push()` and enters the state with `phx.frontend.useNavigate()`, because `next()` would record the next-step button's event instead.
85
+ - **Locked once ordered.** The stepper takes no clicks while the order is being placed or after it was placed; **Design another** in the bar returns to the first step.
86
+
87
+ ## Each step shows the choices the spec gives it
88
+
89
+ The layout does not list which choice belongs to which step. A `$filter` with `navigationStates` makes a spec component active only in those states, so each choice names its step in the spec, plus the summary and the confirmation, which list every choice:
90
+
91
+ ```yaml
92
+ components:
93
+ Guitar:
94
+ attributeSelection:
95
+ bodyShape:
96
+ value:
97
+ $bind: bodyShape
98
+ options:
99
+ - label: Offset
100
+ description: Contoured waist, 3.4 kg
101
+ value: Offset
102
+ - label: Single-cut
103
+ description: Thicker body, long sustain
104
+ value: SingleCut
105
+ $filter:
106
+ navigationStates:
107
+ - body
108
+ - summary
109
+ - ordered
110
+ ```
111
+
112
+ `useActiveChoices` reads the `attributeSelection`, `materialSwatch` and `textValue` components active now with `phx.frontend.useComponents()` and orders them by the layout's field list, which also names each choice: the spec's control components carry no label.
113
+
114
+ ```ts
115
+ import { useMemo } from 'react'
116
+
117
+ import { attributeSelectionComponent, materialSwatchComponent, textValueComponent } from '@salsita-ai/sdk/domain'
118
+
119
+ import { byFieldOrder } from './fields.js'
120
+
121
+ export type ChoiceKind = 'selection' | 'swatch' | 'text'
122
+
123
+ /** A choice of the spec that is active now. */
124
+ export interface ActiveChoice {
125
+ readonly instanceName: string
126
+ readonly kind: ChoiceKind
127
+ }
128
+
129
+ // The guitar's choices whose spec component is active in the current navigation state, in the layout's order. A
130
+ // `$filter` with `navigationStates` makes a component active only in those states, so on each step this is the step's
131
+ // choices, and on the summary every choice. The control hooks throw for an inactive component; a field mounts only
132
+ // for a choice listed here.
133
+ export const useActiveChoices = (): readonly ActiveChoice[] => {
134
+ const guitarId = phx.frontend.useRootEntityId()
135
+ const selections = phx.frontend.useComponents({
136
+ componentTypes: [attributeSelectionComponent],
137
+ entityIds: [guitarId],
138
+ })
139
+ const swatches = phx.frontend.useComponents({ componentTypes: [materialSwatchComponent], entityIds: [guitarId] })
140
+ const texts = phx.frontend.useComponents({ componentTypes: [textValueComponent], entityIds: [guitarId] })
141
+
142
+ return useMemo(() => {
143
+ const choices = [
144
+ ...(selections ?? []).map(
145
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'selection' })
146
+ ),
147
+ ...(swatches ?? []).map(
148
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'swatch' })
149
+ ),
150
+ ...(texts ?? []).map(
151
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'text' })
152
+ ),
153
+ ]
154
+
155
+ return choices.sort((a, b) => byFieldOrder(a.instanceName, b.instanceName))
156
+ }, [selections, swatches, texts])
157
+ }
158
+ ```
159
+
160
+ `StepFields` then mounts one field per active choice, by its kind. Moving a choice to another step is a change of its `$filter` in the spec, and the step follows:
161
+
162
+ ```tsx
163
+ import { Box } from '@mui/material'
164
+
165
+ import { ChoiceField } from './ChoiceField.js'
166
+ import { FinishField } from './FinishField.js'
167
+ import { SignatureField } from './SignatureField.js'
168
+ import { fieldOf } from './fields.js'
169
+ import { useActiveChoices } from './use-active-choices.js'
170
+
171
+ interface StepFieldsProps {
172
+ readonly compact: boolean
173
+ }
174
+
175
+ // The fields of the active step. The layout does not list which choice belongs to which step: it shows the choices
176
+ // the spec leaves active, so moving a choice to another step is a change of its `$filter` in the spec.
177
+ export const StepFields = ({ compact }: StepFieldsProps) => {
178
+ const choices = useActiveChoices()
179
+
180
+ return (
181
+ <Box component="div" sx={{ display: 'grid', gap: compact ? 2.5 : 4 }}>
182
+ {choices.map(({ instanceName, kind }) => {
183
+ const { label, warningTypes } = fieldOf(instanceName)
184
+ const props = { instanceName, label, warningTypes, compact }
185
+
186
+ switch (kind) {
187
+ case 'selection':
188
+ return <ChoiceField key={instanceName} {...props} />
189
+ case 'swatch':
190
+ return <FinishField key={instanceName} {...props} />
191
+ case 'text':
192
+ return <SignatureField key={instanceName} {...props} />
193
+ }
194
+ })}
195
+ </Box>
196
+ )
197
+ }
198
+ ```
199
+
200
+ Mount a field only for a choice that is active. The control hooks, such as `useAttributeSelection`, throw for a component that a `$filter` makes inactive, so a layout that renders its fields some other way checks first with `phx.frontend.useComponent({ entityId, componentType, instanceName })`, which returns `null` for an inactive component.
201
+
202
+ ## The camera follows the wizard
203
+
204
+ The cameras are spec components too, and the same `$filter` switches them: the whole guitar for the body, the summary and the confirmation, the upper neck and the headstock for the neck, the pickups for the electronics, and the headstock close up for the signature. Entering a step activates its camera and the camera moves there; the layout has no camera code.
205
+
206
+ ```yaml
207
+ components:
208
+ Guitar:
209
+ fitRootPerspectiveCamera:
210
+ overview:
211
+ fov: 30
212
+ fitToBox: true
213
+ $filter:
214
+ navigationStates:
215
+ - body
216
+ - summary
217
+ - ordered
218
+ staticPerspectiveCamera:
219
+ neck:
220
+ fov: 30
221
+ lookAtX: 0
222
+ lookAtY: 625
223
+ lookAtZ: 0
224
+ distance: 1350
225
+ azimuthAngle: 0.55
226
+ polarAngle: 1.36
227
+ $filter:
228
+ navigationStates:
229
+ - neck
230
+ ```
231
+
232
+ The overview fits the guitar into the safe area the page leaves free. A `staticPerspectiveCamera` looks at a point of the model from a fixed distance, in millimetres, which suits a close-up of one part; the camera padding of the safe area still applies, so the part stays beside the panel. [Cameras / Transitions](?path=/docs/3d-scene-cameras-transitions--docs) describes when the camera moves between cameras.
233
+
234
+ ## Finish swatches
235
+
236
+ The finish is a `materialSwatch`: the spec lists each swatch with its name and the colour of its chip.
237
+
238
+ ```yaml
239
+ components:
240
+ Guitar:
241
+ materialSwatch:
242
+ finish:
243
+ selectedSwatchKey:
244
+ $bind: finish
245
+ swatches:
246
+ Seafoam:
247
+ swatchName: Seafoam green
248
+ color: '#8fcab6'
249
+ CandyRed:
250
+ swatchName: Candy apple red
251
+ color: '#b0141f'
252
+ Midnight:
253
+ swatchName: Midnight blue metallic
254
+ color: '#1d2b4f'
255
+ NaturalAsh:
256
+ swatchName: Natural ash
257
+ color: '#d8b98a'
258
+ ```
259
+
260
+ `FinishField` reads it with `phx.frontend.useSwatchSelection(guitarId, 'finish', [])`. Each option has `swatchKey`, `swatchName`, `color`, `selected`, `disabled`, `hint` and `onClick()`, which writes the swatch; the third argument lists the `fixable` violation types the options ignore. The field draws the swatches as round chips in the spec's colours with the chosen one's name beside the label. How the finish renders on the guitar is the material override's business, see [The stage and the model](#the-stage-and-the-model).
261
+
262
+ ## A signature on the headstock
263
+
264
+ The customer can sign the headstock. The signature is a `textValue`, and a `sceneEntityObjectTextDecal` on the `headstock` object prints it in script along the headstock's face:
265
+
266
+ ```yaml
267
+ components:
268
+ Guitar:
269
+ textValue:
270
+ signature:
271
+ value:
272
+ $bind: signature
273
+ sceneEntityObjectTextDecal:
274
+ headstock:
275
+ text:
276
+ $read: signature
277
+ font: SignPainter-HouseScript
278
+ preloadFonts:
279
+ - SignPainter-HouseScript
280
+ fontSize: auto
281
+ textColor:
282
+ $read: $signatureColor
283
+ positionX: 0.002
284
+ positionY: 0.76
285
+ positionZ: 0.004
286
+ rotationZ: 1.5708
287
+ sizeX: 0.13
288
+ sizeY: 0.03
289
+ sizeZ: 0.02
290
+ ```
291
+
292
+ The decal's instance name is the object's: `headstock` in `sceneEntityRemoteObject`. Its position, rotation and size are in the object's own units, metres for this model, and `fontSize: auto` fits the text into the decal's width. The font must be loaded by the page; `preloadFonts` redraws the decal once it is.
293
+
294
+ The headstock takes 16 characters. The limit is the expression `$signatureLimit`, declared in the spec so the layout can read it, and a `fixable` rule rejects a longer signature with a message that names it:
295
+
296
+ ```ts
297
+ export default phx.domain.implementCustomInvariants({
298
+ Guitar: {
299
+ // `fixable`: a change that makes the signature too long is rejected, so the headstock never shows more than fits.
300
+ // The message names the limit, so it is built here rather than in the spec.
301
+ signatureLength: s => {
302
+ if (s.signature.length <= s.$signatureLimit) {
303
+ return []
304
+ }
305
+
306
+ return [
307
+ {
308
+ type: 'signatureTooLong',
309
+ severity: 'fixable',
310
+ message: `The headstock fits ${s.$signatureLimit} characters. Shorten the signature.`,
311
+ },
312
+ ]
313
+ },
314
+ // `warning` in the spec: the change is kept, so a body chosen after the pickups does not undo them, and ordering
315
+ // waits until the customer resolves it
316
+ offsetHumbuckers: s => {
317
+ return s.bodyShape !== 'Offset' || s.pickups !== 'Humbuckers'
318
+ },
319
+ },
320
+ })
321
+ ```
322
+
323
+ `SignatureField` writes with `phx.frontend.useTextValue(guitarId, 'signature')`. `update(event, value)` stages each value as the customer types, so the headstock follows every key, and `resolve()` commits it when they leave the field or press Enter. While the staged text breaks the rule, `hasErrors` is set and `hints` holds the rule's message, which the field shows in red with the counter; `resolve()` then rolls back to the committed signature instead, so the guitar never keeps a signature that does not fit.
324
+
325
+ ```tsx
326
+ import { Box, InputBase, Typography } from '@mui/material'
327
+
328
+ import { FieldShell } from './FieldShell.js'
329
+ import { NIGHTJAR } from './tokens.js'
330
+
331
+ interface SignatureFieldProps {
332
+ readonly instanceName: string
333
+ readonly label: string
334
+ readonly warningTypes: readonly string[]
335
+ readonly compact: boolean
336
+ }
337
+
338
+ // The spec's `textValue`, which the spec prints on the headstock with a text decal. Typing stages each value, so the
339
+ // headstock follows every key; leaving the field or pressing Enter commits it. A value the `fixable` length rule
340
+ // rejects shows the rule's message as the hook's `hints`, and leaving the field rolls back to the last value that
341
+ // fitted. The counter reads the limit from the spec's `$signatureLimit` expression, which the rule checks too.
342
+ export const SignatureField = ({ instanceName, label, warningTypes, compact }: SignatureFieldProps) => {
343
+ const guitarId = phx.frontend.useRootEntityId()
344
+ const guitar = phx.frontend.useRootEntityState()
345
+ const signature = phx.frontend.useTextValue(guitarId, instanceName)
346
+ const limit = guitar.$signatureLimit
347
+
348
+ return (
349
+ <FieldShell
350
+ id={`nightjar-${instanceName}`}
351
+ label={label}
352
+ aside={
353
+ <Typography
354
+ aria-live="polite"
355
+ sx={{
356
+ fontFamily: NIGHTJAR.font,
357
+ fontSize: 13,
358
+ fontVariantNumeric: 'tabular-nums',
359
+ color: signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.muted,
360
+ }}
361
+ >
362
+ {signature.value.length} / {limit}
363
+ </Typography>
364
+ }
365
+ hint={
366
+ compact ? undefined : 'In gold leaf on dark necks, in black on maple. Leave it empty for a plain headstock.'
367
+ }
368
+ warningTypes={warningTypes}
369
+ compact={compact}
370
+ >
371
+ <Box
372
+ component="div"
373
+ sx={{
374
+ display: 'grid',
375
+ gap: 1,
376
+ paddingInline: 2,
377
+ paddingBlock: compact ? 1 : 1.5,
378
+ borderRadius: 1.5,
379
+ border: `1px solid ${signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.line}`,
380
+ bgcolor: NIGHTJAR.raised,
381
+ '&:focus-within': { borderColor: signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.amber },
382
+ }}
383
+ >
384
+ <InputBase
385
+ value={signature.value}
386
+ placeholder="Your name"
387
+ onChange={event => signature.update(event, event.target.value)}
388
+ onBlur={signature.resolve}
389
+ onKeyDown={event => {
390
+ if (event.key === 'Enter') {
391
+ signature.resolve()
392
+ }
393
+ }}
394
+ inputProps={{
395
+ 'aria-labelledby': `nightjar-${instanceName}-label`,
396
+ 'aria-invalid': signature.hasErrors,
397
+ 'aria-describedby': `nightjar-${instanceName}-error`,
398
+ spellCheck: false,
399
+ }}
400
+ sx={{
401
+ fontFamily: NIGHTJAR.script,
402
+ fontSize: compact ? 30 : 38,
403
+ lineHeight: 1.2,
404
+ color: NIGHTJAR.amber,
405
+ '& input::placeholder': { color: NIGHTJAR.faint, opacity: 1 },
406
+ }}
407
+ />
408
+ </Box>
409
+ <Typography
410
+ id={`nightjar-${instanceName}-error`}
411
+ role="alert"
412
+ sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.alert, '&:empty': { display: 'none' } }}
413
+ >
414
+ {signature.hasErrors ? signature.hints : ''}
415
+ </Typography>
416
+ </FieldShell>
417
+ )
418
+ }
419
+ ```
420
+
421
+ ## Summary and conflicts
422
+
423
+ The last step reviews the guitar. `SummaryRows` lists every choice active in the `summary` state with the control hook of its kind: the option's `title` from `useAttributeSelection`, the swatch's name and colour from `useSwatchSelection`, the text from `useTextValue`. The summary only reviews; a choice is changed on its step.
424
+
425
+ One rule spans two steps: the Offset body is routed for single-coil-sized pickups. It is a `warning`, so a change that breaks it is kept, and a body chosen after the pickups does not undo them:
426
+
427
+ ```yaml
428
+ invariants:
429
+ Guitar:
430
+ offsetHumbuckers:
431
+ type: offsetHumbuckers
432
+ severity: warning
433
+ message: The Offset body is routed for single-coil-sized pickups. Choose single coils or P-90s, or the Single-cut body for humbuckers.
434
+ ```
435
+
436
+ While it holds, the layout shows it three times, each read from `phx.frontend.useFinalizeViolations().violations`: under the **Pickups** and **Body shape** fields, as a mark on the **Electronics** step, and on the summary with **Go to Electronics**. The layout's `STEP_WARNINGS` names the step that resolves each warning.
437
+
438
+ ## Place the order
439
+
440
+ The spec declares the order action, and the handler, `src/handlers/place-order.ts`, stands in for the shop's order service:
441
+
442
+ ```yaml
443
+ components:
444
+ Guitar:
445
+ finalizeAction:
446
+ placeOrder:
447
+ variant: primary
448
+ label: Place order
449
+ title: Send the guitar as configured to the Northfret workshop
450
+ icon: ShoppingBag
451
+ handler: placeOrder
452
+ ```
453
+
454
+ ```ts
455
+ import { useCallback } from 'react'
456
+
457
+ // Stands in for Northfret's order service. Placing an order takes a moment. The service checks stock when the order
458
+ // arrives, which the configurator cannot know beforehand: gold hardware is back-ordered, so it refuses that order with
459
+ // its reason; any other order is accepted and the page shows the confirmation.
460
+ export default phx.frontend.createPhoenixHandler('placeOrder', () => {
461
+ const navigate = phx.frontend.useNavigate()
462
+ const guitar = phx.frontend.useRootEntityState()
463
+
464
+ return {
465
+ handler: useCallback(async () => {
466
+ await new Promise(resolve => setTimeout(resolve, 1600))
467
+
468
+ if (guitar.hardware === 'Gold') {
469
+ throw new Error(
470
+ 'Gold hardware is back-ordered until next month. Choose chrome or black hardware, or order later.'
471
+ )
472
+ }
473
+
474
+ navigate('ordered')
475
+ }, [guitar.hardware, navigate]),
476
+ }
477
+ })
478
+ ```
479
+
480
+ **Place order** sits in the bar on the summary step. It runs `phx.frontend.useFinalizeActions().primary`:
481
+
482
+ - **Blocked.** While a warning holds, the action is `disabled` and `hints` holds the warnings' text: the bar shows "Settle the choices above first" beside the button, and the summary lists the conflicts. The name and the e-mail are the layout's own state. The button stays clickable; while either blocks the order, a click reveals what is missing from the form instead of ordering.
483
+ - **Pending.** `run()` starts the handler beside the layout; `isPending` turns the button into "Placing order…" with a spinner and locks the stepper, **Back** and the form.
484
+ - **Failed.** The service refuses gold hardware, which the configurator cannot know beforehand. The handler throws, and the action's `error` is shown at the top of the summary; no snackbar is raised. **Place order** stays available to try again.
485
+ - **Placed.** The handler enters `ordered`, where the panel thanks the customer by name with an order number made from the configuration's id, and lists the build.
486
+
487
+ A viewer of a shared configuration sees no **Place order**: neither hook checks read mode, so the bar hides it while `phx.frontend.useAccessService().accessInfo.viewMode` is set.
488
+
489
+ ## Pages that slide in
490
+
491
+ `ConfiguratorLayout` keeps the scene mounted and lays the desktop or the phone page over it, by the width of the configurator's container from `phx.frontend.useActiveViewports()`. It also holds the contact form's state and the furthest step reached, so both survive the switch between the two, and the form survives the move to the confirmation.
492
+
493
+ ```tsx
494
+ import { Box } from '@mui/material'
495
+
496
+ import { LayoutSlots } from '@salsita-ai/sdk/configurator'
497
+
498
+ import { NIGHTJAR } from '../_fields/index.js'
499
+ import { DesktopPage } from '../_shared/DesktopPage.js'
500
+ import { PhonePage } from '../_shared/PhonePage.js'
501
+ import { useOrderForm } from '../_shared/order-form.js'
502
+ import { useReachedStep } from '../_shared/use-reached-step.js'
503
+
504
+ // A wizard over the scene. The canvas stays mounted the whole time; the desktop or the phone page lies over it. The
505
+ // contact form's state and the furthest step reached live here, so they survive the switch between the two; the form
506
+ // also survives the move to the confirmation.
507
+ const ConfiguratorLayout = () => {
508
+ // `null` until the container has been measured; only the scene shows until then
509
+ const viewports = phx.frontend.useActiveViewports()
510
+ const { activeIndex } = phx.frontend.useStepNavigation()
511
+ const form = useOrderForm()
512
+ const reached = useReachedStep(activeIndex)
513
+ const isDesktop = viewports?.includes('desktop') ?? false
514
+
515
+ return (
516
+ <Box component="div" sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: NIGHTJAR.ink }}>
517
+ <LayoutSlots.SharedCanvas />
518
+ {viewports !== null &&
519
+ (isDesktop ? <DesktopPage form={form} reached={reached} /> : <PhonePage form={form} reached={reached} />)}
520
+ </Box>
521
+ )
522
+ }
523
+
524
+ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
525
+ ```
526
+
527
+ Each page measures its safe area with `phx.frontend.useSceneSafeArea()`. The summary's panel is wider than a step's, so the steps, the summary and the confirmation are three pages, each with its own safe area, and each slides in when it opens. The safe area slides with its page: the measurement taken when the page mounts sees it shifted, and a `transform` that ends is not a resize, so nothing measures again. The page calls `update()` from the same hook when its own slide ends, not when the step content's slide inside it does. Without it the guitar stays shifted sideways by the slide's 48 pixels.
528
+
529
+ ```tsx
530
+ import { Box, Typography } from '@mui/material'
531
+ import type { AnimationEvent } from 'react'
532
+
533
+ import { ENTER_FROM_END, HEADING_SX, NIGHTJAR } from '../_fields/index.js'
534
+ import { OrderBar } from './OrderBar.js'
535
+ import { OrderConfirmation } from './OrderConfirmation.js'
536
+ import { StepHeader } from './StepHeader.js'
537
+ import { StepPanel } from './StepPanel.js'
538
+ import { SummaryPanel } from './SummaryPanel.js'
539
+ import type { OrderForm } from './order-form.js'
540
+
541
+ type Page = 'steps' | 'summary' | 'ordered'
542
+
543
+ // The summary needs room for the contact form, so its panel is wider
544
+ const PANEL_WIDTHS: Readonly<Record<Page, number>> = { steps: 420, summary: 520, ordered: 420 }
545
+
546
+ const pageOf = (navigationState: string): Page => {
547
+ return navigationState === 'ordered' ? 'ordered' : navigationState === 'summary' ? 'summary' : 'steps'
548
+ }
549
+
550
+ // The brand's mark: a fret of amber over the name of the shop
551
+ const Brand = () => {
552
+ return (
553
+ <Box component="div" sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 'none' }}>
554
+ <Box component="span" aria-hidden sx={{ inlineSize: 4, blockSize: 28, bgcolor: NIGHTJAR.amber }} />
555
+ <Box component="div" sx={{ display: 'grid' }}>
556
+ <Typography sx={{ ...HEADING_SX, fontSize: 16, letterSpacing: '0.32em', lineHeight: 1.1 }}>
557
+ Northfret
558
+ </Typography>
559
+ <Typography sx={{ ...HEADING_SX, fontSize: 10, letterSpacing: '0.36em', color: NIGHTJAR.muted }}>
560
+ Custom shop
561
+ </Typography>
562
+ </Box>
563
+ </Box>
564
+ )
565
+ }
566
+
567
+ interface DesktopPageProps {
568
+ readonly form: OrderForm
569
+ readonly reached: number
570
+ }
571
+
572
+ // The desktop wizard: the stepper on top, the guitar in the free area on the left, the panel of the step on the
573
+ // right and the bar with the price at the bottom.
574
+ //
575
+ // The summary's panel is wider than a step's, so the steps, the summary and the confirmation are three pages, each
576
+ // with its own safe area, and each slides in when it opens. The safe area slides with its page: the first
577
+ // measurement, taken when the page mounts, sees it shifted, and no resize follows when the slide ends. `update()`
578
+ // from `useSceneSafeArea` measures again once the slide has settled, so the camera frames the guitar in the place the
579
+ // page leaves for it.
580
+ export const DesktopPage = ({ form, reached }: DesktopPageProps) => {
581
+ const { containerRef, safeAreaRef, update } = phx.frontend.useSceneSafeArea()
582
+ const navigationState = phx.frontend.useActiveNavigationState()
583
+ const page = pageOf(navigationState)
584
+
585
+ // The step's own content slides too; only the page's slide moves the safe area
586
+ const handleAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
587
+ if (event.target === event.currentTarget) {
588
+ update()
589
+ }
590
+ }
591
+
592
+ return (
593
+ <Box
594
+ component="div"
595
+ ref={containerRef}
596
+ sx={{
597
+ position: 'absolute',
598
+ inset: 0,
599
+ display: 'grid',
600
+ gridTemplateRows: 'auto minmax(0, 1fr) auto',
601
+ fontFamily: NIGHTJAR.font,
602
+ color: NIGHTJAR.cream,
603
+ // The free area lies over the canvas; only the bars and the panel take the pointer
604
+ pointerEvents: 'none',
605
+ }}
606
+ >
607
+ <Box
608
+ component="header"
609
+ sx={{
610
+ display: 'flex',
611
+ alignItems: 'center',
612
+ gap: 5,
613
+ minBlockSize: 72,
614
+ paddingInline: 4,
615
+ bgcolor: NIGHTJAR.ink,
616
+ borderBlockEnd: `1px solid ${NIGHTJAR.line}`,
617
+ pointerEvents: 'auto',
618
+ }}
619
+ >
620
+ <Brand />
621
+ <StepHeader compact={false} reached={reached} />
622
+ </Box>
623
+
624
+ <Box
625
+ component="div"
626
+ key={page}
627
+ onAnimationEnd={handleAnimationEnd}
628
+ sx={{
629
+ display: 'grid',
630
+ gridTemplateColumns: `minmax(0, 1fr) ${PANEL_WIDTHS[page]}px`,
631
+ minBlockSize: 0,
632
+ animation: `${ENTER_FROM_END} 480ms cubic-bezier(0.2, 0.7, 0.2, 1)`,
633
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
634
+ }}
635
+ >
636
+ <Box component="div" sx={{ position: 'relative', minInlineSize: 0 }}>
637
+ {/* The camera fits the guitar into this box, so its inset is the room around the guitar */}
638
+ <Box component="div" ref={safeAreaRef} sx={{ position: 'absolute', inset: '40px 72px 56px' }} />
639
+ <Typography
640
+ sx={{
641
+ position: 'absolute',
642
+ insetBlockEnd: 20,
643
+ insetInline: 0,
644
+ textAlign: 'center',
645
+ fontSize: 12,
646
+ letterSpacing: '0.14em',
647
+ textTransform: 'uppercase',
648
+ color: NIGHTJAR.faint,
649
+ }}
650
+ >
651
+ Drag to turn the guitar
652
+ </Typography>
653
+ </Box>
654
+ <Box
655
+ component="aside"
656
+ aria-label={page === 'steps' ? 'Step' : page === 'summary' ? 'Summary' : 'Order'}
657
+ sx={{
658
+ overflowY: 'auto',
659
+ paddingInline: 4,
660
+ paddingBlock: 4,
661
+ bgcolor: NIGHTJAR.panel,
662
+ borderInlineStart: `1px solid ${NIGHTJAR.line}`,
663
+ pointerEvents: 'auto',
664
+ }}
665
+ >
666
+ {page === 'steps' && <StepPanel compact={false} />}
667
+ {page === 'summary' && <SummaryPanel compact={false} form={form} />}
668
+ {page === 'ordered' && <OrderConfirmation compact={false} contact={form.contact} />}
669
+ </Box>
670
+ </Box>
671
+
672
+ <OrderBar compact={false} form={form} />
673
+ </Box>
674
+ )
675
+ }
676
+ ```
677
+
678
+ ## On a phone
679
+
680
+ The same layout in a frame 390 pixels wide, as on a phone. A compact "2 / 5 · Neck" line with a thin progress bar replaces the stepper, the choices become chips in a row that scrolls sideways, the guitar keeps the top of the screen and the bar stays fixed at the bottom. Opened on its own from the sidebar, the **Phone** story sets Storybook's viewport to 390 × 844.
681
+
682
+ ## The stage and the model
683
+
684
+ The spec's `canvasCustomEnvironment` names `NightjarStage`, a layout element rendered inside the scene: a dark room with a warm glow on the wall behind the guitar, a warm key light and a cool rim. drei's `Lightformer` panels exist only in the reflections, where they draw the long highlights on the clearcoat and the hardware, and no image has to be downloaded.
685
+
686
+ ```tsx
687
+ import { Environment, Lightformer } from '@react-three/drei'
688
+ import { useMemo } from 'react'
689
+ import { CanvasTexture, SRGBColorSpace } from 'three'
690
+
691
+ import { NIGHTJAR } from '../_fields/index.js'
692
+
693
+ /** A warm pool of light that fades into the dark, drawn once on a canvas. */
694
+ const createGlow = (): CanvasTexture => {
695
+ const size = 512
696
+ const canvas = document.createElement('canvas')
697
+ canvas.width = size
698
+ canvas.height = size
699
+ const context = canvas.getContext('2d')
700
+
701
+ if (context) {
702
+ const gradient = context.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2)
703
+ gradient.addColorStop(0, 'rgba(242, 163, 58, 0.42)')
704
+ gradient.addColorStop(0.22, 'rgba(214, 112, 46, 0.2)')
705
+ gradient.addColorStop(0.5, 'rgba(122, 30, 43, 0.07)')
706
+ gradient.addColorStop(0.75, 'rgba(14, 15, 18, 0)')
707
+ context.fillStyle = gradient
708
+ context.fillRect(0, 0, size, size)
709
+ }
710
+
711
+ const texture = new CanvasTexture(canvas)
712
+ texture.colorSpace = SRGBColorSpace
713
+ return texture
714
+ }
715
+
716
+ // The stage named by the spec's `canvasCustomEnvironment`: a dark room with a warm glow on the wall behind the guitar,
717
+ // a warm key light and a cool rim. The light panels exist only in the reflections, where they draw the long highlights
718
+ // on the clearcoat and the hardware. The scene is in millimetres.
719
+ export default phx.frontend.createCustomPhoenixLayoutElement('NightjarStage', () => {
720
+ const glow = useMemo(createGlow, [])
721
+
722
+ return (
723
+ <>
724
+ <color attach="background" args={[NIGHTJAR.ink]} />
725
+ <ambientLight intensity={0.4} />
726
+ <directionalLight position={[-900, 1400, 1600]} intensity={2.4} color="#ffe6c7" />
727
+ <directionalLight position={[1400, 700, -900]} intensity={1.2} color="#a9c1ff" />
728
+
729
+ {/* The glow on the wall, behind the guitar and out of the camera's fit */}
730
+ <mesh position={[-250, 300, -900]}>
731
+ <planeGeometry args={[3600, 3600]} />
732
+ <meshBasicMaterial map={glow} transparent depthWrite={false} toneMapped={false} />
733
+ </mesh>
734
+
735
+ <Environment resolution={256} frames={1}>
736
+ <color attach="background" args={['#15161b']} />
737
+ {/* Tall strips at both sides for the long highlights, a softbox above, an amber wash behind */}
738
+ <Lightformer form="rect" intensity={7} position={[-5, 1, 3]} rotation-y={Math.PI / 3} scale={[1.2, 9, 1]} />
739
+ <Lightformer form="rect" intensity={4} position={[5, 1, 2]} rotation-y={-Math.PI / 3} scale={[1, 9, 1]} />
740
+ <Lightformer form="rect" intensity={2.5} position={[0, 6, 1]} rotation-x={Math.PI / 2} scale={[8, 3, 1]} />
741
+ <Lightformer form="rect" intensity={1.2} position={[0, 1, 7]} rotation-y={Math.PI} scale={[8, 4, 1]} />
742
+ <Lightformer form="rect" color={NIGHTJAR.amber} intensity={0.8} position={[0, -2, -7]} scale={[10, 3, 1]} />
743
+ </Environment>
744
+ </>
745
+ )
746
+ })
747
+ ```
748
+
749
+ The finish is a physical material with a clearcoat, set per body object with `sceneEntityObjectLocalMaterialOverride`: the override names the object, and only that object's `Finish` material changes. Natural ash shows a wood texture through the clearcoat; the paints have none. The materials many parts share, such as `Hardware` on the tuners, the bridges, the knobs and the switches, use `sceneEntityLocalMaterialOverride`, which changes every object of the entity that carries the material. The values come from expressions in `src/expressions/guitar.ts`, which also price the guitar.
750
+
751
+ ```yaml
752
+ components:
753
+ Guitar:
754
+ sceneEntityObjectLocalMaterialOverride:
755
+ bodyOffset:
756
+ materialsToOverride:
757
+ - Finish
758
+ materialType: physical
759
+ color:
760
+ $read: $finishColor
761
+ textureUrl:
762
+ $read: $finishTexture
763
+ metalness:
764
+ $read: $finishMetalness
765
+ roughness:
766
+ $read: $finishRoughness
767
+ clearcoat: 1
768
+ clearcoatRoughness: 0.04
769
+ ```
770
+
771
+ The guitar is `public/models/gallery/guitar.glb`, generated by `scripts/models/guitar.ts` in the Storybook package from three.js geometry: two bodies extruded from their outlines with a rounded bevel, a pickguard for each, the neck, fretboard, frets, inlays, headstock, tuners and strings, three pickup sets and the bridges, knobs and switches of each body. Each part is a named node the spec loads with `sceneEntityRemoteObject.<name>.objectName`; `sceneEntityObjectProperties` shows the parts of the chosen body and pickups. The script lives in `scripts/models/`, outside the example's `src/`, because every file under `src/` is part of this page.
772
+
773
+ ## Test cases
774
+
775
+ - The page opens on **Body**: the Offset in Seafoam green with a parchment pickguard, maple neck, rosewood board, single coils and chrome, **$1,650.00**, and **Next: Neck**. The stepper shows step 1 active and the rest upcoming, which cannot be clicked yet.
776
+ - **Candy apple red** turns the body red under a glossy clearcoat at $1,830.00; **Natural ash** shows the wood grain. The finish's name appears beside the label.
777
+ - **Single-cut** swaps the body, its small pickguard, the bar bridge with a tailpiece, four knobs and the toggle switch, and adds $150. **None** removes the pickguard.
778
+ - **Next: Neck** slides the neck's choices in from the right and moves the camera to the upper neck and the headstock. **Roasted maple** and **Ebony** change the neck and the board; on a maple board the dots turn black.
779
+ - **Back** slides the body's choices in from the left and returns the camera to the whole guitar.
780
+ - On **Electronics** the camera moves to the pickups. **Humbuckers** on the Offset shows "The Offset body is routed for single-coil-sized pickups. …" under **Pickups**, and the stepper marks **Electronics**. **P-90s** clears both.
781
+ - **Gold** turns the tuners, the bridge, the knobs and the switch gold.
782
+ - On **Details** the camera shows the headstock. Typing a name prints it on the headstock as you type, in gold on a dark neck and in black on maple; the counter shows the characters used of 16, and the price rises by $45.
783
+ - Typing past 16 characters turns the counter and the field red and shows "The headstock fits 16 characters. Shorten the signature."; leaving the field puts back the signature committed before, and the headstock with it.
784
+ - **Review order** opens the summary: a wider panel lists every choice, with the signature in script. With the humbucker conflict, the summary lists it with **Go to Electronics**, the bar shows "Settle the choices above first", and **Place order** reveals "Enter the name the order is for." and "Enter an e-mail address for the build updates." under the empty fields.
785
+ - **Go to Electronics** opens that step; after **P-90s**, the stepper's **Summary** returns to the summary in one click.
786
+ - After going back from the summary to an earlier step, narrowing the window until the phone page shows and widening it again leaves the stepper's **Summary** enabled.
787
+ - With a name and an e-mail, **Place order** shows "Placing order…" with a spinner for under two seconds. With gold hardware it then shows "The order did not go through" and the service's reason at the top of the summary; with chrome or black it opens the confirmation: "Thank you, …", the order number, the e-mail and the build. The stepper no longer takes clicks, and **Design another** returns to **Body**.
788
+ - Each step, the summary and the confirmation leave the guitar centred in the area beside the panel once their slide has settled.
789
+ - With the browser console's Verbose level, **Next** and **Back** log the step buttons' `nextStepButton` and `previousStepButton` events, the stepper the `topNavigation` `step` event, **Review order** the `summaryButton` event and **Place order** the `finalizeButton` event: without a Google Tag Manager id, Storybook's analytics log their events with `console.debug`.
790
+ - In the **Phone** story the guitar keeps the top of the screen, "1 / 5 · Body" and the progress line sit above it, the choices are chips in rows that scroll sideways, and **Next**, **Review order** and **Place order** work as on the desktop.
791
+ - The browser console shows no errors, apart from the "Failed to finalize" line that the refused order with gold hardware logs.
792
+
793
+ ## What this layout leaves out
794
+
795
+ - **Messages for rejected changes.** The only rule that rejects a change is the signature's, which the field explains itself, so the layout mounts neither `LayoutSlots.Services` nor `phx.frontend.useConfiguratorMessages()`. A project whose other rules reject changes, or that saves the configuration, mounts one of the two so a rejected change or a failed save is not silent; see [Custom Layout → Show messages](?path=/docs/layouts-custom-layout--docs#show-messages).
796
+ - **Read mode.** **Place order** is hidden while `phx.frontend.useAccessService().accessInfo.viewMode` is set, but the fields stay editable and the example ships no read template; [Custom Layout → Serve read mode](?path=/docs/layouts-custom-layout--docs#serve-read-mode) shows one.
797
+ - **An order that outlives the page.** The order number is made from the configuration's id and the contact details are the layout's state, so a configuration reopened in the `ordered` state thanks no one by name. A deployed project sends the details with the order and shows what its service answers.
798
+ - **The built-in arrangement.** Header, sidebar, sidebar footer, tabs, tool and view controls and the toolbar are not mounted; [Layout Slots](?path=/docs/phoenix-layout-slots--docs) lists every slot a project layout can mount.
799
+
800
+ ## Related pages
801
+
802
+ - [Kestrel Kettle](?path=/docs/layouts-gallery-kestrel-kettle--docs) sells a small product from one page, and [Ardent Compressor](?path=/docs/layouts-gallery-ardent-compressor--docs) is a stepped quote builder without a scene.
803
+ - [Custom Layout](?path=/docs/layouts-custom-layout--docs) explains a project layout step by step.
804
+ - [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout) lists the hooks used here, steps and tabs included.
805
+ - [Floating Header → Adding step navigation](?path=/docs/layouts-header-floating--docs#adding-step-navigation) shows the built-in stepper over the same `navigationSteps`.
806
+ - [Domain Model → Invariants](?path=/docs/phoenix-domain-model--docs#invariants) describes the rules.
807
+ - [Finalization](?path=/docs/phoenix-finalization--docs) describes the server side of a finalize action.
808
+
809
+ ## Complete Implementation
810
+
811
+ {/* AUTO-GENERATED SECTION: This section is automatically updated by 'phoenix story build' */}
812
+
813
+ <details>
814
+ <summary>Click to view the complete implementation files</summary>
815
+
816
+ ### ./spec/domain.yaml
817
+
818
+ ```yaml
819
+ model:
820
+ latest:
821
+ entityTypes:
822
+ - Guitar
823
+ aliases:
824
+ RootEntity:
825
+ - Guitar
826
+ commonTypes:
827
+ BodyShape:
828
+ enum:
829
+ - Offset
830
+ - SingleCut
831
+ Finish:
832
+ enum:
833
+ - Seafoam
834
+ - CandyRed
835
+ - Midnight
836
+ - NaturalAsh
837
+ Pickguard:
838
+ enum:
839
+ - Parchment
840
+ - Black
841
+ - Pearloid
842
+ - None
843
+ NeckWood:
844
+ enum:
845
+ - Maple
846
+ - RoastedMaple
847
+ - Mahogany
848
+ Fretboard:
849
+ enum:
850
+ - Maple
851
+ - Rosewood
852
+ - Ebony
853
+ Pickups:
854
+ enum:
855
+ - SingleCoils
856
+ - Humbuckers
857
+ - P90s
858
+ Hardware:
859
+ enum:
860
+ - Chrome
861
+ - Gold
862
+ - Black
863
+ shapes:
864
+ Guitar:
865
+ bodyShape:
866
+ $ref: '#/model/latest/commonTypes/BodyShape'
867
+ finish:
868
+ $ref: '#/model/latest/commonTypes/Finish'
869
+ pickguard:
870
+ $ref: '#/model/latest/commonTypes/Pickguard'
871
+ neckWood:
872
+ $ref: '#/model/latest/commonTypes/NeckWood'
873
+ fretboard:
874
+ $ref: '#/model/latest/commonTypes/Fretboard'
875
+ pickups:
876
+ $ref: '#/model/latest/commonTypes/Pickups'
877
+ hardware:
878
+ $ref: '#/model/latest/commonTypes/Hardware'
879
+ signature:
880
+ type: string
881
+
882
+ presets:
883
+ - rootEntityType: Guitar
884
+ initialDomainEntities:
885
+ - id: Guitar-1
886
+ type: Guitar
887
+ bodyShape: Offset
888
+ finish: Seafoam
889
+ pickguard: Parchment
890
+ neckWood: Maple
891
+ fretboard: Rosewood
892
+ pickups: SingleCoils
893
+ hardware: Chrome
894
+ signature: ''
895
+
896
+ # One state per wizard step, and `ordered` for the confirmation after the order
897
+ navigationStates:
898
+ - body
899
+ - neck
900
+ - electronics
901
+ - details
902
+ - summary
903
+ - ordered
904
+
905
+ # The longest signature the headstock takes; the invariant and the layout's counter both read it
906
+ expressions:
907
+ Guitar:
908
+ $signatureLimit:
909
+ type: number
910
+
911
+ invariants:
912
+ Guitar:
913
+ signatureLength:
914
+ types:
915
+ - signatureTooLong
916
+ offsetHumbuckers:
917
+ type: offsetHumbuckers
918
+ severity: warning
919
+ message: The Offset body is routed for single-coil-sized pickups. Choose single coils or P-90s, or the Single-cut body for humbuckers.
920
+
921
+ components:
922
+ Guitar:
923
+ detail:
924
+ main:
925
+ title: Nightjar
926
+ subtitle: Northfret Custom Shop
927
+ description: A hand-built electric guitar. Choose the body, the neck, the electronics and the details, then order it from the summary.
928
+ price:
929
+ main:
930
+ price:
931
+ $read: $price
932
+ navigationSteps:
933
+ main:
934
+ steps:
935
+ - label: Body
936
+ navigationStates:
937
+ - body
938
+ - label: Neck
939
+ navigationStates:
940
+ - neck
941
+ - label: Electronics
942
+ navigationStates:
943
+ - electronics
944
+ - label: Details
945
+ navigationStates:
946
+ - details
947
+ - label: Summary
948
+ navigationStates:
949
+ - summary
950
+ - ordered
951
+ finalizeAction:
952
+ placeOrder:
953
+ variant: primary
954
+ label: Place order
955
+ title: Send the guitar as configured to the Northfret workshop
956
+ icon: ShoppingBag
957
+ handler: placeOrder
958
+
959
+ # Each choice is active on its own step, and on the summary and the confirmation, which list every choice
960
+ attributeSelection:
961
+ bodyShape:
962
+ value:
963
+ $bind: bodyShape
964
+ options:
965
+ - label: Offset
966
+ description: Contoured waist, 3.4 kg
967
+ value: Offset
968
+ - label: Single-cut
969
+ description: Thicker body, long sustain
970
+ value: SingleCut
971
+ $filter:
972
+ navigationStates:
973
+ - body
974
+ - summary
975
+ - ordered
976
+ pickguard:
977
+ value:
978
+ $bind: pickguard
979
+ options:
980
+ - label: Parchment
981
+ value: Parchment
982
+ - label: Black
983
+ value: Black
984
+ - label: Pearloid
985
+ description: Shimmers in the light
986
+ value: Pearloid
987
+ - label: None
988
+ value: None
989
+ $filter:
990
+ navigationStates:
991
+ - body
992
+ - summary
993
+ - ordered
994
+ neckWood:
995
+ value:
996
+ $bind: neckWood
997
+ options:
998
+ - label: Maple
999
+ description: Bright and snappy
1000
+ value: Maple
1001
+ - label: Roasted maple
1002
+ description: Baked for stability, caramel colour
1003
+ value: RoastedMaple
1004
+ - label: Mahogany
1005
+ description: Warm and round
1006
+ value: Mahogany
1007
+ $filter:
1008
+ navigationStates:
1009
+ - neck
1010
+ - summary
1011
+ - ordered
1012
+ fretboard:
1013
+ value:
1014
+ $bind: fretboard
1015
+ options:
1016
+ - label: Maple
1017
+ value: Maple
1018
+ - label: Rosewood
1019
+ value: Rosewood
1020
+ - label: Ebony
1021
+ description: Dense and smooth
1022
+ value: Ebony
1023
+ $filter:
1024
+ navigationStates:
1025
+ - neck
1026
+ - summary
1027
+ - ordered
1028
+ pickups:
1029
+ value:
1030
+ $bind: pickups
1031
+ options:
1032
+ - label: Single coils
1033
+ description: Glassy and articulate
1034
+ value: SingleCoils
1035
+ - label: Humbuckers
1036
+ description: Thick and hum-free
1037
+ value: Humbuckers
1038
+ - label: P-90s
1039
+ description: Gritty, with bite
1040
+ value: P90s
1041
+ $filter:
1042
+ navigationStates:
1043
+ - electronics
1044
+ - summary
1045
+ - ordered
1046
+ hardware:
1047
+ value:
1048
+ $bind: hardware
1049
+ options:
1050
+ - label: Chrome
1051
+ value: Chrome
1052
+ - label: Gold
1053
+ value: Gold
1054
+ - label: Black
1055
+ value: Black
1056
+ $filter:
1057
+ navigationStates:
1058
+ - electronics
1059
+ - summary
1060
+ - ordered
1061
+ materialSwatch:
1062
+ finish:
1063
+ selectedSwatchKey:
1064
+ $bind: finish
1065
+ swatches:
1066
+ Seafoam:
1067
+ swatchName: Seafoam green
1068
+ color: '#8fcab6'
1069
+ CandyRed:
1070
+ swatchName: Candy apple red
1071
+ color: '#b0141f'
1072
+ Midnight:
1073
+ swatchName: Midnight blue metallic
1074
+ color: '#1d2b4f'
1075
+ NaturalAsh:
1076
+ swatchName: Natural ash
1077
+ color: '#d8b98a'
1078
+ $filter:
1079
+ navigationStates:
1080
+ - body
1081
+ - summary
1082
+ - ordered
1083
+ textValue:
1084
+ signature:
1085
+ value:
1086
+ $bind: signature
1087
+ $filter:
1088
+ navigationStates:
1089
+ - details
1090
+ - summary
1091
+ - ordered
1092
+
1093
+ canvasConfig:
1094
+ main:
1095
+ highlightingDisabled: true
1096
+ canvasCustomEnvironment:
1097
+ main:
1098
+ name: NightjarStage
1099
+
1100
+ # The camera follows the wizard: the whole guitar for the body and the summary, then a closer view of what each
1101
+ # step changes. Each camera is active only in its step's navigation state; the scene is in millimetres.
1102
+ fitRootPerspectiveCamera:
1103
+ overview:
1104
+ fov: 30
1105
+ near: 10
1106
+ far: 20000
1107
+ minDistance: 600
1108
+ maxDistance: 9000
1109
+ azimuthAngle: 0.28
1110
+ polarAngle: 1.48
1111
+ minPolarAngle: 0.9
1112
+ maxPolarAngle: 2
1113
+ fitToBox: true
1114
+ fitMeshChanges: false
1115
+ $filter:
1116
+ navigationStates:
1117
+ - body
1118
+ - summary
1119
+ - ordered
1120
+ staticPerspectiveCamera:
1121
+ neck:
1122
+ fov: 30
1123
+ near: 10
1124
+ far: 20000
1125
+ lookAtX: 0
1126
+ lookAtY: 625
1127
+ lookAtZ: 0
1128
+ distance: 1350
1129
+ minDistance: 300
1130
+ maxDistance: 3000
1131
+ azimuthAngle: 0.55
1132
+ polarAngle: 1.36
1133
+ $filter:
1134
+ navigationStates:
1135
+ - neck
1136
+ electronics:
1137
+ fov: 30
1138
+ near: 10
1139
+ far: 20000
1140
+ lookAtX: 0
1141
+ lookAtY: 40
1142
+ lookAtZ: 0
1143
+ distance: 1150
1144
+ minDistance: 300
1145
+ maxDistance: 3000
1146
+ azimuthAngle: -0.32
1147
+ polarAngle: 1.22
1148
+ $filter:
1149
+ navigationStates:
1150
+ - electronics
1151
+ details:
1152
+ fov: 30
1153
+ near: 10
1154
+ far: 20000
1155
+ lookAtX: -10
1156
+ lookAtY: 750
1157
+ lookAtZ: 0
1158
+ distance: 520
1159
+ minDistance: 200
1160
+ maxDistance: 3000
1161
+ azimuthAngle: 0.12
1162
+ polarAngle: 1.5
1163
+ $filter:
1164
+ navigationStates:
1165
+ - details
1166
+
1167
+ sceneEntityRemoteObject:
1168
+ bodyOffset:
1169
+ fileUrl: /models/gallery/guitar.glb
1170
+ objectName: BodyOffset
1171
+ dimensionUnit: m
1172
+ bodySingleCut:
1173
+ fileUrl: /models/gallery/guitar.glb
1174
+ objectName: BodySingleCut
1175
+ dimensionUnit: m
1176
+ pickguardOffset:
1177
+ fileUrl: /models/gallery/guitar.glb
1178
+ objectName: PickguardOffset
1179
+ dimensionUnit: m
1180
+ pickguardSingleCut:
1181
+ fileUrl: /models/gallery/guitar.glb
1182
+ objectName: PickguardSingleCut
1183
+ dimensionUnit: m
1184
+ neck:
1185
+ fileUrl: /models/gallery/guitar.glb
1186
+ objectName: Neck
1187
+ dimensionUnit: m
1188
+ headstock:
1189
+ fileUrl: /models/gallery/guitar.glb
1190
+ objectName: Headstock
1191
+ dimensionUnit: m
1192
+ fretboard:
1193
+ fileUrl: /models/gallery/guitar.glb
1194
+ objectName: Fretboard
1195
+ dimensionUnit: m
1196
+ frets:
1197
+ fileUrl: /models/gallery/guitar.glb
1198
+ objectName: Frets
1199
+ dimensionUnit: m
1200
+ inlays:
1201
+ fileUrl: /models/gallery/guitar.glb
1202
+ objectName: Inlays
1203
+ dimensionUnit: m
1204
+ nut:
1205
+ fileUrl: /models/gallery/guitar.glb
1206
+ objectName: Nut
1207
+ dimensionUnit: m
1208
+ strings:
1209
+ fileUrl: /models/gallery/guitar.glb
1210
+ objectName: Strings
1211
+ dimensionUnit: m
1212
+ tuners:
1213
+ fileUrl: /models/gallery/guitar.glb
1214
+ objectName: Tuners
1215
+ dimensionUnit: m
1216
+ pickupsSingleCoil:
1217
+ fileUrl: /models/gallery/guitar.glb
1218
+ objectName: PickupsSingleCoil
1219
+ dimensionUnit: m
1220
+ polesSingleCoil:
1221
+ fileUrl: /models/gallery/guitar.glb
1222
+ objectName: PolesSingleCoil
1223
+ dimensionUnit: m
1224
+ pickupsHumbucker:
1225
+ fileUrl: /models/gallery/guitar.glb
1226
+ objectName: PickupsHumbucker
1227
+ dimensionUnit: m
1228
+ polesHumbucker:
1229
+ fileUrl: /models/gallery/guitar.glb
1230
+ objectName: PolesHumbucker
1231
+ dimensionUnit: m
1232
+ pickupsP90:
1233
+ fileUrl: /models/gallery/guitar.glb
1234
+ objectName: PickupsP90
1235
+ dimensionUnit: m
1236
+ polesP90:
1237
+ fileUrl: /models/gallery/guitar.glb
1238
+ objectName: PolesP90
1239
+ dimensionUnit: m
1240
+ bridgeTremolo:
1241
+ fileUrl: /models/gallery/guitar.glb
1242
+ objectName: BridgeTremolo
1243
+ dimensionUnit: m
1244
+ bridgeTuneOMatic:
1245
+ fileUrl: /models/gallery/guitar.glb
1246
+ objectName: BridgeTuneOMatic
1247
+ dimensionUnit: m
1248
+ knobsOffset:
1249
+ fileUrl: /models/gallery/guitar.glb
1250
+ objectName: KnobsOffset
1251
+ dimensionUnit: m
1252
+ knobsSingleCut:
1253
+ fileUrl: /models/gallery/guitar.glb
1254
+ objectName: KnobsSingleCut
1255
+ dimensionUnit: m
1256
+ switchOffset:
1257
+ fileUrl: /models/gallery/guitar.glb
1258
+ objectName: SwitchOffset
1259
+ dimensionUnit: m
1260
+ switchSingleCut:
1261
+ fileUrl: /models/gallery/guitar.glb
1262
+ objectName: SwitchSingleCut
1263
+ dimensionUnit: m
1264
+
1265
+ # Each body brings its own pickguard, bridge, knobs and switch; the pickups show the chosen set
1266
+ sceneEntityObjectProperties:
1267
+ bodyOffset:
1268
+ visible:
1269
+ $eval: self.bodyShape === 'Offset'
1270
+ bodySingleCut:
1271
+ visible:
1272
+ $eval: self.bodyShape === 'SingleCut'
1273
+ pickguardOffset:
1274
+ visible:
1275
+ $eval: self.bodyShape === 'Offset' && self.pickguard !== 'None'
1276
+ pickguardSingleCut:
1277
+ visible:
1278
+ $eval: self.bodyShape === 'SingleCut' && self.pickguard !== 'None'
1279
+ bridgeTremolo:
1280
+ visible:
1281
+ $eval: self.bodyShape === 'Offset'
1282
+ bridgeTuneOMatic:
1283
+ visible:
1284
+ $eval: self.bodyShape === 'SingleCut'
1285
+ knobsOffset:
1286
+ visible:
1287
+ $eval: self.bodyShape === 'Offset'
1288
+ knobsSingleCut:
1289
+ visible:
1290
+ $eval: self.bodyShape === 'SingleCut'
1291
+ switchOffset:
1292
+ visible:
1293
+ $eval: self.bodyShape === 'Offset'
1294
+ switchSingleCut:
1295
+ visible:
1296
+ $eval: self.bodyShape === 'SingleCut'
1297
+ pickupsSingleCoil:
1298
+ visible:
1299
+ $eval: self.pickups === 'SingleCoils'
1300
+ polesSingleCoil:
1301
+ visible:
1302
+ $eval: self.pickups === 'SingleCoils'
1303
+ pickupsHumbucker:
1304
+ visible:
1305
+ $eval: self.pickups === 'Humbuckers'
1306
+ polesHumbucker:
1307
+ visible:
1308
+ $eval: self.pickups === 'Humbuckers'
1309
+ pickupsP90:
1310
+ visible:
1311
+ $eval: self.pickups === 'P90s'
1312
+ polesP90:
1313
+ visible:
1314
+ $eval: self.pickups === 'P90s'
1315
+
1316
+ # The nitro finish: a physical material with a clearcoat on each body object; natural ash shows the grain
1317
+ sceneEntityObjectLocalMaterialOverride:
1318
+ bodyOffset:
1319
+ materialsToOverride:
1320
+ - Finish
1321
+ materialType: physical
1322
+ color:
1323
+ $read: $finishColor
1324
+ textureUrl:
1325
+ $read: $finishTexture
1326
+ rotation: 1.5708
1327
+ repeatX: 2.2
1328
+ repeatY: 2.2
1329
+ metalness:
1330
+ $read: $finishMetalness
1331
+ roughness:
1332
+ $read: $finishRoughness
1333
+ clearcoat: 1
1334
+ clearcoatRoughness: 0.04
1335
+ bodySingleCut:
1336
+ materialsToOverride:
1337
+ - Finish
1338
+ materialType: physical
1339
+ color:
1340
+ $read: $finishColor
1341
+ textureUrl:
1342
+ $read: $finishTexture
1343
+ rotation: 1.5708
1344
+ repeatX: 2.2
1345
+ repeatY: 2.2
1346
+ metalness:
1347
+ $read: $finishMetalness
1348
+ roughness:
1349
+ $read: $finishRoughness
1350
+ clearcoat: 1
1351
+ clearcoatRoughness: 0.04
1352
+
1353
+ # The materials many parts share change for every object that carries them
1354
+ sceneEntityLocalMaterialOverride:
1355
+ pickguard:
1356
+ materialsToOverride:
1357
+ - Pickguard
1358
+ materialType: physical
1359
+ color:
1360
+ $read: $pickguardColor
1361
+ roughness:
1362
+ $read: $pickguardRoughness
1363
+ metalness: 0
1364
+ iridescence:
1365
+ $read: $pickguardIridescence
1366
+ iridescenceIor: 1.4
1367
+ clearcoat:
1368
+ $read: $pickguardIridescence
1369
+ neck:
1370
+ materialsToOverride:
1371
+ - Neck
1372
+ materialType: physical
1373
+ color:
1374
+ $read: $neckColor
1375
+ textureUrl:
1376
+ $read: $neckTexture
1377
+ repeatX: 5
1378
+ repeatY: 1
1379
+ offsetX: 0.5
1380
+ metalness: 0
1381
+ roughness: 0.42
1382
+ clearcoat: 0.6
1383
+ clearcoatRoughness: 0.2
1384
+ fretboard:
1385
+ materialsToOverride:
1386
+ - Fretboard
1387
+ color:
1388
+ $read: $fretboardColor
1389
+ textureUrl:
1390
+ $read: $fretboardTexture
1391
+ repeatX: 5
1392
+ repeatY: 1
1393
+ offsetX: 0.5
1394
+ metalness: 0
1395
+ roughness: 0.62
1396
+ inlays:
1397
+ materialsToOverride:
1398
+ - Inlay
1399
+ color:
1400
+ $read: $inlayColor
1401
+ metalness: 0
1402
+ roughness: 0.3
1403
+ hardware:
1404
+ materialsToOverride:
1405
+ - Hardware
1406
+ color:
1407
+ $read: $hardwareColor
1408
+ metalness:
1409
+ $read: $hardwareMetalness
1410
+ roughness:
1411
+ $read: $hardwareRoughness
1412
+ pickupCover:
1413
+ materialsToOverride:
1414
+ - PickupCover
1415
+ color:
1416
+ $read: $pickupCoverColor
1417
+ metalness: 0
1418
+ roughness: 0.45
1419
+
1420
+ # The signature in script on the headstock's face, along its length; the text is the customer's own
1421
+ sceneEntityObjectTextDecal:
1422
+ headstock:
1423
+ text:
1424
+ $read: signature
1425
+ font: SignPainter-HouseScript
1426
+ preloadFonts:
1427
+ - SignPainter-HouseScript
1428
+ fontSize: auto
1429
+ textColor:
1430
+ $read: $signatureColor
1431
+ textCanvasHeight: 256
1432
+ positionX: 0.002
1433
+ positionY: 0.76
1434
+ positionZ: 0.004
1435
+ rotationX: 0
1436
+ rotationY: 0
1437
+ rotationZ: 1.5708
1438
+ sizeX: 0.13
1439
+ sizeY: 0.03
1440
+ sizeZ: 0.02
1441
+ roughness: 0.4
1442
+ metalness: 0.2
1443
+ bumpScale: 0.4
1444
+ invertedBump: true
1445
+ ```
1446
+
1447
+ ### ./src/\_fields/ChoiceField.tsx
1448
+
1449
+ ```tsx
1450
+ import { Box, ButtonBase, Typography } from '@mui/material'
1451
+ import type { KeyboardEvent } from 'react'
1452
+
1453
+ import { FieldShell } from './FieldShell.js'
1454
+ import { NIGHTJAR } from './tokens.js'
1455
+
1456
+ // The body outlines of the model, drawn small, with a stub of the neck
1457
+ const BODY_PATHS: Readonly<Record<string, string>> = {
1458
+ Offset:
1459
+ 'M20 52L23.2 51.8L26.4 51.6L29.6 50.8L32.5 49.5L35.1 47.6L36.9 45L38.1 41.9L38.6 38.8L38.5 35.6L37.8 32.4L36.8 29.4L35.5 26.4L34.6 23.3L34.3 20.1L34.8 17L35 13.7L34.4 10.6L32.8 7.8L30.1 6.2L27.4 7.7L25.5 10.3L22.6 10.7L19.4 10.1L16.4 8.9L15.2 6L14.1 3L11.7 1L8.7 2L7 4.7L6.1 7.8L5.4 10.9L5.3 14.1L5.8 17.3L5.5 20.5L4.8 23.6L3.8 26.7L2.8 29.8L2.2 33L1.8 36.2L1.8 39.4L2.1 42.6L3.1 45.7L4.9 48.3L7.3 50.4L10.4 51.5L13.6 52L16.8 52.1Z',
1460
+ SingleCut:
1461
+ 'M20 51L22.9 50.9L25.7 50.7L28.5 50.2L31.2 49.1L33.6 47.5L35.4 45.3L36.5 42.7L37.1 39.9L37.3 37L36.9 34.2L36 31.5L34.5 29L33.2 26.5L32.2 23.8L32.2 21L31.9 18.1L31 15.4L29.6 12.9L27.7 10.8L25.1 11.6L23.9 14.2L23 12.2L23.1 9.3L21.6 7.1L18.9 6.2L16 6L13.2 6.5L10.6 7.7L8.5 9.6L7.3 12.2L7 15L7.6 17.8L7.3 20.7L6.5 23.4L5.6 26.2L4.6 28.8L3.8 31.6L3.3 34.4L3.1 37.3L3.2 40.1L3.7 43L4.7 45.6L6.4 47.9L8.8 49.5L11.4 50.6L14.3 51L17.1 51.1Z',
1462
+ }
1463
+
1464
+ // Two pickups as seen from the front: thin single coils, double humbuckers, wide P-90s
1465
+ const PICKUP_SHAPES: Readonly<Record<string, readonly { x: number; y: number; width: number; height: number }[]>> = {
1466
+ SingleCoils: [
1467
+ { x: 8, y: 13, width: 24, height: 5 },
1468
+ { x: 8, y: 30, width: 24, height: 5 },
1469
+ ],
1470
+ Humbuckers: [
1471
+ { x: 9, y: 9, width: 22, height: 5 },
1472
+ { x: 9, y: 15, width: 22, height: 5 },
1473
+ { x: 9, y: 28, width: 22, height: 5 },
1474
+ { x: 9, y: 34, width: 22, height: 5 },
1475
+ ],
1476
+ P90s: [
1477
+ { x: 6, y: 10, width: 28, height: 10 },
1478
+ { x: 6, y: 28, width: 28, height: 10 },
1479
+ ],
1480
+ }
1481
+
1482
+ // Swatches as CSS backgrounds: wood with a hint of grain, metal with a highlight, the pearloid's shimmer
1483
+ const WOOD_GRAIN = 'repeating-linear-gradient(100deg, rgba(0, 0, 0, 0.08) 0 2px, transparent 2px 6px)'
1484
+
1485
+ const SWATCHES: Readonly<Record<string, Readonly<Record<string, string>>>> = {
1486
+ pickguard: {
1487
+ Parchment: '#ece2cb',
1488
+ Black: 'radial-gradient(circle at 30% 30%, #3a3a40, #0c0c0e 70%)',
1489
+ Pearloid: 'conic-gradient(from 40deg, #f6f1ea, #e6eef6, #f4e6f0, #eef6ea, #f6f1ea)',
1490
+ None: 'transparent',
1491
+ },
1492
+ neckWood: {
1493
+ Maple: `${WOOD_GRAIN}, #ecd3a3`,
1494
+ RoastedMaple: `${WOOD_GRAIN}, #b9804a`,
1495
+ Mahogany: `${WOOD_GRAIN}, #7b3f28`,
1496
+ },
1497
+ fretboard: {
1498
+ Maple: `${WOOD_GRAIN}, #ecd3a3`,
1499
+ Rosewood: `${WOOD_GRAIN}, #4a2a1c`,
1500
+ Ebony: `${WOOD_GRAIN}, #1d1916`,
1501
+ },
1502
+ hardware: {
1503
+ Chrome: 'linear-gradient(135deg, #ffffff, #9aa1a8 55%, #e4e7ea)',
1504
+ Gold: 'linear-gradient(135deg, #fff1c4, #b8862e 55%, #f3d38a)',
1505
+ Black: 'linear-gradient(135deg, #6a6a70, #121214 55%, #3a3a40)',
1506
+ },
1507
+ }
1508
+
1509
+ interface OptionArtProps {
1510
+ readonly instanceName: string
1511
+ readonly value: string
1512
+ readonly size: number
1513
+ }
1514
+
1515
+ // The small picture beside an option: a silhouette for the body, the pickup layout, or a swatch of the material
1516
+ const OptionArt = ({ instanceName, value, size }: OptionArtProps) => {
1517
+ const bodyPath = instanceName === 'bodyShape' ? BODY_PATHS[value] : undefined
1518
+ const pickups = instanceName === 'pickups' ? PICKUP_SHAPES[value] : undefined
1519
+ const swatch = SWATCHES[instanceName]?.[value]
1520
+
1521
+ if (bodyPath) {
1522
+ return (
1523
+ <Box component="svg" viewBox="0 0 40 54" aria-hidden sx={{ inlineSize: size, blockSize: size, flex: 'none' }}>
1524
+ <rect x={17.8} y={0} width={4.4} height={12} rx={1} fill={NIGHTJAR.faint} />
1525
+ <path d={bodyPath} fill={NIGHTJAR.cream} />
1526
+ </Box>
1527
+ )
1528
+ }
1529
+
1530
+ if (pickups) {
1531
+ return (
1532
+ <Box component="svg" viewBox="0 0 40 48" aria-hidden sx={{ inlineSize: size, blockSize: size, flex: 'none' }}>
1533
+ {pickups.map(shape => (
1534
+ <rect key={`${shape.x}-${shape.y}`} {...shape} rx={2.5} fill={NIGHTJAR.cream} />
1535
+ ))}
1536
+ </Box>
1537
+ )
1538
+ }
1539
+
1540
+ if (swatch === undefined) {
1541
+ return null
1542
+ }
1543
+
1544
+ return (
1545
+ <Box
1546
+ component="span"
1547
+ aria-hidden
1548
+ sx={{
1549
+ inlineSize: size,
1550
+ blockSize: size,
1551
+ flex: 'none',
1552
+ borderRadius: '50%',
1553
+ background: swatch,
1554
+ boxShadow: `inset 0 0 0 1px ${NIGHTJAR.line}`,
1555
+ // "None" is an empty, dashed ring
1556
+ ...(value === 'None' && {
1557
+ border: `1.5px dashed ${NIGHTJAR.faint}`,
1558
+ boxShadow: 'none',
1559
+ }),
1560
+ }}
1561
+ />
1562
+ )
1563
+ }
1564
+
1565
+ // Arrow keys move the selection between the options, as in a native radio group
1566
+ const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
1567
+ ArrowRight: 1,
1568
+ ArrowDown: 1,
1569
+ ArrowLeft: -1,
1570
+ ArrowUp: -1,
1571
+ }
1572
+
1573
+ interface ChoiceFieldProps {
1574
+ readonly instanceName: string
1575
+ readonly label: string
1576
+ readonly warningTypes: readonly string[]
1577
+ /** Chips in one scrolling row, for a phone; cards in a grid otherwise */
1578
+ readonly compact: boolean
1579
+ }
1580
+
1581
+ // One `attributeSelection` of the spec: cards with a picture, a title and a description, or chips on a phone. An
1582
+ // option a `fixable` invariant rules out is faded with the reason below; it stays clickable, so the change is tried
1583
+ // and rejected.
1584
+ export const ChoiceField = ({ instanceName, label, warningTypes, compact }: ChoiceFieldProps) => {
1585
+ const resolve = phx.frontend.useResolveLocalizable()
1586
+ const guitarId = phx.frontend.useRootEntityId()
1587
+ const { value, options } = phx.frontend.useAttributeSelection(guitarId, instanceName)
1588
+ const selected = options.find(option => option.value === value)
1589
+ const ruledOut = options.find(option => option.disabled && option.hint)?.hint
1590
+
1591
+ const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
1592
+ const step = ARROW_STEPS[event.key]
1593
+
1594
+ if (step === undefined) {
1595
+ return
1596
+ }
1597
+
1598
+ event.preventDefault()
1599
+ const next = (index + step + options.length) % options.length
1600
+ options[next]?.onSelect()
1601
+ event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
1602
+ }
1603
+
1604
+ return (
1605
+ <FieldShell
1606
+ id={`nightjar-${instanceName}`}
1607
+ label={label}
1608
+ aside={
1609
+ compact && selected ? (
1610
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.cream }}>
1611
+ {resolve(selected.title)}
1612
+ </Typography>
1613
+ ) : undefined
1614
+ }
1615
+ hint={ruledOut}
1616
+ warningTypes={warningTypes}
1617
+ compact={compact}
1618
+ >
1619
+ <Box
1620
+ component="div"
1621
+ role="radiogroup"
1622
+ aria-labelledby={`nightjar-${instanceName}-label`}
1623
+ sx={
1624
+ compact
1625
+ ? {
1626
+ display: 'flex',
1627
+ gap: 1,
1628
+ marginInline: -2,
1629
+ paddingInline: 2,
1630
+ paddingBlockEnd: 0.5,
1631
+ overflowX: 'auto',
1632
+ scrollbarWidth: 'none',
1633
+ }
1634
+ : { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: 1 }
1635
+ }
1636
+ >
1637
+ {options.map((option, index) => {
1638
+ const isSelected = option.value === value
1639
+ const description = resolve(option.description)
1640
+
1641
+ return (
1642
+ <ButtonBase
1643
+ key={String(option.value)}
1644
+ role="radio"
1645
+ aria-checked={isSelected}
1646
+ aria-disabled={option.disabled}
1647
+ tabIndex={isSelected ? 0 : -1}
1648
+ onClick={option.onSelect}
1649
+ onKeyDown={event => handleKeyDown(event, index)}
1650
+ sx={{
1651
+ display: 'flex',
1652
+ alignItems: 'center',
1653
+ justifyContent: 'flex-start',
1654
+ gap: compact ? 1 : 1.5,
1655
+ flex: compact ? 'none' : undefined,
1656
+ minBlockSize: compact ? 44 : 64,
1657
+ paddingInline: compact ? 1.5 : 1.5,
1658
+ paddingBlock: compact ? 0.75 : 1.25,
1659
+ textAlign: 'start',
1660
+ borderRadius: compact ? 999 : 1.5,
1661
+ border: `1px solid ${isSelected ? NIGHTJAR.amber : NIGHTJAR.line}`,
1662
+ bgcolor: isSelected ? NIGHTJAR.amberTint : NIGHTJAR.raised,
1663
+ color: NIGHTJAR.cream,
1664
+ opacity: option.disabled ? 0.45 : 1,
1665
+ transition: 'border-color 160ms, background-color 160ms',
1666
+ '&:hover': { bgcolor: isSelected ? NIGHTJAR.amberTint : NIGHTJAR.raisedHover },
1667
+ '&.Mui-focusVisible': { outline: `2px solid ${NIGHTJAR.amber}`, outlineOffset: 2 },
1668
+ }}
1669
+ >
1670
+ <OptionArt instanceName={instanceName} value={String(option.value)} size={compact ? 22 : 36} />
1671
+ <Box component="span" sx={{ display: 'grid', gap: 0.25 }}>
1672
+ <Typography
1673
+ component="span"
1674
+ sx={{
1675
+ fontFamily: NIGHTJAR.font,
1676
+ fontSize: compact ? 14 : 15,
1677
+ fontWeight: 500,
1678
+ whiteSpace: 'nowrap',
1679
+ color: NIGHTJAR.cream,
1680
+ }}
1681
+ >
1682
+ {resolve(option.title)}
1683
+ </Typography>
1684
+ {!compact && description && (
1685
+ <Typography
1686
+ component="span"
1687
+ sx={{ fontFamily: NIGHTJAR.font, fontSize: 13, lineHeight: 1.3, color: NIGHTJAR.muted }}
1688
+ >
1689
+ {description}
1690
+ </Typography>
1691
+ )}
1692
+ </Box>
1693
+ </ButtonBase>
1694
+ )
1695
+ })}
1696
+ </Box>
1697
+ </FieldShell>
1698
+ )
1699
+ }
1700
+ ```
1701
+
1702
+ ### ./src/\_fields/FieldShell.tsx
1703
+
1704
+ ```tsx
1705
+ import { Box, Typography } from '@mui/material'
1706
+ import type { ReactNode } from 'react'
1707
+
1708
+ import { LABEL_SX, NIGHTJAR } from './tokens.js'
1709
+ import { useWarnings } from './use-warnings.js'
1710
+
1711
+ interface FieldShellProps {
1712
+ readonly id: string
1713
+ readonly label: string
1714
+ /** Beside the label: the chosen option's name, or a counter */
1715
+ readonly aside?: ReactNode
1716
+ /** Below the field: why an option is ruled out, or what the customer should know */
1717
+ readonly hint?: string | undefined
1718
+ readonly warningTypes: readonly string[]
1719
+ readonly compact: boolean
1720
+ readonly children: ReactNode
1721
+ }
1722
+
1723
+ // A labelled field. A `warning` violation the field explains is shown below it while it holds; it is the same text
1724
+ // the summary lists, and the stepper marks the step until it is resolved.
1725
+ export const FieldShell = ({ id, label, aside, hint, warningTypes, compact, children }: FieldShellProps) => {
1726
+ const warning = useWarnings().find(candidate => warningTypes.includes(candidate.type))
1727
+
1728
+ return (
1729
+ <Box component="section" aria-labelledby={`${id}-label`} sx={{ display: 'grid', gap: compact ? 1 : 1.5 }}>
1730
+ <Box component="div" sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 2 }}>
1731
+ <Typography component="h3" id={`${id}-label`} sx={LABEL_SX}>
1732
+ {label}
1733
+ </Typography>
1734
+ {aside}
1735
+ </Box>
1736
+ {children}
1737
+ {hint && <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 13, color: NIGHTJAR.muted }}>{hint}</Typography>}
1738
+ {warning && (
1739
+ <Typography
1740
+ role="status"
1741
+ sx={{
1742
+ paddingInline: 1.5,
1743
+ paddingBlock: 1,
1744
+ borderInlineStart: `3px solid ${NIGHTJAR.amber}`,
1745
+ bgcolor: NIGHTJAR.amberTint,
1746
+ fontFamily: NIGHTJAR.font,
1747
+ fontSize: 14,
1748
+ lineHeight: 1.4,
1749
+ color: NIGHTJAR.cream,
1750
+ }}
1751
+ >
1752
+ {warning.text}
1753
+ </Typography>
1754
+ )}
1755
+ </Box>
1756
+ )
1757
+ }
1758
+ ```
1759
+
1760
+ ### ./src/\_fields/FinishField.tsx
1761
+
1762
+ ```tsx
1763
+ import { Box, ButtonBase, Tooltip, Typography } from '@mui/material'
1764
+ import type { KeyboardEvent } from 'react'
1765
+
1766
+ import { FieldShell } from './FieldShell.js'
1767
+ import { NIGHTJAR } from './tokens.js'
1768
+
1769
+ const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
1770
+ ArrowRight: 1,
1771
+ ArrowDown: 1,
1772
+ ArrowLeft: -1,
1773
+ ArrowUp: -1,
1774
+ }
1775
+
1776
+ // A highlight over each swatch, so the paint reads as a glossy finish
1777
+ const GLOSS = 'radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0) 42%)'
1778
+
1779
+ interface FinishFieldProps {
1780
+ readonly instanceName: string
1781
+ readonly label: string
1782
+ readonly warningTypes: readonly string[]
1783
+ readonly compact: boolean
1784
+ }
1785
+
1786
+ // The spec's `materialSwatch`: round swatches in the colours the spec gives each finish, the chosen one's name
1787
+ // beside the label. `useSwatchSelection` takes the `fixable` violation types to ignore as its third argument.
1788
+ export const FinishField = ({ instanceName, label, warningTypes, compact }: FinishFieldProps) => {
1789
+ const resolve = phx.frontend.useResolveLocalizable()
1790
+ const guitarId = phx.frontend.useRootEntityId()
1791
+ const { options } = phx.frontend.useSwatchSelection(guitarId, instanceName, [])
1792
+ const selected = options.find(option => option.selected)
1793
+ const ruledOut = options.find(option => option.disabled && option.hint)?.hint
1794
+ const size = compact ? 40 : 48
1795
+
1796
+ const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
1797
+ const step = ARROW_STEPS[event.key]
1798
+
1799
+ if (step === undefined) {
1800
+ return
1801
+ }
1802
+
1803
+ event.preventDefault()
1804
+ const next = (index + step + options.length) % options.length
1805
+ options[next]?.onClick()
1806
+ event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
1807
+ }
1808
+
1809
+ return (
1810
+ <FieldShell
1811
+ id={`nightjar-${instanceName}`}
1812
+ label={label}
1813
+ aside={
1814
+ selected && (
1815
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.cream }}>
1816
+ {resolve(selected.swatchName)}
1817
+ </Typography>
1818
+ )
1819
+ }
1820
+ hint={ruledOut ?? undefined}
1821
+ warningTypes={warningTypes}
1822
+ compact={compact}
1823
+ >
1824
+ <Box
1825
+ component="div"
1826
+ role="radiogroup"
1827
+ aria-labelledby={`nightjar-${instanceName}-label`}
1828
+ sx={{ display: 'flex', flexWrap: 'wrap', gap: compact ? 1.5 : 2 }}
1829
+ >
1830
+ {options.map((option, index) => {
1831
+ const name = resolve(option.swatchName)
1832
+
1833
+ return (
1834
+ <Tooltip key={option.swatchKey} title={name}>
1835
+ <ButtonBase
1836
+ role="radio"
1837
+ aria-label={name}
1838
+ aria-checked={option.selected}
1839
+ aria-disabled={option.disabled}
1840
+ tabIndex={option.selected ? 0 : -1}
1841
+ onClick={option.onClick}
1842
+ onKeyDown={event => handleKeyDown(event, index)}
1843
+ sx={{
1844
+ inlineSize: size,
1845
+ blockSize: size,
1846
+ borderRadius: '50%',
1847
+ background: `${GLOSS}, ${option.color}`,
1848
+ // The ring sits outside the swatch, in the panel's colour, so the swatch keeps its full size
1849
+ boxShadow: option.selected
1850
+ ? `0 0 0 3px ${NIGHTJAR.ink}, 0 0 0 5px ${NIGHTJAR.amber}`
1851
+ : `inset 0 0 0 1px ${NIGHTJAR.line}`,
1852
+ opacity: option.disabled ? 0.4 : 1,
1853
+ transition: 'box-shadow 160ms, transform 160ms',
1854
+ '&:hover': { transform: 'scale(1.06)' },
1855
+ '&.Mui-focusVisible': { outline: `2px solid ${NIGHTJAR.cream}`, outlineOffset: 6 },
1856
+ }}
1857
+ />
1858
+ </Tooltip>
1859
+ )
1860
+ })}
1861
+ </Box>
1862
+ </FieldShell>
1863
+ )
1864
+ }
1865
+ ```
1866
+
1867
+ ### ./src/\_fields/SignatureField.tsx
1868
+
1869
+ ```tsx
1870
+ import { Box, InputBase, Typography } from '@mui/material'
1871
+
1872
+ import { FieldShell } from './FieldShell.js'
1873
+ import { NIGHTJAR } from './tokens.js'
1874
+
1875
+ interface SignatureFieldProps {
1876
+ readonly instanceName: string
1877
+ readonly label: string
1878
+ readonly warningTypes: readonly string[]
1879
+ readonly compact: boolean
1880
+ }
1881
+
1882
+ // The spec's `textValue`, which the spec prints on the headstock with a text decal. Typing stages each value, so the
1883
+ // headstock follows every key; leaving the field or pressing Enter commits it. A value the `fixable` length rule
1884
+ // rejects shows the rule's message as the hook's `hints`, and leaving the field rolls back to the last value that
1885
+ // fitted. The counter reads the limit from the spec's `$signatureLimit` expression, which the rule checks too.
1886
+ export const SignatureField = ({ instanceName, label, warningTypes, compact }: SignatureFieldProps) => {
1887
+ const guitarId = phx.frontend.useRootEntityId()
1888
+ const guitar = phx.frontend.useRootEntityState()
1889
+ const signature = phx.frontend.useTextValue(guitarId, instanceName)
1890
+ const limit = guitar.$signatureLimit
1891
+
1892
+ return (
1893
+ <FieldShell
1894
+ id={`nightjar-${instanceName}`}
1895
+ label={label}
1896
+ aside={
1897
+ <Typography
1898
+ aria-live="polite"
1899
+ sx={{
1900
+ fontFamily: NIGHTJAR.font,
1901
+ fontSize: 13,
1902
+ fontVariantNumeric: 'tabular-nums',
1903
+ color: signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.muted,
1904
+ }}
1905
+ >
1906
+ {signature.value.length} / {limit}
1907
+ </Typography>
1908
+ }
1909
+ hint={
1910
+ compact ? undefined : 'In gold leaf on dark necks, in black on maple. Leave it empty for a plain headstock.'
1911
+ }
1912
+ warningTypes={warningTypes}
1913
+ compact={compact}
1914
+ >
1915
+ <Box
1916
+ component="div"
1917
+ sx={{
1918
+ display: 'grid',
1919
+ gap: 1,
1920
+ paddingInline: 2,
1921
+ paddingBlock: compact ? 1 : 1.5,
1922
+ borderRadius: 1.5,
1923
+ border: `1px solid ${signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.line}`,
1924
+ bgcolor: NIGHTJAR.raised,
1925
+ '&:focus-within': { borderColor: signature.hasErrors ? NIGHTJAR.alert : NIGHTJAR.amber },
1926
+ }}
1927
+ >
1928
+ <InputBase
1929
+ value={signature.value}
1930
+ placeholder="Your name"
1931
+ onChange={event => signature.update(event, event.target.value)}
1932
+ onBlur={signature.resolve}
1933
+ onKeyDown={event => {
1934
+ if (event.key === 'Enter') {
1935
+ signature.resolve()
1936
+ }
1937
+ }}
1938
+ inputProps={{
1939
+ 'aria-labelledby': `nightjar-${instanceName}-label`,
1940
+ 'aria-invalid': signature.hasErrors,
1941
+ 'aria-describedby': `nightjar-${instanceName}-error`,
1942
+ spellCheck: false,
1943
+ }}
1944
+ sx={{
1945
+ fontFamily: NIGHTJAR.script,
1946
+ fontSize: compact ? 30 : 38,
1947
+ lineHeight: 1.2,
1948
+ color: NIGHTJAR.amber,
1949
+ '& input::placeholder': { color: NIGHTJAR.faint, opacity: 1 },
1950
+ }}
1951
+ />
1952
+ </Box>
1953
+ <Typography
1954
+ id={`nightjar-${instanceName}-error`}
1955
+ role="alert"
1956
+ sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.alert, '&:empty': { display: 'none' } }}
1957
+ >
1958
+ {signature.hasErrors ? signature.hints : ''}
1959
+ </Typography>
1960
+ </FieldShell>
1961
+ )
1962
+ }
1963
+ ```
1964
+
1965
+ ### ./src/\_fields/StepFields.tsx
1966
+
1967
+ ```tsx
1968
+ import { Box } from '@mui/material'
1969
+
1970
+ import { ChoiceField } from './ChoiceField.js'
1971
+ import { FinishField } from './FinishField.js'
1972
+ import { SignatureField } from './SignatureField.js'
1973
+ import { fieldOf } from './fields.js'
1974
+ import { useActiveChoices } from './use-active-choices.js'
1975
+
1976
+ interface StepFieldsProps {
1977
+ readonly compact: boolean
1978
+ }
1979
+
1980
+ // The fields of the active step. The layout does not list which choice belongs to which step: it shows the choices
1981
+ // the spec leaves active, so moving a choice to another step is a change of its `$filter` in the spec.
1982
+ export const StepFields = ({ compact }: StepFieldsProps) => {
1983
+ const choices = useActiveChoices()
1984
+
1985
+ return (
1986
+ <Box component="div" sx={{ display: 'grid', gap: compact ? 2.5 : 4 }}>
1987
+ {choices.map(({ instanceName, kind }) => {
1988
+ const { label, warningTypes } = fieldOf(instanceName)
1989
+ const props = { instanceName, label, warningTypes, compact }
1990
+
1991
+ switch (kind) {
1992
+ case 'selection':
1993
+ return <ChoiceField key={instanceName} {...props} />
1994
+ case 'swatch':
1995
+ return <FinishField key={instanceName} {...props} />
1996
+ case 'text':
1997
+ return <SignatureField key={instanceName} {...props} />
1998
+ }
1999
+ })}
2000
+ </Box>
2001
+ )
2002
+ }
2003
+ ```
2004
+
2005
+ ### ./src/\_fields/SummaryRows.tsx
2006
+
2007
+ ```tsx
2008
+ import { Box, Typography } from '@mui/material'
2009
+ import type { ReactNode } from 'react'
2010
+
2011
+ import { fieldOf } from './fields.js'
2012
+ import { NIGHTJAR } from './tokens.js'
2013
+ import type { ActiveChoice } from './use-active-choices.js'
2014
+ import { useActiveChoices } from './use-active-choices.js'
2015
+
2016
+ interface RowProps {
2017
+ readonly label: string
2018
+ readonly children: ReactNode
2019
+ }
2020
+
2021
+ const Row = ({ label, children }: RowProps) => {
2022
+ return (
2023
+ <Box
2024
+ component="div"
2025
+ sx={{
2026
+ display: 'flex',
2027
+ alignItems: 'baseline',
2028
+ justifyContent: 'space-between',
2029
+ gap: 2,
2030
+ paddingBlock: 1.1,
2031
+ borderBlockEnd: `1px solid ${NIGHTJAR.line}`,
2032
+ }}
2033
+ >
2034
+ <Typography component="dt" sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.muted }}>
2035
+ {label}
2036
+ </Typography>
2037
+ <Typography
2038
+ component="dd"
2039
+ sx={{
2040
+ display: 'flex',
2041
+ alignItems: 'center',
2042
+ gap: 1,
2043
+ margin: 0,
2044
+ fontFamily: NIGHTJAR.font,
2045
+ fontSize: 15,
2046
+ fontWeight: 500,
2047
+ textAlign: 'end',
2048
+ color: NIGHTJAR.cream,
2049
+ }}
2050
+ >
2051
+ {children}
2052
+ </Typography>
2053
+ </Box>
2054
+ )
2055
+ }
2056
+
2057
+ interface ChoiceRowProps {
2058
+ readonly instanceName: string
2059
+ readonly label: string
2060
+ }
2061
+
2062
+ // Each row reads its choice with the same control hook the step uses: the option's title, the swatch's name and
2063
+ // colour, the text as written
2064
+ const SelectionRow = ({ instanceName, label }: ChoiceRowProps) => {
2065
+ const resolve = phx.frontend.useResolveLocalizable()
2066
+ const guitarId = phx.frontend.useRootEntityId()
2067
+ const { value, options } = phx.frontend.useAttributeSelection(guitarId, instanceName)
2068
+ const selected = options.find(option => option.value === value)
2069
+
2070
+ return <Row label={label}>{selected ? resolve(selected.title) : '—'}</Row>
2071
+ }
2072
+
2073
+ const SwatchRow = ({ instanceName, label }: ChoiceRowProps) => {
2074
+ const resolve = phx.frontend.useResolveLocalizable()
2075
+ const guitarId = phx.frontend.useRootEntityId()
2076
+ const { options } = phx.frontend.useSwatchSelection(guitarId, instanceName, [])
2077
+ const selected = options.find(option => option.selected)
2078
+
2079
+ return (
2080
+ <Row label={label}>
2081
+ {selected && (
2082
+ <Box
2083
+ component="span"
2084
+ aria-hidden
2085
+ sx={{ inlineSize: 12, blockSize: 12, borderRadius: '50%', bgcolor: selected.color }}
2086
+ />
2087
+ )}
2088
+ {selected ? resolve(selected.swatchName) : '—'}
2089
+ </Row>
2090
+ )
2091
+ }
2092
+
2093
+ const TextRow = ({ instanceName, label }: ChoiceRowProps) => {
2094
+ const guitarId = phx.frontend.useRootEntityId()
2095
+ const { value } = phx.frontend.useTextValue(guitarId, instanceName)
2096
+
2097
+ return (
2098
+ <Row label={label}>
2099
+ {value.trim() === '' ? (
2100
+ 'None'
2101
+ ) : (
2102
+ <Box component="span" sx={{ fontFamily: NIGHTJAR.script, fontSize: 24, lineHeight: 1, color: NIGHTJAR.amber }}>
2103
+ {value}
2104
+ </Box>
2105
+ )}
2106
+ </Row>
2107
+ )
2108
+ }
2109
+
2110
+ const ChoiceRow = ({ instanceName, kind }: ActiveChoice) => {
2111
+ const { label } = fieldOf(instanceName)
2112
+
2113
+ switch (kind) {
2114
+ case 'selection':
2115
+ return <SelectionRow instanceName={instanceName} label={label} />
2116
+ case 'swatch':
2117
+ return <SwatchRow instanceName={instanceName} label={label} />
2118
+ case 'text':
2119
+ return <TextRow instanceName={instanceName} label={label} />
2120
+ }
2121
+ }
2122
+
2123
+ // Every choice, read-only: the summary and the confirmation list the choices the spec leaves active in their states
2124
+ export const SummaryRows = () => {
2125
+ const choices = useActiveChoices()
2126
+
2127
+ return (
2128
+ <Box component="dl" sx={{ margin: 0 }}>
2129
+ {choices.map(choice => (
2130
+ <ChoiceRow key={choice.instanceName} {...choice} />
2131
+ ))}
2132
+ </Box>
2133
+ )
2134
+ }
2135
+ ```
2136
+
2137
+ ### ./src/\_fields/fields.ts
2138
+
2139
+ ```ts
2140
+ /** A choice of the spec as the layout presents it. */
2141
+ export interface Field {
2142
+ /** The spec component's instance name */
2143
+ readonly instanceName: string
2144
+ /** The spec's `attributeSelection`, `materialSwatch` and `textValue` carry no label; the layout names the choice */
2145
+ readonly label: string
2146
+ /** The `warning` violations the field explains when they hold */
2147
+ readonly warningTypes: readonly string[]
2148
+ }
2149
+
2150
+ // Every choice the layout knows, in the order it lists them. Which ones a step shows is the spec's business: a step
2151
+ // lists the choices whose `$filter` leaves them active in its navigation state.
2152
+ export const FIELDS: readonly Field[] = [
2153
+ { instanceName: 'bodyShape', label: 'Body shape', warningTypes: ['offsetHumbuckers'] },
2154
+ { instanceName: 'finish', label: 'Finish', warningTypes: [] },
2155
+ { instanceName: 'pickguard', label: 'Pickguard', warningTypes: [] },
2156
+ { instanceName: 'neckWood', label: 'Neck wood', warningTypes: [] },
2157
+ { instanceName: 'fretboard', label: 'Fretboard', warningTypes: [] },
2158
+ { instanceName: 'pickups', label: 'Pickups', warningTypes: ['offsetHumbuckers'] },
2159
+ { instanceName: 'hardware', label: 'Hardware', warningTypes: [] },
2160
+ { instanceName: 'signature', label: 'Headstock signature', warningTypes: [] },
2161
+ ]
2162
+
2163
+ /** The layout's field for an instance name, or a plain one for a choice the layout does not know. */
2164
+ export const fieldOf = (instanceName: string): Field => {
2165
+ return (
2166
+ FIELDS.find(field => field.instanceName === instanceName) ?? { instanceName, label: instanceName, warningTypes: [] }
2167
+ )
2168
+ }
2169
+
2170
+ /** Orders instance names as the layout lists them; unknown ones go last. */
2171
+ export const byFieldOrder = (a: string, b: string): number => {
2172
+ const rank = (instanceName: string) => {
2173
+ const index = FIELDS.findIndex(field => field.instanceName === instanceName)
2174
+
2175
+ return index === -1 ? FIELDS.length : index
2176
+ }
2177
+
2178
+ return rank(a) - rank(b)
2179
+ }
2180
+ ```
2181
+
2182
+ ### ./src/\_fields/tokens.ts
2183
+
2184
+ ```ts
2185
+ import { keyframes } from '@mui/material'
2186
+
2187
+ // Backstage at night: ink panels, amber light and cream type. Barlow is the brand's typeface, loaded by the page;
2188
+ // SignPainter only previews the headstock signature. The stacks fall back to system fonts where they are not loaded.
2189
+ export const NIGHTJAR = {
2190
+ ink: '#0e0f12',
2191
+ panel: 'rgba(14, 15, 18, 0.9)',
2192
+ raised: '#181a20',
2193
+ raisedHover: '#20232a',
2194
+ line: 'rgba(237, 230, 214, 0.14)',
2195
+ amber: '#f2a33a',
2196
+ amberHover: '#ffb54f',
2197
+ amberTint: 'rgba(242, 163, 58, 0.12)',
2198
+ cream: '#ede6d6',
2199
+ muted: 'rgba(237, 230, 214, 0.64)',
2200
+ faint: 'rgba(237, 230, 214, 0.38)',
2201
+ oxblood: '#7a1e2b',
2202
+ alert: '#f07a84',
2203
+ alertTint: 'rgba(122, 30, 43, 0.32)',
2204
+ font: "'Barlow', 'Helvetica Neue', Arial, sans-serif",
2205
+ script: "'SignPainter-HouseScript', 'Brush Script MT', cursive",
2206
+ } as const
2207
+
2208
+ // Bold capitals with wide tracking, for headings and the stepper
2209
+ export const HEADING_SX = {
2210
+ fontFamily: NIGHTJAR.font,
2211
+ fontWeight: 700,
2212
+ letterSpacing: '0.16em',
2213
+ textTransform: 'uppercase',
2214
+ color: NIGHTJAR.cream,
2215
+ } as const
2216
+
2217
+ // Small capitals, for field labels and overlines
2218
+ export const LABEL_SX = {
2219
+ fontFamily: NIGHTJAR.font,
2220
+ fontSize: 12,
2221
+ fontWeight: 500,
2222
+ letterSpacing: '0.2em',
2223
+ textTransform: 'uppercase',
2224
+ color: NIGHTJAR.muted,
2225
+ } as const
2226
+
2227
+ // A page or a step's content enters from the side it comes from, or a phone's sheet from below, fading in
2228
+ export const ENTER_FROM_END = keyframes`
2229
+ from { opacity: 0; transform: translateX(48px); }
2230
+ to { opacity: 1; transform: none; }
2231
+ `
2232
+
2233
+ export const ENTER_FROM_START = keyframes`
2234
+ from { opacity: 0; transform: translateX(-48px); }
2235
+ to { opacity: 1; transform: none; }
2236
+ `
2237
+
2238
+ export const ENTER_FROM_BELOW = keyframes`
2239
+ from { opacity: 0; transform: translateY(56px); }
2240
+ to { opacity: 1; transform: none; }
2241
+ `
2242
+ ```
2243
+
2244
+ ### ./src/\_fields/use-active-choices.ts
2245
+
2246
+ ```ts
2247
+ import { useMemo } from 'react'
2248
+
2249
+ import { attributeSelectionComponent, materialSwatchComponent, textValueComponent } from '@salsita-ai/sdk/domain'
2250
+
2251
+ import { byFieldOrder } from './fields.js'
2252
+
2253
+ export type ChoiceKind = 'selection' | 'swatch' | 'text'
2254
+
2255
+ /** A choice of the spec that is active now. */
2256
+ export interface ActiveChoice {
2257
+ readonly instanceName: string
2258
+ readonly kind: ChoiceKind
2259
+ }
2260
+
2261
+ // The guitar's choices whose spec component is active in the current navigation state, in the layout's order. A
2262
+ // `$filter` with `navigationStates` makes a component active only in those states, so on each step this is the step's
2263
+ // choices, and on the summary every choice. The control hooks throw for an inactive component; a field mounts only
2264
+ // for a choice listed here.
2265
+ export const useActiveChoices = (): readonly ActiveChoice[] => {
2266
+ const guitarId = phx.frontend.useRootEntityId()
2267
+ const selections = phx.frontend.useComponents({
2268
+ componentTypes: [attributeSelectionComponent],
2269
+ entityIds: [guitarId],
2270
+ })
2271
+ const swatches = phx.frontend.useComponents({ componentTypes: [materialSwatchComponent], entityIds: [guitarId] })
2272
+ const texts = phx.frontend.useComponents({ componentTypes: [textValueComponent], entityIds: [guitarId] })
2273
+
2274
+ return useMemo(() => {
2275
+ const choices = [
2276
+ ...(selections ?? []).map(
2277
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'selection' })
2278
+ ),
2279
+ ...(swatches ?? []).map(
2280
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'swatch' })
2281
+ ),
2282
+ ...(texts ?? []).map(
2283
+ ({ componentInstance }): ActiveChoice => ({ instanceName: componentInstance.instanceName, kind: 'text' })
2284
+ ),
2285
+ ]
2286
+
2287
+ return choices.sort((a, b) => byFieldOrder(a.instanceName, b.instanceName))
2288
+ }, [selections, swatches, texts])
2289
+ }
2290
+ ```
2291
+
2292
+ ### ./src/\_fields/use-warnings.ts
2293
+
2294
+ ```ts
2295
+ import { useMemo } from 'react'
2296
+
2297
+ export interface Warning {
2298
+ readonly type: string
2299
+ readonly text: string
2300
+ }
2301
+
2302
+ // The warnings of the committed configuration, with their text resolved. A `warning` invariant keeps the change, so
2303
+ // its violation stays until the customer resolves it, and ordering waits for that. `useFinalizeViolations` lists them.
2304
+ export const useWarnings = (): readonly Warning[] => {
2305
+ const { violations } = phx.frontend.useFinalizeViolations()
2306
+ const resolve = phx.frontend.useResolveLocalizable()
2307
+
2308
+ return useMemo(() => {
2309
+ return (violations ?? [])
2310
+ .filter(({ violation }) => violation.severity === 'warning')
2311
+ .map(({ violation }) => ({ type: violation.type, text: resolve(violation.message) ?? violation.type }))
2312
+ }, [violations, resolve])
2313
+ }
2314
+ ```
2315
+
2316
+ ### ./src/\_shared/DesktopPage.tsx
2317
+
2318
+ ```tsx
2319
+ import { Box, Typography } from '@mui/material'
2320
+ import type { AnimationEvent } from 'react'
2321
+
2322
+ import { ENTER_FROM_END, HEADING_SX, NIGHTJAR } from '../_fields/index.js'
2323
+ import { OrderBar } from './OrderBar.js'
2324
+ import { OrderConfirmation } from './OrderConfirmation.js'
2325
+ import { StepHeader } from './StepHeader.js'
2326
+ import { StepPanel } from './StepPanel.js'
2327
+ import { SummaryPanel } from './SummaryPanel.js'
2328
+ import type { OrderForm } from './order-form.js'
2329
+
2330
+ type Page = 'steps' | 'summary' | 'ordered'
2331
+
2332
+ // The summary needs room for the contact form, so its panel is wider
2333
+ const PANEL_WIDTHS: Readonly<Record<Page, number>> = { steps: 420, summary: 520, ordered: 420 }
2334
+
2335
+ const pageOf = (navigationState: string): Page => {
2336
+ return navigationState === 'ordered' ? 'ordered' : navigationState === 'summary' ? 'summary' : 'steps'
2337
+ }
2338
+
2339
+ // The brand's mark: a fret of amber over the name of the shop
2340
+ const Brand = () => {
2341
+ return (
2342
+ <Box component="div" sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 'none' }}>
2343
+ <Box component="span" aria-hidden sx={{ inlineSize: 4, blockSize: 28, bgcolor: NIGHTJAR.amber }} />
2344
+ <Box component="div" sx={{ display: 'grid' }}>
2345
+ <Typography sx={{ ...HEADING_SX, fontSize: 16, letterSpacing: '0.32em', lineHeight: 1.1 }}>
2346
+ Northfret
2347
+ </Typography>
2348
+ <Typography sx={{ ...HEADING_SX, fontSize: 10, letterSpacing: '0.36em', color: NIGHTJAR.muted }}>
2349
+ Custom shop
2350
+ </Typography>
2351
+ </Box>
2352
+ </Box>
2353
+ )
2354
+ }
2355
+
2356
+ interface DesktopPageProps {
2357
+ readonly form: OrderForm
2358
+ readonly reached: number
2359
+ }
2360
+
2361
+ // The desktop wizard: the stepper on top, the guitar in the free area on the left, the panel of the step on the
2362
+ // right and the bar with the price at the bottom.
2363
+ //
2364
+ // The summary's panel is wider than a step's, so the steps, the summary and the confirmation are three pages, each
2365
+ // with its own safe area, and each slides in when it opens. The safe area slides with its page: the first
2366
+ // measurement, taken when the page mounts, sees it shifted, and no resize follows when the slide ends. `update()`
2367
+ // from `useSceneSafeArea` measures again once the slide has settled, so the camera frames the guitar in the place the
2368
+ // page leaves for it.
2369
+ export const DesktopPage = ({ form, reached }: DesktopPageProps) => {
2370
+ const { containerRef, safeAreaRef, update } = phx.frontend.useSceneSafeArea()
2371
+ const navigationState = phx.frontend.useActiveNavigationState()
2372
+ const page = pageOf(navigationState)
2373
+
2374
+ // The step's own content slides too; only the page's slide moves the safe area
2375
+ const handleAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
2376
+ if (event.target === event.currentTarget) {
2377
+ update()
2378
+ }
2379
+ }
2380
+
2381
+ return (
2382
+ <Box
2383
+ component="div"
2384
+ ref={containerRef}
2385
+ sx={{
2386
+ position: 'absolute',
2387
+ inset: 0,
2388
+ display: 'grid',
2389
+ gridTemplateRows: 'auto minmax(0, 1fr) auto',
2390
+ fontFamily: NIGHTJAR.font,
2391
+ color: NIGHTJAR.cream,
2392
+ // The free area lies over the canvas; only the bars and the panel take the pointer
2393
+ pointerEvents: 'none',
2394
+ }}
2395
+ >
2396
+ <Box
2397
+ component="header"
2398
+ sx={{
2399
+ display: 'flex',
2400
+ alignItems: 'center',
2401
+ gap: 5,
2402
+ minBlockSize: 72,
2403
+ paddingInline: 4,
2404
+ bgcolor: NIGHTJAR.ink,
2405
+ borderBlockEnd: `1px solid ${NIGHTJAR.line}`,
2406
+ pointerEvents: 'auto',
2407
+ }}
2408
+ >
2409
+ <Brand />
2410
+ <StepHeader compact={false} reached={reached} />
2411
+ </Box>
2412
+
2413
+ <Box
2414
+ component="div"
2415
+ key={page}
2416
+ onAnimationEnd={handleAnimationEnd}
2417
+ sx={{
2418
+ display: 'grid',
2419
+ gridTemplateColumns: `minmax(0, 1fr) ${PANEL_WIDTHS[page]}px`,
2420
+ minBlockSize: 0,
2421
+ animation: `${ENTER_FROM_END} 480ms cubic-bezier(0.2, 0.7, 0.2, 1)`,
2422
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
2423
+ }}
2424
+ >
2425
+ <Box component="div" sx={{ position: 'relative', minInlineSize: 0 }}>
2426
+ {/* The camera fits the guitar into this box, so its inset is the room around the guitar */}
2427
+ <Box component="div" ref={safeAreaRef} sx={{ position: 'absolute', inset: '40px 72px 56px' }} />
2428
+ <Typography
2429
+ sx={{
2430
+ position: 'absolute',
2431
+ insetBlockEnd: 20,
2432
+ insetInline: 0,
2433
+ textAlign: 'center',
2434
+ fontSize: 12,
2435
+ letterSpacing: '0.14em',
2436
+ textTransform: 'uppercase',
2437
+ color: NIGHTJAR.faint,
2438
+ }}
2439
+ >
2440
+ Drag to turn the guitar
2441
+ </Typography>
2442
+ </Box>
2443
+ <Box
2444
+ component="aside"
2445
+ aria-label={page === 'steps' ? 'Step' : page === 'summary' ? 'Summary' : 'Order'}
2446
+ sx={{
2447
+ overflowY: 'auto',
2448
+ paddingInline: 4,
2449
+ paddingBlock: 4,
2450
+ bgcolor: NIGHTJAR.panel,
2451
+ borderInlineStart: `1px solid ${NIGHTJAR.line}`,
2452
+ pointerEvents: 'auto',
2453
+ }}
2454
+ >
2455
+ {page === 'steps' && <StepPanel compact={false} />}
2456
+ {page === 'summary' && <SummaryPanel compact={false} form={form} />}
2457
+ {page === 'ordered' && <OrderConfirmation compact={false} contact={form.contact} />}
2458
+ </Box>
2459
+ </Box>
2460
+
2461
+ <OrderBar compact={false} form={form} />
2462
+ </Box>
2463
+ )
2464
+ }
2465
+ ```
2466
+
2467
+ ### ./src/\_shared/OrderBar.tsx
2468
+
2469
+ ```tsx
2470
+ import { Box, Button, CircularProgress, IconButton, Tooltip, Typography } from '@mui/material'
2471
+
2472
+ import { FrontendAnalyticsFactory } from '@salsita-ai/sdk/configurator'
2473
+ import { detailComponent } from '@salsita-ai/sdk/domain'
2474
+
2475
+ import { HEADING_SX, LABEL_SX, NIGHTJAR } from '../_fields/index.js'
2476
+ import type { OrderForm } from './order-form.js'
2477
+
2478
+ const BACK_ICON = 'M15 5l-7 7 7 7'
2479
+
2480
+ interface OrderBarProps {
2481
+ /** The phone's bar: price and two buttons */
2482
+ readonly compact: boolean
2483
+ readonly form: OrderForm
2484
+ }
2485
+
2486
+ // The bar at the bottom: the guitar's name and price, Back, and the step's main action. On a step that is Next, on
2487
+ // the step before the summary **Review order**, on the summary **Place order**, and after the order **Design another**.
2488
+ export const OrderBar = ({ compact, form }: OrderBarProps) => {
2489
+ const resolve = phx.frontend.useResolveLocalizable()
2490
+ const detail = phx.frontend.useFirstRootComponent(detailComponent)
2491
+ const price = phx.frontend.useRootEntityPrice()
2492
+ const { steps, activeIndex, canGoPrevious, isLast, next, previous } = phx.frontend.useStepNavigation()
2493
+ const navigationState = phx.frontend.useActiveNavigationState()
2494
+ const navigate = phx.frontend.useNavigate()
2495
+ const analytics = phx.frontend.useAnalytics()
2496
+ const { primary, hints } = phx.frontend.useFinalizeActions()
2497
+ const { accessInfo } = phx.frontend.useAccessService()
2498
+
2499
+ const isOrdered = navigationState === 'ordered'
2500
+ const isPending = primary?.isPending ?? false
2501
+ const nextStep = steps[activeIndex + 1]
2502
+ const firstStep = steps[0]
2503
+
2504
+ // The step before the last one leads to the summary. Like the built-in summary button, it records
2505
+ // `SummaryButtonClicked`, which `next()` would not: it records the next-step button's event.
2506
+ const enterSummary = () => {
2507
+ if (nextStep === undefined) {
2508
+ return
2509
+ }
2510
+
2511
+ analytics.push(FrontendAnalyticsFactory.SummaryButtonClicked())
2512
+ navigate(nextStep.state)
2513
+ }
2514
+
2515
+ // A conflict or a missing detail blocks the order; the button stays clickable and shows what is missing instead
2516
+ const placeOrder = () => {
2517
+ if (primary === null) {
2518
+ return
2519
+ }
2520
+
2521
+ if (primary.disabled || !form.isComplete) {
2522
+ form.revealErrors()
2523
+ return
2524
+ }
2525
+
2526
+ void primary.run()
2527
+ }
2528
+
2529
+ const designAnother = () => {
2530
+ if (firstStep) {
2531
+ navigate(firstStep.state)
2532
+ }
2533
+ }
2534
+
2535
+ const action = isOrdered
2536
+ ? { label: 'Design another', onClick: designAnother, blocked: false }
2537
+ : isLast
2538
+ ? { label: primary?.label ?? 'Place order', onClick: placeOrder, blocked: primary?.disabled ?? true }
2539
+ : nextStep?.index === steps.length - 1
2540
+ ? { label: 'Review order', onClick: enterSummary, blocked: false }
2541
+ : { label: compact ? 'Next' : `Next: ${nextStep?.label ?? ''}`, onClick: next, blocked: false }
2542
+ const showAction = !(isLast && !isOrdered && (primary === null || accessInfo.viewMode))
2543
+ const blockedNote = isLast && !isOrdered && primary?.disabled && hints ? 'Settle the choices above first' : null
2544
+
2545
+ return (
2546
+ <Box
2547
+ component="footer"
2548
+ sx={{
2549
+ display: 'flex',
2550
+ alignItems: 'center',
2551
+ gap: compact ? 1.5 : 3,
2552
+ minBlockSize: compact ? 72 : 84,
2553
+ paddingInline: compact ? 2 : 4,
2554
+ paddingBlock: 1.5,
2555
+ bgcolor: NIGHTJAR.ink,
2556
+ borderBlockStart: `1px solid ${NIGHTJAR.line}`,
2557
+ pointerEvents: 'auto',
2558
+ }}
2559
+ >
2560
+ {!compact && (
2561
+ <Box component="div" sx={{ display: 'grid', gap: 0.25, minInlineSize: 0 }}>
2562
+ <Typography sx={{ ...HEADING_SX, fontSize: 18, letterSpacing: '0.24em' }}>
2563
+ {detail ? resolve(detail.state.title) : 'Nightjar'}
2564
+ </Typography>
2565
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 13, color: NIGHTJAR.muted, whiteSpace: 'nowrap' }}>
2566
+ {detail ? resolve(detail.state.subtitle) : ''}
2567
+ </Typography>
2568
+ </Box>
2569
+ )}
2570
+
2571
+ <Box component="div" sx={{ display: 'grid', marginInlineStart: compact ? 0 : 'auto', minInlineSize: 0 }}>
2572
+ <Typography sx={{ ...LABEL_SX, fontSize: 11 }}>{isOrdered ? 'Ordered' : 'Your price'}</Typography>
2573
+ <Typography
2574
+ aria-live="polite"
2575
+ sx={{
2576
+ fontFamily: NIGHTJAR.font,
2577
+ fontSize: compact ? 22 : 28,
2578
+ fontWeight: 700,
2579
+ lineHeight: 1.1,
2580
+ fontVariantNumeric: 'tabular-nums',
2581
+ color: NIGHTJAR.cream,
2582
+ }}
2583
+ >
2584
+ {price}
2585
+ </Typography>
2586
+ </Box>
2587
+
2588
+ <Box
2589
+ component="div"
2590
+ sx={{ display: 'flex', alignItems: 'center', gap: 1.5, marginInlineStart: compact ? 'auto' : 2 }}
2591
+ >
2592
+ {blockedNote && !compact && (
2593
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.amber }}>{blockedNote}</Typography>
2594
+ )}
2595
+ {canGoPrevious && !isOrdered && (
2596
+ <Tooltip title="Back">
2597
+ <span>
2598
+ <IconButton
2599
+ aria-label="Back"
2600
+ onClick={previous}
2601
+ disabled={isPending}
2602
+ sx={{
2603
+ inlineSize: 48,
2604
+ blockSize: 48,
2605
+ border: `1px solid ${NIGHTJAR.line}`,
2606
+ color: NIGHTJAR.cream,
2607
+ '&:hover': { bgcolor: NIGHTJAR.raisedHover },
2608
+ '&.Mui-disabled': { color: NIGHTJAR.faint },
2609
+ }}
2610
+ >
2611
+ <svg viewBox="0 0 24 24" width={20} height={20} fill="none" stroke="currentColor" strokeWidth={2}>
2612
+ <path d={BACK_ICON} strokeLinecap="round" strokeLinejoin="round" />
2613
+ </svg>
2614
+ </IconButton>
2615
+ </span>
2616
+ </Tooltip>
2617
+ )}
2618
+ {showAction && (
2619
+ <Button
2620
+ onClick={action.onClick}
2621
+ disabled={isPending}
2622
+ aria-disabled={action.blocked || undefined}
2623
+ title={isLast && !isOrdered ? (hints ?? primary?.title) : undefined}
2624
+ startIcon={isPending ? <CircularProgress size={16} sx={{ color: NIGHTJAR.ink }} /> : undefined}
2625
+ sx={{
2626
+ ...HEADING_SX,
2627
+ minBlockSize: 48,
2628
+ paddingInline: compact ? 2.5 : 3.5,
2629
+ borderRadius: 1,
2630
+ fontSize: 14,
2631
+ whiteSpace: 'nowrap',
2632
+ color: NIGHTJAR.ink,
2633
+ bgcolor: action.blocked ? NIGHTJAR.muted : NIGHTJAR.amber,
2634
+ '&:hover': { bgcolor: action.blocked ? NIGHTJAR.muted : NIGHTJAR.amberHover },
2635
+ '&.Mui-disabled': { color: NIGHTJAR.ink, bgcolor: NIGHTJAR.amber, opacity: 0.75 },
2636
+ '&.Mui-focusVisible': { outline: `2px solid ${NIGHTJAR.cream}`, outlineOffset: 2 },
2637
+ }}
2638
+ >
2639
+ {isPending ? 'Placing order…' : action.label}
2640
+ </Button>
2641
+ )}
2642
+ </Box>
2643
+ </Box>
2644
+ )
2645
+ }
2646
+ ```
2647
+
2648
+ ### ./src/\_shared/OrderConfirmation.tsx
2649
+
2650
+ ```tsx
2651
+ import { Box, Typography } from '@mui/material'
2652
+
2653
+ import { HEADING_SX, LABEL_SX, NIGHTJAR, SummaryRows } from '../_fields/index.js'
2654
+ import type { Contact } from './order-form.js'
2655
+
2656
+ const CHECK_ICON = 'M5 12.5 10 17.5 19 7'
2657
+
2658
+ interface OrderConfirmationProps {
2659
+ readonly compact: boolean
2660
+ readonly contact: Contact
2661
+ }
2662
+
2663
+ // The `ordered` navigation state, which the order handler enters once the order is accepted. The order number is
2664
+ // made from the configuration's id, so it stays the same when the confirmation is shown again.
2665
+ export const OrderConfirmation = ({ compact, contact }: OrderConfirmationProps) => {
2666
+ const configurationId = phx.frontend.useConfigurationId()
2667
+ const price = phx.frontend.useRootEntityPrice()
2668
+ const orderNumber = `NJ-${String(configurationId)
2669
+ .replace(/[^a-z0-9]/gi, '')
2670
+ .slice(-6)
2671
+ .toUpperCase()}`
2672
+ const name = contact.name.trim()
2673
+
2674
+ return (
2675
+ <Box component="div" role="status" sx={{ display: 'grid', gap: compact ? 2.5 : 3 }}>
2676
+ <Box component="header" sx={{ display: 'grid', gap: 1.5, justifyItems: 'start' }}>
2677
+ <Box
2678
+ component="svg"
2679
+ viewBox="0 0 24 24"
2680
+ aria-hidden
2681
+ sx={{ inlineSize: 44, blockSize: 44, padding: 1.25, borderRadius: '50%', bgcolor: NIGHTJAR.amber }}
2682
+ >
2683
+ <path d={CHECK_ICON} fill="none" stroke={NIGHTJAR.ink} strokeWidth={2.6} strokeLinecap="round" />
2684
+ </Box>
2685
+ <Typography sx={LABEL_SX}>Order {orderNumber}</Typography>
2686
+ <Typography component="h2" sx={{ ...HEADING_SX, fontSize: compact ? 24 : 30, lineHeight: 1.05 }}>
2687
+ {name ? `Thank you, ${name}` : 'Thank you'}
2688
+ </Typography>
2689
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 15, lineHeight: 1.5, color: NIGHTJAR.muted }}>
2690
+ Your Nightjar goes into the build queue at {price}. We will write to {contact.email.trim() || 'you'} when the
2691
+ shop confirms it; a build takes about eight weeks.
2692
+ </Typography>
2693
+ </Box>
2694
+ <Box component="section" sx={{ display: 'grid', gap: 1.5 }}>
2695
+ <Typography component="h3" sx={LABEL_SX}>
2696
+ What we are building
2697
+ </Typography>
2698
+ <SummaryRows />
2699
+ </Box>
2700
+ </Box>
2701
+ )
2702
+ }
2703
+ ```
2704
+
2705
+ ### ./src/\_shared/PhonePage.tsx
2706
+
2707
+ ```tsx
2708
+ import { Box, Typography } from '@mui/material'
2709
+ import type { AnimationEvent } from 'react'
2710
+
2711
+ import { ENTER_FROM_BELOW, HEADING_SX, NIGHTJAR } from '../_fields/index.js'
2712
+ import { OrderBar } from './OrderBar.js'
2713
+ import { OrderConfirmation } from './OrderConfirmation.js'
2714
+ import { StepHeader } from './StepHeader.js'
2715
+ import { StepPanel } from './StepPanel.js'
2716
+ import { SummaryPanel } from './SummaryPanel.js'
2717
+ import type { OrderForm } from './order-form.js'
2718
+
2719
+ type Page = 'steps' | 'summary' | 'ordered'
2720
+
2721
+ // How much of the screen below the header the guitar keeps: the summary's form needs more of it
2722
+ const SCENE_SHARES: Readonly<Record<Page, string>> = { steps: '46%', summary: '30%', ordered: '40%' }
2723
+
2724
+ const pageOf = (navigationState: string): Page => {
2725
+ return navigationState === 'ordered' ? 'ordered' : navigationState === 'summary' ? 'summary' : 'steps'
2726
+ }
2727
+
2728
+ interface PhonePageProps {
2729
+ readonly form: OrderForm
2730
+ readonly reached: number
2731
+ }
2732
+
2733
+ // The phone wizard: a compact progress line, the guitar at the top, the step's choices in a sheet below it and the
2734
+ // bar fixed at the bottom. Like the desktop pages, each page rises in with its own safe area and measures it again
2735
+ // with `update()` once the slide has settled.
2736
+ export const PhonePage = ({ form, reached }: PhonePageProps) => {
2737
+ const { containerRef, safeAreaRef, update } = phx.frontend.useSceneSafeArea()
2738
+ const navigationState = phx.frontend.useActiveNavigationState()
2739
+ const page = pageOf(navigationState)
2740
+
2741
+ const handleAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
2742
+ if (event.target === event.currentTarget) {
2743
+ update()
2744
+ }
2745
+ }
2746
+
2747
+ return (
2748
+ <Box
2749
+ component="div"
2750
+ ref={containerRef}
2751
+ sx={{
2752
+ position: 'absolute',
2753
+ inset: 0,
2754
+ display: 'flex',
2755
+ flexDirection: 'column',
2756
+ fontFamily: NIGHTJAR.font,
2757
+ color: NIGHTJAR.cream,
2758
+ pointerEvents: 'none',
2759
+ }}
2760
+ >
2761
+ <Box
2762
+ component="header"
2763
+ sx={{
2764
+ display: 'grid',
2765
+ gap: 1.25,
2766
+ paddingInline: 2,
2767
+ paddingBlock: 1.5,
2768
+ bgcolor: NIGHTJAR.ink,
2769
+ borderBlockEnd: `1px solid ${NIGHTJAR.line}`,
2770
+ pointerEvents: 'auto',
2771
+ }}
2772
+ >
2773
+ <Box component="div" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
2774
+ <Box component="span" aria-hidden sx={{ inlineSize: 3, blockSize: 16, bgcolor: NIGHTJAR.amber }} />
2775
+ <Typography sx={{ ...HEADING_SX, fontSize: 13, letterSpacing: '0.32em' }}>Northfret</Typography>
2776
+ </Box>
2777
+ <StepHeader compact reached={reached} />
2778
+ </Box>
2779
+
2780
+ <Box
2781
+ component="div"
2782
+ key={page}
2783
+ onAnimationEnd={handleAnimationEnd}
2784
+ sx={{
2785
+ display: 'flex',
2786
+ flexDirection: 'column',
2787
+ flex: '1 1 0',
2788
+ minBlockSize: 0,
2789
+ animation: `${ENTER_FROM_BELOW} 460ms cubic-bezier(0.2, 0.7, 0.2, 1)`,
2790
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
2791
+ }}
2792
+ >
2793
+ <Box component="div" sx={{ position: 'relative', flex: `0 0 ${SCENE_SHARES[page]}`, minBlockSize: 0 }}>
2794
+ <Box component="div" ref={safeAreaRef} sx={{ position: 'absolute', inset: '12px 16px 8px' }} />
2795
+ </Box>
2796
+ <Box
2797
+ component="main"
2798
+ sx={{
2799
+ flex: '1 1 0',
2800
+ minBlockSize: 0,
2801
+ overflowY: 'auto',
2802
+ overscrollBehavior: 'contain',
2803
+ paddingInline: 2,
2804
+ paddingBlock: 2.5,
2805
+ bgcolor: NIGHTJAR.panel,
2806
+ borderBlockStart: `1px solid ${NIGHTJAR.line}`,
2807
+ borderStartStartRadius: 16,
2808
+ borderStartEndRadius: 16,
2809
+ pointerEvents: 'auto',
2810
+ }}
2811
+ >
2812
+ {page === 'steps' && <StepPanel compact />}
2813
+ {page === 'summary' && <SummaryPanel compact form={form} />}
2814
+ {page === 'ordered' && <OrderConfirmation compact contact={form.contact} />}
2815
+ </Box>
2816
+ </Box>
2817
+
2818
+ <OrderBar compact form={form} />
2819
+ </Box>
2820
+ )
2821
+ }
2822
+ ```
2823
+
2824
+ ### ./src/\_shared/StepHeader.tsx
2825
+
2826
+ ```tsx
2827
+ import { Box, ButtonBase, Typography } from '@mui/material'
2828
+
2829
+ import { HEADING_SX, NIGHTJAR, useWarnings } from '../_fields/index.js'
2830
+ import { STEP_WARNINGS } from './step-warnings.js'
2831
+
2832
+ const CHECK_ICON = 'M5 12.5 10 17.5 19 7'
2833
+
2834
+ interface StepHeaderProps {
2835
+ /** One line with a progress bar, for a phone; the full stepper otherwise */
2836
+ readonly compact: boolean
2837
+ /** The furthest step the customer has reached; the layout keeps it, so it survives the switch between the pages */
2838
+ readonly reached: number
2839
+ }
2840
+
2841
+ // The spec's `navigationSteps` as a stepper. Labels and order come from the spec; `useStepNavigation` gives each step
2842
+ // its status, and `goTo` enters a step and records the stepper's analytics event. The customer can go back to any step
2843
+ // they have reached, never ahead of it, and nowhere while the order is being placed or after it was placed.
2844
+ export const StepHeader = ({ compact, reached }: StepHeaderProps) => {
2845
+ const { steps, activeIndex, activeStep, goTo } = phx.frontend.useStepNavigation()
2846
+ const navigationState = phx.frontend.useActiveNavigationState()
2847
+ const { primary } = phx.frontend.useFinalizeActions()
2848
+ const warnings = useWarnings()
2849
+ const isLocked = navigationState === 'ordered' || (primary?.isPending ?? false)
2850
+
2851
+ const hasWarning = (state: string) => {
2852
+ return warnings.some(warning => STEP_WARNINGS[state]?.includes(warning.type))
2853
+ }
2854
+
2855
+ if (compact) {
2856
+ const progress = steps.length === 0 ? 0 : (activeIndex + 1) / steps.length
2857
+
2858
+ return (
2859
+ <Box component="nav" aria-label="Build steps" sx={{ display: 'grid', gap: 1 }}>
2860
+ <Typography
2861
+ sx={{ ...HEADING_SX, fontSize: 13, letterSpacing: '0.14em', display: 'flex', alignItems: 'center', gap: 1 }}
2862
+ >
2863
+ <Box component="span" sx={{ color: NIGHTJAR.amber, fontVariantNumeric: 'tabular-nums' }}>
2864
+ {activeIndex + 1} / {steps.length}
2865
+ </Box>
2866
+ <Box component="span" aria-hidden sx={{ color: NIGHTJAR.faint }}>
2867
+ ·
2868
+ </Box>
2869
+ <Box component="span" aria-current="step">
2870
+ {activeStep?.label}
2871
+ </Box>
2872
+ </Typography>
2873
+ <Box
2874
+ component="div"
2875
+ role="progressbar"
2876
+ aria-label="Build progress"
2877
+ aria-valuemin={0}
2878
+ aria-valuemax={steps.length}
2879
+ aria-valuenow={activeIndex + 1}
2880
+ sx={{ blockSize: 2, borderRadius: 1, bgcolor: NIGHTJAR.line, overflow: 'hidden' }}
2881
+ >
2882
+ <Box
2883
+ component="div"
2884
+ sx={{
2885
+ blockSize: '100%',
2886
+ inlineSize: `${progress * 100}%`,
2887
+ bgcolor: NIGHTJAR.amber,
2888
+ transition: 'inline-size 400ms ease-out',
2889
+ }}
2890
+ />
2891
+ </Box>
2892
+ </Box>
2893
+ )
2894
+ }
2895
+
2896
+ return (
2897
+ <Box component="nav" aria-label="Build steps">
2898
+ <Box
2899
+ component="ol"
2900
+ sx={{ display: 'flex', alignItems: 'center', gap: 1, margin: 0, padding: 0, listStyle: 'none' }}
2901
+ >
2902
+ {steps.map(step => {
2903
+ const isActive = step.status === 'active'
2904
+ const isComplete = step.status === 'complete'
2905
+ const canEnter = !isLocked && !isActive && step.index <= reached
2906
+ const warn = hasWarning(step.state)
2907
+
2908
+ return (
2909
+ <Box component="li" key={step.state} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
2910
+ {step.index > 0 && (
2911
+ <Box
2912
+ component="span"
2913
+ aria-hidden
2914
+ sx={{
2915
+ inlineSize: 28,
2916
+ blockSize: '1px',
2917
+ bgcolor: step.index <= reached ? NIGHTJAR.muted : NIGHTJAR.line,
2918
+ }}
2919
+ />
2920
+ )}
2921
+ <ButtonBase
2922
+ aria-label={warn ? `${step.label}, needs attention` : step.label}
2923
+ aria-current={isActive ? 'step' : undefined}
2924
+ disabled={!canEnter}
2925
+ onClick={() => goTo(step.index)}
2926
+ sx={{
2927
+ display: 'flex',
2928
+ alignItems: 'center',
2929
+ gap: 1,
2930
+ paddingInline: 1,
2931
+ paddingBlock: 0.75,
2932
+ borderRadius: 1,
2933
+ color: isActive ? NIGHTJAR.cream : step.index <= reached ? NIGHTJAR.muted : NIGHTJAR.faint,
2934
+ '&:hover': { color: NIGHTJAR.cream },
2935
+ '&.Mui-focusVisible': { outline: `2px solid ${NIGHTJAR.amber}` },
2936
+ }}
2937
+ >
2938
+ <Box
2939
+ component="span"
2940
+ sx={{
2941
+ display: 'grid',
2942
+ placeItems: 'center',
2943
+ inlineSize: 24,
2944
+ blockSize: 24,
2945
+ borderRadius: '50%',
2946
+ fontFamily: NIGHTJAR.font,
2947
+ fontSize: 12,
2948
+ fontWeight: 700,
2949
+ color: isActive ? NIGHTJAR.ink : 'inherit',
2950
+ bgcolor: isActive ? NIGHTJAR.amber : 'transparent',
2951
+ border: isActive ? 'none' : `1px solid ${isComplete ? NIGHTJAR.muted : NIGHTJAR.line}`,
2952
+ }}
2953
+ >
2954
+ {isComplete ? (
2955
+ <Box component="svg" viewBox="0 0 24 24" aria-hidden sx={{ inlineSize: 14, blockSize: 14 }}>
2956
+ <path d={CHECK_ICON} fill="none" stroke="currentColor" strokeWidth={2.6} strokeLinecap="round" />
2957
+ </Box>
2958
+ ) : (
2959
+ step.index + 1
2960
+ )}
2961
+ </Box>
2962
+ {/* In a narrow window only the active step keeps its label; the others keep their number */}
2963
+ <Typography
2964
+ component="span"
2965
+ sx={{
2966
+ ...HEADING_SX,
2967
+ display: { xs: isActive ? 'inline' : 'none', lg: 'inline' },
2968
+ fontSize: 13,
2969
+ color: 'inherit',
2970
+ }}
2971
+ >
2972
+ {step.label}
2973
+ </Typography>
2974
+ {warn && (
2975
+ <Box
2976
+ component="span"
2977
+ aria-hidden
2978
+ sx={{ inlineSize: 7, blockSize: 7, borderRadius: '50%', bgcolor: NIGHTJAR.amber }}
2979
+ />
2980
+ )}
2981
+ </ButtonBase>
2982
+ </Box>
2983
+ )
2984
+ })}
2985
+ </Box>
2986
+ </Box>
2987
+ )
2988
+ }
2989
+ ```
2990
+
2991
+ ### ./src/\_shared/StepPanel.tsx
2992
+
2993
+ ```tsx
2994
+ import { Box, Typography } from '@mui/material'
2995
+ import { useState } from 'react'
2996
+
2997
+ import { ENTER_FROM_END, ENTER_FROM_START, HEADING_SX, LABEL_SX, NIGHTJAR, StepFields } from '../_fields/index.js'
2998
+
2999
+ // A line under each step's title; the layout's copy, keyed by the step's navigation state
3000
+ const STEP_INTROS: Readonly<Record<string, string>> = {
3001
+ body: 'The shape sets the feel, the finish and the pickguard the look.',
3002
+ neck: 'What your hand holds: the neck and the board under the strings.',
3003
+ electronics: 'The sound: two pickups, and the metal parts around them.',
3004
+ details: 'Sign the headstock. The shop paints it on by hand.',
3005
+ }
3006
+
3007
+ /** 1 when the active step moved forward, -1 when it moved back; the step's content slides in from that side. */
3008
+ const useStepDirection = (activeIndex: number): 1 | -1 => {
3009
+ const [shown, setShown] = useState<{ readonly index: number; readonly direction: 1 | -1 }>({
3010
+ index: activeIndex,
3011
+ direction: 1,
3012
+ })
3013
+
3014
+ if (activeIndex !== shown.index) {
3015
+ setShown({ index: activeIndex, direction: activeIndex > shown.index ? 1 : -1 })
3016
+ }
3017
+
3018
+ return shown.direction
3019
+ }
3020
+
3021
+ interface StepPanelProps {
3022
+ readonly compact: boolean
3023
+ }
3024
+
3025
+ // The active step: its number, its label from the spec, and its fields. The content is keyed by the step, so each
3026
+ // step slides in from the side the customer moves towards.
3027
+ export const StepPanel = ({ compact }: StepPanelProps) => {
3028
+ const { steps, activeStep, activeIndex } = phx.frontend.useStepNavigation()
3029
+ const direction = useStepDirection(activeIndex)
3030
+
3031
+ if (activeStep === null) {
3032
+ return null
3033
+ }
3034
+
3035
+ return (
3036
+ <Box
3037
+ component="div"
3038
+ key={activeStep.state}
3039
+ sx={{
3040
+ display: 'grid',
3041
+ gap: compact ? 2.5 : 3.5,
3042
+ animation: `${direction === 1 ? ENTER_FROM_END : ENTER_FROM_START} 420ms cubic-bezier(0.2, 0.7, 0.2, 1)`,
3043
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
3044
+ }}
3045
+ >
3046
+ {!compact && (
3047
+ <Box component="header" sx={{ display: 'grid', gap: 1 }}>
3048
+ <Typography sx={LABEL_SX}>
3049
+ Step {activeIndex + 1} of {steps.length}
3050
+ </Typography>
3051
+ <Typography component="h2" sx={{ ...HEADING_SX, fontSize: 30, lineHeight: 1.05 }}>
3052
+ {activeStep.label}
3053
+ </Typography>
3054
+ {STEP_INTROS[activeStep.state] && (
3055
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 15, lineHeight: 1.45, color: NIGHTJAR.muted }}>
3056
+ {STEP_INTROS[activeStep.state]}
3057
+ </Typography>
3058
+ )}
3059
+ </Box>
3060
+ )}
3061
+ <StepFields compact={compact} />
3062
+ </Box>
3063
+ )
3064
+ }
3065
+ ```
3066
+
3067
+ ### ./src/\_shared/SummaryPanel.tsx
3068
+
3069
+ ```tsx
3070
+ import { Box, Button, InputBase, Typography } from '@mui/material'
3071
+ import type { ReactNode } from 'react'
3072
+ import { useEffect, useRef } from 'react'
3073
+
3074
+ import { HEADING_SX, LABEL_SX, NIGHTJAR, SummaryRows, useWarnings } from '../_fields/index.js'
3075
+ import type { OrderForm } from './order-form.js'
3076
+ import { STEP_WARNINGS } from './step-warnings.js'
3077
+
3078
+ const SECTION_SX = { display: 'grid', gap: 1.5 } as const
3079
+
3080
+ interface ContactInputProps {
3081
+ readonly id: string
3082
+ readonly label: string
3083
+ readonly value: string
3084
+ readonly onChange: (value: string) => void
3085
+ readonly error: string | undefined
3086
+ readonly type: 'text' | 'email'
3087
+ readonly autoComplete: string
3088
+ readonly disabled: boolean
3089
+ }
3090
+
3091
+ const ContactInput = ({ id, label, value, onChange, error, type, autoComplete, disabled }: ContactInputProps) => {
3092
+ return (
3093
+ <Box component="div" sx={{ display: 'grid', gap: 0.75 }}>
3094
+ <Typography
3095
+ component="label"
3096
+ htmlFor={id}
3097
+ sx={{ fontFamily: NIGHTJAR.font, fontSize: 14, color: NIGHTJAR.muted }}
3098
+ >
3099
+ {label}
3100
+ </Typography>
3101
+ <InputBase
3102
+ id={id}
3103
+ type={type}
3104
+ value={value}
3105
+ disabled={disabled}
3106
+ autoComplete={autoComplete}
3107
+ onChange={event => onChange(event.target.value)}
3108
+ inputProps={{ 'aria-invalid': error !== undefined, 'aria-describedby': error ? `${id}-error` : undefined }}
3109
+ sx={{
3110
+ paddingInline: 1.5,
3111
+ paddingBlock: 0.75,
3112
+ borderRadius: 1.5,
3113
+ border: `1px solid ${error ? NIGHTJAR.alert : NIGHTJAR.line}`,
3114
+ bgcolor: NIGHTJAR.raised,
3115
+ fontFamily: NIGHTJAR.font,
3116
+ fontSize: 16,
3117
+ color: NIGHTJAR.cream,
3118
+ '&.Mui-focused': { borderColor: error ? NIGHTJAR.alert : NIGHTJAR.amber },
3119
+ }}
3120
+ />
3121
+ {error && (
3122
+ <Typography id={`${id}-error`} sx={{ fontFamily: NIGHTJAR.font, fontSize: 13, color: NIGHTJAR.alert }}>
3123
+ {error}
3124
+ </Typography>
3125
+ )}
3126
+ </Box>
3127
+ )
3128
+ }
3129
+
3130
+ const Card = ({ children, tone }: { readonly children: ReactNode; readonly tone: 'plain' | 'warning' | 'alert' }) => {
3131
+ const borders = { plain: NIGHTJAR.line, warning: NIGHTJAR.amber, alert: NIGHTJAR.alert }
3132
+ const backgrounds = { plain: NIGHTJAR.raised, warning: NIGHTJAR.amberTint, alert: NIGHTJAR.alertTint }
3133
+
3134
+ return (
3135
+ <Box
3136
+ component="section"
3137
+ sx={{
3138
+ display: 'grid',
3139
+ gap: 1.5,
3140
+ padding: 2,
3141
+ borderRadius: 2,
3142
+ border: `1px solid ${borders[tone]}`,
3143
+ bgcolor: backgrounds[tone],
3144
+ }}
3145
+ >
3146
+ {children}
3147
+ </Box>
3148
+ )
3149
+ }
3150
+
3151
+ // The warnings left between choices, each with the way to the step that resolves it. Ordering waits until none is
3152
+ // left: `useFinalizeActions` reports the action `disabled` with the same texts as `hints`.
3153
+ const Conflicts = () => {
3154
+ const warnings = useWarnings()
3155
+ const { steps, goTo } = phx.frontend.useStepNavigation()
3156
+ const conflicts = warnings.flatMap(warning => {
3157
+ const step = steps.find(candidate => STEP_WARNINGS[candidate.state]?.includes(warning.type))
3158
+
3159
+ return step ? [{ ...warning, step }] : []
3160
+ })
3161
+
3162
+ if (conflicts.length === 0) {
3163
+ return null
3164
+ }
3165
+
3166
+ return (
3167
+ <Card tone="warning">
3168
+ <Typography component="h3" sx={{ ...LABEL_SX, color: NIGHTJAR.amber }}>
3169
+ {conflicts.length === 1 ? 'One choice to settle before ordering' : `${conflicts.length} choices to settle`}
3170
+ </Typography>
3171
+ {conflicts.map(conflict => (
3172
+ <Box component="div" key={conflict.type} sx={{ display: 'grid', gap: 1, justifyItems: 'start' }}>
3173
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 15, lineHeight: 1.45, color: NIGHTJAR.cream }}>
3174
+ {conflict.text}
3175
+ </Typography>
3176
+ <Button
3177
+ onClick={() => goTo(conflict.step.index)}
3178
+ sx={{
3179
+ ...LABEL_SX,
3180
+ minBlockSize: 36,
3181
+ paddingInline: 1.5,
3182
+ color: NIGHTJAR.ink,
3183
+ bgcolor: NIGHTJAR.amber,
3184
+ '&:hover': { bgcolor: NIGHTJAR.amberHover },
3185
+ }}
3186
+ >
3187
+ Go to {conflict.step.label}
3188
+ </Button>
3189
+ </Box>
3190
+ ))}
3191
+ </Card>
3192
+ )
3193
+ }
3194
+
3195
+ interface SummaryPanelProps {
3196
+ readonly compact: boolean
3197
+ readonly form: OrderForm
3198
+ }
3199
+
3200
+ // The last step: every choice as the spec's control hooks read it, the conflicts left, the contact form and the
3201
+ // order's error. The summary only reviews; a choice is changed on its step, which the stepper and the conflicts link
3202
+ // to. **Place order** is in the bar at the bottom.
3203
+ export const SummaryPanel = ({ compact, form }: SummaryPanelProps) => {
3204
+ const { primary } = phx.frontend.useFinalizeActions()
3205
+ const { contact, setContact, errors, showErrors, blockedAttempts, isComplete } = form
3206
+ const isPending = primary?.isPending ?? false
3207
+ const topRef = useRef<HTMLDivElement>(null)
3208
+ const detailsRef = useRef<HTMLElement>(null)
3209
+ // The attempts already handled; the summary opens again after a blocked attempt without scrolling
3210
+ const scrolledFor = useRef(blockedAttempts)
3211
+
3212
+ // After each blocked attempt to order, bring what blocks it into view: the missing details, or the conflicts on top.
3213
+ // Typing into the form changes `isComplete` but is no new attempt, so it does not scroll.
3214
+ useEffect(() => {
3215
+ if (blockedAttempts === scrolledFor.current) {
3216
+ return
3217
+ }
3218
+
3219
+ scrolledFor.current = blockedAttempts
3220
+ const target = isComplete ? topRef.current : detailsRef.current
3221
+ target?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
3222
+ }, [blockedAttempts, isComplete])
3223
+
3224
+ return (
3225
+ <Box component="div" ref={topRef} sx={{ display: 'grid', gap: compact ? 2.5 : 3, scrollMarginBlockStart: 32 }}>
3226
+ {!compact && (
3227
+ <Box component="header" sx={{ display: 'grid', gap: 1 }}>
3228
+ <Typography sx={LABEL_SX}>Last step</Typography>
3229
+ <Typography component="h2" sx={{ ...HEADING_SX, fontSize: 30, lineHeight: 1.05 }}>
3230
+ Your Nightjar
3231
+ </Typography>
3232
+ <Typography sx={{ fontFamily: NIGHTJAR.font, fontSize: 15, lineHeight: 1.45, color: NIGHTJAR.muted }}>
3233
+ Check the build, tell us who it is for, and place the order. Nothing is charged until the shop confirms.
3234
+ </Typography>
3235
+ </Box>
3236
+ )}
3237
+
3238
+ {primary?.error && (
3239
+ <Card tone="alert">
3240
+ <Typography component="h3" sx={{ ...LABEL_SX, color: NIGHTJAR.alert }}>
3241
+ The order did not go through
3242
+ </Typography>
3243
+ <Typography
3244
+ role="alert"
3245
+ sx={{ fontFamily: NIGHTJAR.font, fontSize: 15, lineHeight: 1.45, color: NIGHTJAR.cream }}
3246
+ >
3247
+ {primary.error.message}
3248
+ </Typography>
3249
+ </Card>
3250
+ )}
3251
+
3252
+ <Conflicts />
3253
+
3254
+ <Box component="section" sx={SECTION_SX}>
3255
+ <Typography component="h3" sx={LABEL_SX}>
3256
+ The build
3257
+ </Typography>
3258
+ <SummaryRows />
3259
+ </Box>
3260
+
3261
+ <Box component="section" ref={detailsRef} sx={{ ...SECTION_SX, scrollMarginBlockEnd: 32 }}>
3262
+ <Typography component="h3" sx={LABEL_SX}>
3263
+ Your details
3264
+ </Typography>
3265
+ <Box component="div" sx={{ display: 'grid', gridTemplateColumns: compact ? '1fr' : '1fr 1fr', gap: 1.5 }}>
3266
+ <ContactInput
3267
+ id="nightjar-name"
3268
+ label="Name"
3269
+ type="text"
3270
+ autoComplete="name"
3271
+ value={contact.name}
3272
+ onChange={name => setContact({ ...contact, name })}
3273
+ error={showErrors ? errors.name : undefined}
3274
+ disabled={isPending}
3275
+ />
3276
+ <ContactInput
3277
+ id="nightjar-email"
3278
+ label="E-mail"
3279
+ type="email"
3280
+ autoComplete="email"
3281
+ value={contact.email}
3282
+ onChange={email => setContact({ ...contact, email })}
3283
+ error={showErrors ? errors.email : undefined}
3284
+ disabled={isPending}
3285
+ />
3286
+ </Box>
3287
+ </Box>
3288
+ </Box>
3289
+ )
3290
+ }
3291
+ ```
3292
+
3293
+ ### ./src/\_shared/order-form.ts
3294
+
3295
+ ```ts
3296
+ import { useState } from 'react'
3297
+
3298
+ /** The customer's name and e-mail, which the summary asks for. */
3299
+ export interface Contact {
3300
+ readonly name: string
3301
+ readonly email: string
3302
+ }
3303
+
3304
+ /** What is missing from the contact details, per field. */
3305
+ export interface ContactErrors {
3306
+ readonly name?: string
3307
+ readonly email?: string
3308
+ }
3309
+
3310
+ /**
3311
+ * The summary's contact form. It is the layout's own state, not the configuration's: the details are needed only to
3312
+ * place the order, and the confirmation thanks the customer by name.
3313
+ */
3314
+ export interface OrderForm {
3315
+ readonly contact: Contact
3316
+ readonly setContact: (contact: Contact) => void
3317
+ readonly errors: ContactErrors
3318
+ readonly isComplete: boolean
3319
+ /** Whether the form shows what is missing: from the first attempt to order on */
3320
+ readonly showErrors: boolean
3321
+ /** How many attempts to order were blocked; the summary scrolls to what blocks the order after each */
3322
+ readonly blockedAttempts: number
3323
+ readonly revealErrors: () => void
3324
+ }
3325
+
3326
+ const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/
3327
+
3328
+ export const useOrderForm = (): OrderForm => {
3329
+ const [contact, setContact] = useState<Contact>({ name: '', email: '' })
3330
+ const [blockedAttempts, setBlockedAttempts] = useState(0)
3331
+ const errors: ContactErrors = {
3332
+ ...(contact.name.trim() === '' && { name: 'Enter the name the order is for.' }),
3333
+ ...(!EMAIL.test(contact.email.trim()) && { email: 'Enter an e-mail address for the build updates.' }),
3334
+ }
3335
+
3336
+ return {
3337
+ contact,
3338
+ setContact,
3339
+ errors,
3340
+ isComplete: errors.name === undefined && errors.email === undefined,
3341
+ showErrors: blockedAttempts > 0,
3342
+ blockedAttempts,
3343
+ revealErrors: () => setBlockedAttempts(attempts => attempts + 1),
3344
+ }
3345
+ }
3346
+ ```
3347
+
3348
+ ### ./src/\_shared/step-warnings.ts
3349
+
3350
+ ```ts
3351
+ // Which step resolves each warning: the stepper marks the step while the warning holds, and the summary sends the
3352
+ // customer there. A warning can involve several steps; this names the one the layout sends the customer to.
3353
+ export const STEP_WARNINGS: Readonly<Record<string, readonly string[]>> = {
3354
+ electronics: ['offsetHumbuckers'],
3355
+ }
3356
+ ```
3357
+
3358
+ ### ./src/\_shared/use-reached-step.ts
3359
+
3360
+ ```ts
3361
+ import { useState } from 'react'
3362
+
3363
+ // The furthest step the customer has reached. The stepper offers every step up to it, so after changing an earlier
3364
+ // step the customer returns to where they were without stepping through the ones between. The layout calls this, where
3365
+ // it stays mounted, so the furthest step survives the switch between the desktop and the phone page.
3366
+ export const useReachedStep = (activeIndex: number): number => {
3367
+ const [reached, setReached] = useState(activeIndex)
3368
+
3369
+ if (activeIndex > reached) {
3370
+ setReached(activeIndex)
3371
+ }
3372
+
3373
+ return Math.max(reached, activeIndex)
3374
+ }
3375
+ ```
3376
+
3377
+ ### ./src/elements/ConfiguratorLayout.tsx
3378
+
3379
+ ```tsx
3380
+ import { Box } from '@mui/material'
3381
+
3382
+ import { LayoutSlots } from '@salsita-ai/sdk/configurator'
3383
+
3384
+ import { NIGHTJAR } from '../_fields/index.js'
3385
+ import { DesktopPage } from '../_shared/DesktopPage.js'
3386
+ import { PhonePage } from '../_shared/PhonePage.js'
3387
+ import { useOrderForm } from '../_shared/order-form.js'
3388
+ import { useReachedStep } from '../_shared/use-reached-step.js'
3389
+
3390
+ // A wizard over the scene. The canvas stays mounted the whole time; the desktop or the phone page lies over it. The
3391
+ // contact form's state and the furthest step reached live here, so they survive the switch between the two; the form
3392
+ // also survives the move to the confirmation.
3393
+ const ConfiguratorLayout = () => {
3394
+ // `null` until the container has been measured; only the scene shows until then
3395
+ const viewports = phx.frontend.useActiveViewports()
3396
+ const { activeIndex } = phx.frontend.useStepNavigation()
3397
+ const form = useOrderForm()
3398
+ const reached = useReachedStep(activeIndex)
3399
+ const isDesktop = viewports?.includes('desktop') ?? false
3400
+
3401
+ return (
3402
+ <Box component="div" sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: NIGHTJAR.ink }}>
3403
+ <LayoutSlots.SharedCanvas />
3404
+ {viewports !== null &&
3405
+ (isDesktop ? <DesktopPage form={form} reached={reached} /> : <PhonePage form={form} reached={reached} />)}
3406
+ </Box>
3407
+ )
3408
+ }
3409
+
3410
+ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
3411
+ ```
3412
+
3413
+ ### ./src/elements/NightjarStage.tsx
3414
+
3415
+ ```tsx
3416
+ import { Environment, Lightformer } from '@react-three/drei'
3417
+ import { useMemo } from 'react'
3418
+ import { CanvasTexture, SRGBColorSpace } from 'three'
3419
+
3420
+ import { NIGHTJAR } from '../_fields/index.js'
3421
+
3422
+ /** A warm pool of light that fades into the dark, drawn once on a canvas. */
3423
+ const createGlow = (): CanvasTexture => {
3424
+ const size = 512
3425
+ const canvas = document.createElement('canvas')
3426
+ canvas.width = size
3427
+ canvas.height = size
3428
+ const context = canvas.getContext('2d')
3429
+
3430
+ if (context) {
3431
+ const gradient = context.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2)
3432
+ gradient.addColorStop(0, 'rgba(242, 163, 58, 0.42)')
3433
+ gradient.addColorStop(0.22, 'rgba(214, 112, 46, 0.2)')
3434
+ gradient.addColorStop(0.5, 'rgba(122, 30, 43, 0.07)')
3435
+ gradient.addColorStop(0.75, 'rgba(14, 15, 18, 0)')
3436
+ context.fillStyle = gradient
3437
+ context.fillRect(0, 0, size, size)
3438
+ }
3439
+
3440
+ const texture = new CanvasTexture(canvas)
3441
+ texture.colorSpace = SRGBColorSpace
3442
+ return texture
3443
+ }
3444
+
3445
+ // The stage named by the spec's `canvasCustomEnvironment`: a dark room with a warm glow on the wall behind the guitar,
3446
+ // a warm key light and a cool rim. The light panels exist only in the reflections, where they draw the long highlights
3447
+ // on the clearcoat and the hardware. The scene is in millimetres.
3448
+ export default phx.frontend.createCustomPhoenixLayoutElement('NightjarStage', () => {
3449
+ const glow = useMemo(createGlow, [])
3450
+
3451
+ return (
3452
+ <>
3453
+ <color attach="background" args={[NIGHTJAR.ink]} />
3454
+ <ambientLight intensity={0.4} />
3455
+ <directionalLight position={[-900, 1400, 1600]} intensity={2.4} color="#ffe6c7" />
3456
+ <directionalLight position={[1400, 700, -900]} intensity={1.2} color="#a9c1ff" />
3457
+
3458
+ {/* The glow on the wall, behind the guitar and out of the camera's fit */}
3459
+ <mesh position={[-250, 300, -900]}>
3460
+ <planeGeometry args={[3600, 3600]} />
3461
+ <meshBasicMaterial map={glow} transparent depthWrite={false} toneMapped={false} />
3462
+ </mesh>
3463
+
3464
+ <Environment resolution={256} frames={1}>
3465
+ <color attach="background" args={['#15161b']} />
3466
+ {/* Tall strips at both sides for the long highlights, a softbox above, an amber wash behind */}
3467
+ <Lightformer form="rect" intensity={7} position={[-5, 1, 3]} rotation-y={Math.PI / 3} scale={[1.2, 9, 1]} />
3468
+ <Lightformer form="rect" intensity={4} position={[5, 1, 2]} rotation-y={-Math.PI / 3} scale={[1, 9, 1]} />
3469
+ <Lightformer form="rect" intensity={2.5} position={[0, 6, 1]} rotation-x={Math.PI / 2} scale={[8, 3, 1]} />
3470
+ <Lightformer form="rect" intensity={1.2} position={[0, 1, 7]} rotation-y={Math.PI} scale={[8, 4, 1]} />
3471
+ <Lightformer form="rect" color={NIGHTJAR.amber} intensity={0.8} position={[0, -2, -7]} scale={[10, 3, 1]} />
3472
+ </Environment>
3473
+ </>
3474
+ )
3475
+ })
3476
+ ```
3477
+
3478
+ ### ./src/expressions/guitar.ts
3479
+
3480
+ ```ts
3481
+ const BASE_PRICE = 1650
3482
+ const SIGNATURE_PRICE = 45
3483
+ const SIGNATURE_LIMIT = 16
3484
+
3485
+ const SURCHARGES = {
3486
+ bodyShape: { Offset: 0, SingleCut: 150 },
3487
+ finish: { Seafoam: 0, CandyRed: 180, Midnight: 120, NaturalAsh: 90 },
3488
+ pickguard: { Parchment: 0, Black: 0, Pearloid: 40, None: 0 },
3489
+ neckWood: { Maple: 0, RoastedMaple: 110, Mahogany: 80 },
3490
+ fretboard: { Maple: 0, Rosewood: 0, Ebony: 140 },
3491
+ pickups: { SingleCoils: 0, Humbuckers: 120, P90s: 90 },
3492
+ hardware: { Chrome: 0, Gold: 160, Black: 60 },
3493
+ }
3494
+
3495
+ // The wood textures ship with Storybook; a texture is multiplied by the material's colour
3496
+ const LIGHT_WOOD = '/textures/Material_Lightwood.png'
3497
+ const DARK_WOOD = '/textures/Material_Darkwood.png'
3498
+ const ASH = '/textures/oak_varnished_BaseColor.png'
3499
+
3500
+ // How each finish renders under the clearcoat: the paints are solid, natural ash shows the grain through a tint
3501
+ const FINISHES = {
3502
+ Seafoam: { color: '#9fd8c4', metalness: 0, roughness: 0.38, texture: null },
3503
+ CandyRed: { color: '#a3101c', metalness: 0.35, roughness: 0.3, texture: null },
3504
+ Midnight: { color: '#1b2a52', metalness: 0.55, roughness: 0.34, texture: null },
3505
+ NaturalAsh: { color: '#f4e2c4', metalness: 0, roughness: 0.5, texture: ASH },
3506
+ }
3507
+
3508
+ const PICKGUARDS = {
3509
+ Parchment: { color: '#ece2cb', roughness: 0.5, iridescence: 0 },
3510
+ Black: { color: '#121214', roughness: 0.3, iridescence: 0 },
3511
+ Pearloid: { color: '#f2ede6', roughness: 0.22, iridescence: 1 },
3512
+ None: { color: '#ece2cb', roughness: 0.5, iridescence: 0 },
3513
+ }
3514
+
3515
+ const NECKS = {
3516
+ Maple: { color: '#f6dcae', texture: LIGHT_WOOD },
3517
+ RoastedMaple: { color: '#c58a4e', texture: LIGHT_WOOD },
3518
+ Mahogany: { color: '#c47a5a', texture: DARK_WOOD },
3519
+ }
3520
+
3521
+ const FRETBOARDS = {
3522
+ Maple: { color: '#f6dcae', texture: LIGHT_WOOD, inlay: '#1b1714' },
3523
+ Rosewood: { color: '#ffffff', texture: DARK_WOOD, inlay: '#efe9df' },
3524
+ Ebony: { color: '#3a3330', texture: DARK_WOOD, inlay: '#efe9df' },
3525
+ }
3526
+
3527
+ const HARDWARE = {
3528
+ Chrome: { color: '#eceef0', metalness: 1, roughness: 0.12 },
3529
+ Gold: { color: '#e6b964', metalness: 1, roughness: 0.2 },
3530
+ Black: { color: '#26262a', metalness: 0.7, roughness: 0.32 },
3531
+ }
3532
+
3533
+ export default phx.domain.implementCustomExpressions({
3534
+ Guitar: {
3535
+ $price: s => {
3536
+ return (
3537
+ BASE_PRICE +
3538
+ SURCHARGES.bodyShape[s.bodyShape] +
3539
+ SURCHARGES.finish[s.finish] +
3540
+ SURCHARGES.pickguard[s.pickguard] +
3541
+ SURCHARGES.neckWood[s.neckWood] +
3542
+ SURCHARGES.fretboard[s.fretboard] +
3543
+ SURCHARGES.pickups[s.pickups] +
3544
+ SURCHARGES.hardware[s.hardware] +
3545
+ (s.signature.trim() === '' ? 0 : SIGNATURE_PRICE)
3546
+ )
3547
+ },
3548
+ $signatureLimit: () => {
3549
+ return SIGNATURE_LIMIT
3550
+ },
3551
+ $finishColor: s => {
3552
+ return FINISHES[s.finish].color
3553
+ },
3554
+ $finishMetalness: s => {
3555
+ return FINISHES[s.finish].metalness
3556
+ },
3557
+ $finishRoughness: s => {
3558
+ return FINISHES[s.finish].roughness
3559
+ },
3560
+ $finishTexture: s => {
3561
+ return FINISHES[s.finish].texture
3562
+ },
3563
+ $pickguardColor: s => {
3564
+ return PICKGUARDS[s.pickguard].color
3565
+ },
3566
+ $pickguardRoughness: s => {
3567
+ return PICKGUARDS[s.pickguard].roughness
3568
+ },
3569
+ $pickguardIridescence: s => {
3570
+ return PICKGUARDS[s.pickguard].iridescence
3571
+ },
3572
+ $neckColor: s => {
3573
+ return NECKS[s.neckWood].color
3574
+ },
3575
+ $neckTexture: s => {
3576
+ return NECKS[s.neckWood].texture
3577
+ },
3578
+ $fretboardColor: s => {
3579
+ return FRETBOARDS[s.fretboard].color
3580
+ },
3581
+ $fretboardTexture: s => {
3582
+ return FRETBOARDS[s.fretboard].texture
3583
+ },
3584
+ $inlayColor: s => {
3585
+ return FRETBOARDS[s.fretboard].inlay
3586
+ },
3587
+ $hardwareColor: s => {
3588
+ return HARDWARE[s.hardware].color
3589
+ },
3590
+ $hardwareMetalness: s => {
3591
+ return HARDWARE[s.hardware].metalness
3592
+ },
3593
+ $hardwareRoughness: s => {
3594
+ return HARDWARE[s.hardware].roughness
3595
+ },
3596
+ // Single coils match a black pickguard and are cream otherwise; humbuckers and P-90s are black
3597
+ $pickupCoverColor: s => {
3598
+ return s.pickups === 'SingleCoils' && s.pickguard !== 'Black' ? '#efe7d4' : '#141416'
3599
+ },
3600
+ // Gold leaf on the dark woods, black ink on maple
3601
+ $signatureColor: s => {
3602
+ return s.neckWood === 'Maple' ? '#1c1714' : '#e8c27a'
3603
+ },
3604
+ },
3605
+ })
3606
+ ```
3607
+
3608
+ ### ./src/handlers/place-order.ts
3609
+
3610
+ ```ts
3611
+ import { useCallback } from 'react'
3612
+
3613
+ // Stands in for Northfret's order service. Placing an order takes a moment. The service checks stock when the order
3614
+ // arrives, which the configurator cannot know beforehand: gold hardware is back-ordered, so it refuses that order with
3615
+ // its reason; any other order is accepted and the page shows the confirmation.
3616
+ export default phx.frontend.createPhoenixHandler('placeOrder', () => {
3617
+ const navigate = phx.frontend.useNavigate()
3618
+ const guitar = phx.frontend.useRootEntityState()
3619
+
3620
+ return {
3621
+ handler: useCallback(async () => {
3622
+ await new Promise(resolve => setTimeout(resolve, 1600))
3623
+
3624
+ if (guitar.hardware === 'Gold') {
3625
+ throw new Error(
3626
+ 'Gold hardware is back-ordered until next month. Choose chrome or black hardware, or order later.'
3627
+ )
3628
+ }
3629
+
3630
+ navigate('ordered')
3631
+ }, [guitar.hardware, navigate]),
3632
+ }
3633
+ })
3634
+ ```
3635
+
3636
+ ### ./src/invariants/guitar.ts
3637
+
3638
+ ```ts
3639
+ export default phx.domain.implementCustomInvariants({
3640
+ Guitar: {
3641
+ // `fixable`: a change that makes the signature too long is rejected, so the headstock never shows more than fits.
3642
+ // The message names the limit, so it is built here rather than in the spec.
3643
+ signatureLength: s => {
3644
+ if (s.signature.length <= s.$signatureLimit) {
3645
+ return []
3646
+ }
3647
+
3648
+ return [
3649
+ {
3650
+ type: 'signatureTooLong',
3651
+ severity: 'fixable',
3652
+ message: `The headstock fits ${s.$signatureLimit} characters. Shorten the signature.`,
3653
+ },
3654
+ ]
3655
+ },
3656
+ // `warning` in the spec: the change is kept, so a body chosen after the pickups does not undo them, and ordering
3657
+ // waits until the customer resolves it
3658
+ offsetHumbuckers: s => {
3659
+ return s.bodyShape !== 'Offset' || s.pickups !== 'Humbuckers'
3660
+ },
3661
+ },
3662
+ })
3663
+ ```
3664
+
3665
+ </details>