@phis/ui 0.4.1 → 0.4.3

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 (246) hide show
  1. package/components/cms/phi-cms-layout-renderer.js +4 -3
  2. package/components/cms/phi-cms-page-render-scope.d.ts +4 -5
  3. package/components/cms/phi-cms-page-render-scope.js +43 -22
  4. package/components/cms/phi-cms-render-diagnostic-client.js +7 -3
  5. package/components/cms/phi-cms-render-error-boundary.js +2 -1
  6. package/components/controls/phi-bullet-list-control.js +31 -2
  7. package/components/controls/phi-ribbon-control.d.ts +29 -0
  8. package/components/controls/phi-ribbon-control.js +53 -0
  9. package/components/controls/phi-table-control.d.ts +3 -0
  10. package/components/controls/phi-table-control.js +13 -1
  11. package/components/controls/phi-tree-control.js +3 -3
  12. package/components/forms/shared-form-provider-registry.js +1 -4
  13. package/components/media/asset-collection-runtime.js +6 -16
  14. package/components/media/media-font-metrics.d.ts +3 -0
  15. package/components/media/media-font-metrics.js +43 -0
  16. package/components/media/phi-area-upload-widget.js +53 -97
  17. package/components/media/phi-image-preview-data.d.ts +13 -0
  18. package/components/media/phi-image-preview-data.js +184 -9
  19. package/components/media/phi-media-picker-binding.js +16 -13
  20. package/components/media/phi-media-upload.d.ts +15 -8
  21. package/components/media/phi-media-upload.js +18 -9
  22. package/components/media/phi-site-font-assets.js +7 -8
  23. package/components/overlays/phi-overlay-container-client.js +16 -1
  24. package/components/regions/presets/phi-settings-page-shell-tree.js +48 -1
  25. package/components/runtime/runtime-controller-materialization.js +12 -1
  26. package/components/state/scoped-state-store.js +20 -3
  27. package/components/tables/client/phi-table-binding.d.ts +2 -1
  28. package/components/tables/client/phi-table-binding.js +45 -21
  29. package/components/trees/client/phi-tree-binding.js +19 -11
  30. package/components/widgets/client/bullet-list-entries-editor.d.ts +26 -0
  31. package/components/widgets/client/bullet-list-entries-editor.js +103 -0
  32. package/components/widgets/config/choice-shared.js +12 -13
  33. package/components/widgets/config/command-buttons.d.ts +5 -1
  34. package/components/widgets/config/command-buttons.js +38 -81
  35. package/components/widgets/config/control-signal-config.d.ts +3 -1
  36. package/components/widgets/config/control-signal-config.js +19 -17
  37. package/components/widgets/config/image-source-parser.d.ts +1 -0
  38. package/components/widgets/config/image-source-parser.js +7 -8
  39. package/components/widgets/config/mask.d.ts +6 -1
  40. package/components/widgets/config/mask.js +24 -1
  41. package/components/widgets/config/parser-primitives.js +72 -0
  42. package/components/widgets/config/stack-tabs.d.ts +1 -1
  43. package/components/widgets/config/stack-tabs.js +2 -3
  44. package/components/widgets/config/strict-config-reader.d.ts +40 -0
  45. package/components/widgets/config/strict-config-reader.js +174 -0
  46. package/components/widgets/label-sets/effects.js +2 -0
  47. package/components/widgets/label-types/effects.d.ts +1 -0
  48. package/components/widgets/label-types/effects.js +1 -0
  49. package/components/widgets/navigation-descriptor-resolver.server.js +20 -8
  50. package/components/widgets/shared/bullet-list-vocabulary.d.ts +17 -1
  51. package/components/widgets/shared/bullet-list-vocabulary.js +19 -2
  52. package/components/widgets/shared/card-body-client.d.ts +16 -4
  53. package/components/widgets/shared/card-body-client.js +81 -24
  54. package/components/widgets/shared/card-vocabulary.d.ts +21 -4
  55. package/components/widgets/shared/card-vocabulary.js +28 -2
  56. package/components/widgets/shared/markdown-body-client.d.ts +2 -1
  57. package/components/widgets/shared/markdown-body-client.js +3 -1
  58. package/components/widgets/shared/markdown-render.js +8 -1
  59. package/components/widgets/signals/control-signal-capabilities.d.ts +0 -18
  60. package/components/widgets/signals/control-signal-capabilities.js +10 -22
  61. package/components/workspace/catalog-store.d.ts +4 -3
  62. package/components/workspace/catalog-store.js +6 -5
  63. package/gateway/dashboard-cards-route.js +1 -0
  64. package/gateway/internal-references.d.ts +6 -1
  65. package/gateway/internal-references.js +48 -23
  66. package/gateway/site-cms-write-validation.d.ts +9 -0
  67. package/gateway/site-cms-write-validation.js +78 -0
  68. package/gateway/site-proxy.js +19 -3
  69. package/helpers/renderable-block-serialization.js +1 -1
  70. package/next/config.d.mts +3 -0
  71. package/next/config.mjs +28 -1
  72. package/package.json +2 -2
  73. package/plugins/runtime/phi-slot-child-viewport-effects-observer.js +5 -1
  74. package/plugins/runtime-modules/accounting/presets.d.ts +9 -0
  75. package/plugins/runtime-modules/accounting/presets.js +37 -0
  76. package/plugins/runtime-modules/accounting/server.d.ts +1 -4
  77. package/plugins/runtime-modules/accounting/server.js +4 -7
  78. package/plugins/runtime-modules/app/presets.d.ts +18 -0
  79. package/plugins/runtime-modules/app/presets.js +67 -0
  80. package/plugins/runtime-modules/app/server.d.ts +1 -4
  81. package/plugins/runtime-modules/app/server.js +4 -7
  82. package/plugins/runtime-modules/asset/controller/runtime.js +2 -2
  83. package/plugins/runtime-modules/asset/services/collection.js +9 -1
  84. package/plugins/runtime-modules/asset/services/options.js +2 -2
  85. package/plugins/runtime-modules/builder/area-settings-answer.js +2 -1
  86. package/plugins/runtime-modules/builder/authoring-canvas.js +6 -6
  87. package/plugins/runtime-modules/builder/cards.js +3 -5
  88. package/plugins/runtime-modules/builder/clients/builder-region-overrides.js +3 -3
  89. package/plugins/runtime-modules/builder/clients/canvas-widget.js +15 -54
  90. package/plugins/runtime-modules/builder/clients/inspector-config-field.d.ts +4 -1
  91. package/plugins/runtime-modules/builder/clients/inspector-config-field.js +5 -34
  92. package/plugins/runtime-modules/builder/clients/inspector-link-target-field.js +1 -1
  93. package/plugins/runtime-modules/builder/clients/inspector-section-widget.js +8 -8
  94. package/plugins/runtime-modules/builder/clients/inspector-title.js +6 -6
  95. package/plugins/runtime-modules/builder/clients/layout-inspector.d.ts +1 -1
  96. package/plugins/runtime-modules/builder/clients/layout-inspector.js +11 -25
  97. package/plugins/runtime-modules/builder/clients/mode-switch.js +1 -1
  98. package/plugins/runtime-modules/builder/clients/pages-header.js +11 -11
  99. package/plugins/runtime-modules/builder/clients/pages-workspace.js +1 -1
  100. package/plugins/runtime-modules/builder/clients/region-visibility-fields.js +1 -1
  101. package/plugins/runtime-modules/builder/clients/runtime-module-authoring-boundary.js +1 -1
  102. package/plugins/runtime-modules/builder/clients/shells-workspace.js +15 -31
  103. package/plugins/runtime-modules/builder/clients/structure-canvas.js +1 -1
  104. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.d.ts +26 -0
  105. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.js +36 -0
  106. package/plugins/runtime-modules/builder/clients/widget-effects-editor.js +1 -1
  107. package/plugins/runtime-modules/builder/clients/widget-inspector.js +1 -0
  108. package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +2 -2
  109. package/plugins/runtime-modules/builder/cms-instance-allocator.js +8 -28
  110. package/plugins/runtime-modules/builder/controller/client.d.ts +0 -15
  111. package/plugins/runtime-modules/builder/controller/definition.d.ts +0 -15
  112. package/plugins/runtime-modules/builder/controller/definition.js +0 -8
  113. package/plugins/runtime-modules/builder/controller/draft-command-controller.d.ts +1 -2
  114. package/plugins/runtime-modules/builder/controller/draft-command-controller.js +11 -30
  115. package/plugins/runtime-modules/builder/controller/draft-status-controller.js +15 -2
  116. package/plugins/runtime-modules/builder/controller/inspector-controller.d.ts +1 -2
  117. package/plugins/runtime-modules/builder/controller/inspector-controller.js +4 -7
  118. package/plugins/runtime-modules/builder/controller/page-controller.d.ts +1 -2
  119. package/plugins/runtime-modules/builder/controller/page-controller.js +16 -68
  120. package/plugins/runtime-modules/builder/controller/server-definition.d.ts +0 -15
  121. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.d.ts +2 -5
  122. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.js +6 -50
  123. package/plugins/runtime-modules/builder/controller/workspace-controller.d.ts +2 -3
  124. package/plugins/runtime-modules/builder/controller/workspace-controller.js +89 -153
  125. package/plugins/runtime-modules/builder/data-providers.d.ts +1 -0
  126. package/plugins/runtime-modules/builder/data-providers.js +1 -0
  127. package/plugins/runtime-modules/builder/definition.d.ts +1 -0
  128. package/plugins/runtime-modules/builder/developer-workspace-store.d.ts +64 -30
  129. package/plugins/runtime-modules/builder/developer-workspace-store.js +255 -61
  130. package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +1 -1
  131. package/plugins/runtime-modules/builder/history.d.ts +3 -1
  132. package/plugins/runtime-modules/builder/options-providers.js +3 -2
  133. package/plugins/runtime-modules/builder/page-catalog-client.js +19 -7
  134. package/plugins/runtime-modules/builder/page-meta-form.js +1 -0
  135. package/plugins/runtime-modules/builder/page-references-provider.js +1 -1
  136. package/plugins/runtime-modules/builder/persistence.js +33 -69
  137. package/plugins/runtime-modules/builder/public-route-collisions.d.ts +2 -2
  138. package/plugins/runtime-modules/builder/public-route-collisions.js +6 -19
  139. package/plugins/runtime-modules/builder/render-root-node-preview.server.js +20 -3
  140. package/plugins/runtime-modules/builder/render-root-node-scaffold.js +3 -3
  141. package/plugins/runtime-modules/builder/runtime-module-selection.d.ts +2 -12
  142. package/plugins/runtime-modules/builder/runtime-module-selection.js +16 -42
  143. package/plugins/runtime-modules/builder/services/navigation-table.js +9 -15
  144. package/plugins/runtime-modules/builder/services/page-source-tree.js +8 -8
  145. package/plugins/runtime-modules/builder/services/runtime-modules-table.js +10 -10
  146. package/plugins/runtime-modules/builder/services/signal-routes-table.js +3 -3
  147. package/plugins/runtime-modules/builder/signal-route-selection.d.ts +1 -2
  148. package/plugins/runtime-modules/builder/signal-route-selection.js +2 -2
  149. package/plugins/runtime-modules/builder/signal-wiring-options.js +3 -2
  150. package/plugins/runtime-modules/builder/structure-dnd.js +8 -61
  151. package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +13 -10
  152. package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +12 -18
  153. package/plugins/runtime-modules/builder/workspace-catalog-actions.d.ts +55 -0
  154. package/plugins/runtime-modules/builder/workspace-catalog-actions.js +142 -0
  155. package/plugins/runtime-modules/client-manifests/common.js +1 -1
  156. package/plugins/runtime-modules/{runtime-calendar-adapters.js → core/calendar-adapters.js} +2 -2
  157. package/plugins/runtime-modules/{client-calendar-adapters/core.js → core/client-calendar-adapters.js} +1 -1
  158. package/plugins/runtime-modules/core/content-entries/table.js +1 -1
  159. package/plugins/runtime-modules/core/data-providers.d.ts +3 -0
  160. package/plugins/runtime-modules/core/data-providers.js +2 -1
  161. package/plugins/runtime-modules/core/definition.d.ts +3 -0
  162. package/plugins/runtime-modules/core/definition.js +1 -1
  163. package/plugins/runtime-modules/core/messaging/data-providers.d.ts +1 -0
  164. package/plugins/runtime-modules/core/messaging/data-providers.js +1 -0
  165. package/plugins/runtime-modules/core/messaging/thread-inbox-table.js +1 -1
  166. package/plugins/runtime-modules/core/server.js +1 -1
  167. package/plugins/runtime-modules/core/themes.d.ts +44 -0
  168. package/plugins/runtime-modules/core/themes.js +12 -0
  169. package/plugins/runtime-modules/core/widgets/card/collection-view-binding.js +25 -9
  170. package/plugins/runtime-modules/core/widgets/card/config.d.ts +296 -254
  171. package/plugins/runtime-modules/core/widgets/card/config.js +52 -3
  172. package/plugins/runtime-modules/core/widgets/card/editor.js +30 -3
  173. package/plugins/runtime-modules/core/widgets/card/server.d.ts +3 -0
  174. package/plugins/runtime-modules/core/widgets/card/server.js +31 -3
  175. package/plugins/runtime-modules/core/widgets/collection-view/asset-binding.js +7 -6
  176. package/plugins/runtime-modules/core/widgets/collection-view/config.js +9 -2
  177. package/plugins/runtime-modules/core/widgets/description/config.d.ts +1 -25
  178. package/plugins/runtime-modules/core/widgets/description/editor.js +4 -61
  179. package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +1 -25
  180. package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +1 -25
  181. package/plugins/runtime-modules/core/widgets/image/config.js +2 -2
  182. package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +3 -1
  183. package/plugins/runtime-modules/core/widgets/markdown/config.js +14 -0
  184. package/plugins/runtime-modules/core/widgets/markdown/editor.js +2 -3
  185. package/plugins/runtime-modules/core/widgets/result/config.d.ts +4 -25
  186. package/plugins/runtime-modules/core/widgets/result/config.js +2 -0
  187. package/plugins/runtime-modules/core/widgets/result/server.js +25 -19
  188. package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +1 -25
  189. package/plugins/runtime-modules/core/widgets/slot-upload/client.js +11 -2
  190. package/plugins/runtime-modules/core/widgets/table/client.js +8 -4
  191. package/plugins/runtime-modules/core/widgets/table/config.d.ts +3 -83
  192. package/plugins/runtime-modules/core/widgets/table/config.js +302 -452
  193. package/plugins/runtime-modules/core/widgets/tree/config.js +5 -2
  194. package/plugins/runtime-modules/dashboard/card-rows.d.ts +5 -0
  195. package/plugins/runtime-modules/dashboard/card-rows.js +59 -0
  196. package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +4 -6
  197. package/plugins/runtime-modules/dashboard/services/collection.js +17 -7
  198. package/plugins/runtime-modules/groups/data-providers.d.ts +2 -0
  199. package/plugins/runtime-modules/groups/data-providers.js +3 -3
  200. package/plugins/runtime-modules/groups/definition.d.ts +2 -0
  201. package/plugins/runtime-modules/localization/data-providers.d.ts +3 -0
  202. package/plugins/runtime-modules/localization/data-providers.js +2 -0
  203. package/plugins/runtime-modules/localization/definition.d.ts +3 -0
  204. package/plugins/runtime-modules/localization/services/table.js +3 -7
  205. package/plugins/runtime-modules/observability/data-providers.d.ts +1 -0
  206. package/plugins/runtime-modules/observability/data-providers.js +1 -0
  207. package/plugins/runtime-modules/observability/definition.d.ts +1 -0
  208. package/plugins/runtime-modules/observability/module.d.ts +1 -0
  209. package/plugins/runtime-modules/observability/services/table.js +1 -1
  210. package/plugins/runtime-modules/phi-default-site-area-preset-tree.js +2 -105
  211. package/plugins/runtime-modules/public/presets.d.ts +115 -0
  212. package/plugins/runtime-modules/{area-base-presets.js → public/presets.js} +19 -119
  213. package/plugins/runtime-modules/public/server.d.ts +1 -4
  214. package/plugins/runtime-modules/public/server.js +4 -7
  215. package/plugins/runtime-modules/public/trees/phi-default-pub-error-page-tree.js +7 -8
  216. package/plugins/runtime-modules/public-base-page-references.d.ts +1 -1
  217. package/plugins/runtime-modules/public-base-page-references.js +1 -1
  218. package/plugins/runtime-modules/revisions/controller/client.js +11 -11
  219. package/plugins/runtime-modules/revisions/services/table.js +3 -3
  220. package/plugins/runtime-modules/theme/trees/phi-default-builder-theme-page-tree.js +22 -0
  221. package/plugins/runtime-modules/theme/widgets/brand-controls/client.d.ts +2 -1
  222. package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +93 -58
  223. package/plugins/runtime-modules/theme/widgets/brand-controls/config.d.ts +2 -1
  224. package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +23 -22
  225. package/plugins/runtime-modules/theme/widgets/brand-controls/plugin.js +1 -1
  226. package/plugins/runtime-modules/theme-descriptors.js +1 -1
  227. package/plugins/runtime-modules/user-management/data-providers.d.ts +2 -0
  228. package/plugins/runtime-modules/user-management/data-providers.js +1 -0
  229. package/plugins/runtime-modules/user-management/definition.d.ts +2 -0
  230. package/styles/control-shape.css +11 -11
  231. package/styles/controls.css +29 -3
  232. package/styles/layout.css +16 -7
  233. package/types/cms-config.js +142 -185
  234. package/types/cms-plugins.d.ts +4 -11
  235. package/types/core-widget-placements.d.ts +17 -3
  236. package/types/dashboard-cards.d.ts +9 -0
  237. package/types/media.d.ts +0 -3
  238. package/types/renderable-block.d.ts +6 -1
  239. package/types/table-widget.d.ts +9 -0
  240. package/types/table-widget.js +20 -0
  241. package/types.d.ts +1 -1
  242. package/widget-config.d.ts +2 -0
  243. package/widget-config.js +6 -0
  244. package/plugins/runtime-modules/area-base-presets.d.ts +0 -139
  245. /package/plugins/runtime-modules/{runtime-calendar-adapters.d.ts → core/calendar-adapters.d.ts} +0 -0
  246. /package/plugins/runtime-modules/{client-calendar-adapters/core.d.ts → core/client-calendar-adapters.d.ts} +0 -0
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { PHI_WORKSPACE_CATALOG_SCOPE } from "../../../components/workspace/catalog-store";
2
3
  import { isPhiBuilderAreaKey } from "../../../constants/cms-areas";
3
4
  import { prunePhiSignalRoutesFromConfig, } from "../../../helpers/signal-route-lifecycle";
4
5
  import { createPhiSignalCorrelationId } from "../../../components/runtime/runtime-signal-bus";
@@ -6,6 +7,7 @@ import { useRef, useSyncExternalStore } from "react";
6
7
  import { createPhiPluginStateStore } from "../../../components/state/plugin-state-store";
7
8
  import { phiWorkspaceCatalogStore, } from "../../../components/workspace/catalog-store";
8
9
  import { phiBuilderHistory } from "./history";
10
+ import { openPhiWorkspacePage, restorePhiWorkspaceCatalog, setPhiWorkspacePageKey, } from "./workspace-catalog-actions";
9
11
  export function normalizePhiDeveloperBuilderArea(scopeKey) {
10
12
  return isPhiBuilderAreaKey(scopeKey) ? scopeKey : "public";
11
13
  }
@@ -54,14 +56,14 @@ function createDefaultBuilderState() {
54
56
  }
55
57
  export const builderWorkspaceStore = createPhiPluginStateStore("@phis/ui/developer-workspace", createDefaultBuilderState);
56
58
  const builderEffectsCommitters = new Map();
57
- export function openPhiDeveloperBuilderEffectsEditor(scopeKey, effects, target, onCommit) {
59
+ export function openPhiDeveloperBuilderEffectsEditor(effects, target, onCommit) {
58
60
  const correlationId = createPhiSignalCorrelationId();
59
- const previousRequest = builderWorkspaceStore.getSnapshot(scopeKey).effectsEditorRequest;
61
+ const previousRequest = builderWorkspaceStore.getSnapshot(PHI_WORKSPACE_CATALOG_SCOPE).effectsEditorRequest;
60
62
  if (previousRequest) {
61
63
  builderEffectsCommitters.delete(previousRequest.correlationId);
62
64
  }
63
65
  builderEffectsCommitters.set(correlationId, onCommit);
64
- builderWorkspaceStore.patch(scopeKey, (current) => ({
66
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
65
67
  ...current,
66
68
  effectsEditorRequest: { correlationId, effects, target, preview: null },
67
69
  }));
@@ -74,8 +76,8 @@ export function openPhiDeveloperBuilderEffectsEditor(scopeKey, effects, target,
74
76
  * checked because the editor that sent this may already be closed -- a late message from a gesture that
75
77
  * ended must not put a picture back on a node whose editor is gone.
76
78
  */
77
- export function previewPhiDeveloperBuilderEffects(scopeKey, correlationId, preview) {
78
- builderWorkspaceStore.patch(scopeKey, (current) => (current.effectsEditorRequest?.correlationId === correlationId
79
+ export function previewPhiDeveloperBuilderEffects(correlationId, preview) {
80
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => (current.effectsEditorRequest?.correlationId === correlationId
79
81
  ? { ...current, effectsEditorRequest: { ...current.effectsEditorRequest, preview } }
80
82
  : current));
81
83
  }
@@ -91,10 +93,10 @@ export function readPhiDeveloperBuilderEffectsPreview(state, kind, blockId) {
91
93
  return null;
92
94
  return request.target.kind === kind && request.target.blockId === blockId ? request.preview : null;
93
95
  }
94
- export function completePhiDeveloperBuilderEffectsEditor(scopeKey, request, effects) {
96
+ export function completePhiDeveloperBuilderEffectsEditor(request, effects) {
95
97
  const onCommit = builderEffectsCommitters.get(request.correlationId);
96
98
  builderEffectsCommitters.delete(request.correlationId);
97
- builderWorkspaceStore.patch(scopeKey, (current) => ({
99
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
98
100
  ...current,
99
101
  effectsEditorRequest: current.effectsEditorRequest?.correlationId === request.correlationId
100
102
  ? null
@@ -111,17 +113,17 @@ export function completePhiDeveloperBuilderEffectsEditor(scopeKey, request, effe
111
113
  * which Areas, and which addresses are contested -- and the answer is what enables the Module. A second
112
114
  * question replaces the first, because there is only one dialog and only one gesture behind it.
113
115
  */
114
- export function openPhiBuilderPublicRouteCollisionRequest(scopeKey, request) {
116
+ export function openPhiBuilderPublicRouteCollisionRequest(request) {
115
117
  const correlationId = createPhiSignalCorrelationId();
116
- builderWorkspaceStore.patch(scopeKey, (current) => ({
118
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
117
119
  ...current,
118
120
  publicRouteCollisionRequest: { ...request, correlationId },
119
121
  }));
120
122
  return correlationId;
121
123
  }
122
124
  /** The address being typed for one contested route. */
123
- export function answerPhiBuilderPublicRouteCollision(scopeKey, presetKey, path) {
124
- builderWorkspaceStore.patch(scopeKey, (current) => {
125
+ export function answerPhiBuilderPublicRouteCollision(presetKey, path) {
126
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
125
127
  const request = current.publicRouteCollisionRequest;
126
128
  if (!request) {
127
129
  return current;
@@ -141,21 +143,21 @@ export function answerPhiBuilderPublicRouteCollision(scopeKey, presetKey, path)
141
143
  * Opened only when there is something to say: a Module nothing draws from is switched off without a
142
144
  * word, because a dialog that always says "nothing happens" is a dialog nobody reads.
143
145
  */
144
- export function openPhiBuilderModuleDeactivationRequest(scopeKey, request) {
146
+ export function openPhiBuilderModuleDeactivationRequest(request) {
145
147
  const correlationId = createPhiSignalCorrelationId();
146
- builderWorkspaceStore.patch(scopeKey, (current) => ({
148
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
147
149
  ...current,
148
150
  moduleDeactivationRequest: { ...request, correlationId },
149
151
  }));
150
152
  return correlationId;
151
153
  }
152
- export function closePhiBuilderModuleDeactivationRequest(scopeKey) {
153
- builderWorkspaceStore.patch(scopeKey, (current) => current.moduleDeactivationRequest === null
154
+ export function closePhiBuilderModuleDeactivationRequest() {
155
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => current.moduleDeactivationRequest === null
154
156
  ? current
155
157
  : { ...current, moduleDeactivationRequest: null });
156
158
  }
157
- export function closePhiBuilderPublicRouteCollisionRequest(scopeKey) {
158
- builderWorkspaceStore.patch(scopeKey, (current) => current.publicRouteCollisionRequest === null
159
+ export function closePhiBuilderPublicRouteCollisionRequest() {
160
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => current.publicRouteCollisionRequest === null
159
161
  ? current
160
162
  : { ...current, publicRouteCollisionRequest: null });
161
163
  }
@@ -165,7 +167,7 @@ export function closePhiBuilderPublicRouteCollisionRequest(scopeKey) {
165
167
  * change, a new Page -- which used to be four copies that had drifted apart. `chrome` resets the
166
168
  * chrome controls as well, `closePages` folds the Pages panel.
167
169
  */
168
- export function resetPhiBuilderSelectionToPage(current, pageKey, options = {}) {
170
+ function resetPhiBuilderSelectionToPage(current, pageKey, options = {}) {
169
171
  return {
170
172
  ...current,
171
173
  nodeKey: `page:${pageKey}`,
@@ -179,11 +181,9 @@ export function resetPhiBuilderSelectionToPage(current, pageKey, options = {}) {
179
181
  ...(options.closePages ? { pagesOpen: false } : {}),
180
182
  };
181
183
  }
182
- export function selectPhiDeveloperBuilderNode(scopeKey, selection) {
183
- builderWorkspaceStore.patch(scopeKey, (current) => ({
184
+ export function selectPhiDeveloperBuilderNode(selection) {
185
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
184
186
  ...current,
185
- ...(selection.area ? { area: selection.area } : {}),
186
- ...(selection.pageKey ? { pageKey: selection.pageKey } : {}),
187
187
  nodeKey: selection.nodeKey,
188
188
  nodeId: selection.nodeId ?? null,
189
189
  nodeKind: selection.nodeKind,
@@ -220,8 +220,8 @@ export function selectPhiDeveloperBuilderNode(scopeKey, selection) {
220
220
  }));
221
221
  }
222
222
  const builderRegionDraftStore = createPhiPluginStateStore("@phis/ui/developer-region-drafts", () => ({}));
223
- export function patchPhiDeveloperBuilderState(scopeKey, next) {
224
- builderWorkspaceStore.patch(scopeKey, (current) => ({
223
+ export function patchPhiDeveloperBuilderState(next) {
224
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
225
225
  ...current,
226
226
  ...next,
227
227
  }));
@@ -243,7 +243,7 @@ const PHI_WORKSPACE_CATALOG_KEYS = [
243
243
  "publicRouteClaims",
244
244
  "publicRoutePaths",
245
245
  ];
246
- export function splitWorkspacePatch(next) {
246
+ function splitWorkspacePatch(next) {
247
247
  const catalog = {};
248
248
  const tool = {};
249
249
  for (const [key, value] of Object.entries(next)) {
@@ -410,7 +410,7 @@ export function setPhiDeveloperRegionDraftsWithHistory(drafts, options) {
410
410
  * of an older one beside half of a newer one would be a state no Site was ever in.
411
411
  */
412
412
  export function setPhiDeveloperBuilderAreaRootRoutes(areaRootRoutes) {
413
- builderWorkspaceStore.patch("public", (current) => JSON.stringify(current.areaRootRoutes) === JSON.stringify(areaRootRoutes)
413
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => JSON.stringify(current.areaRootRoutes) === JSON.stringify(areaRootRoutes)
414
414
  ? current
415
415
  : { ...current, areaRootRoutes });
416
416
  }
@@ -437,7 +437,7 @@ export function readPhiBuilderEffectiveAreaRootRoute(state, area) {
437
437
  * rather than guessing at it: the value that was sent is the value the server now holds.
438
438
  */
439
439
  export function commitPhiDeveloperBuilderAreaConfig(area, committed) {
440
- builderWorkspaceStore.patch("public", (current) => {
440
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
441
441
  const rootRoutes = { ...current.areaRootRoutes, [area]: committed.rootRoute };
442
442
  const areaMeta = { ...current.areaMeta, [area]: committed.meta };
443
443
  return JSON.stringify(current.areaRootRoutes) === JSON.stringify(rootRoutes) &&
@@ -469,7 +469,7 @@ export function readPhiBuilderStoredAreaRootRoute(state, area) {
469
469
  * default back and the write has to remove the stored config.
470
470
  */
471
471
  export function setPhiDeveloperBuilderAreaRootRoute(area, rootRoute) {
472
- builderWorkspaceStore.patch("public", (current) => {
472
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
473
473
  const next = { ...current.areaRootRouteDrafts };
474
474
  if (rootRoute === undefined) {
475
475
  delete next[area];
@@ -482,19 +482,19 @@ export function setPhiDeveloperBuilderAreaRootRoute(area, rootRoute) {
482
482
  }
483
483
  /** What the Areas answered about being found, as the server sent it with the workspace. */
484
484
  export function setPhiDeveloperBuilderAreaMetaBaseline(areaMeta) {
485
- builderWorkspaceStore.patch("public", (current) => JSON.stringify(current.areaMeta) === JSON.stringify(areaMeta)
485
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => JSON.stringify(current.areaMeta) === JSON.stringify(areaMeta)
486
486
  ? current
487
487
  : { ...current, areaMeta });
488
488
  }
489
489
  /** What each Area's Shell tells its Controllers, as the server sent it with the workspace. */
490
490
  export function setPhiDeveloperBuilderAreaControllerSettings(settingsByArea) {
491
- builderWorkspaceStore.patch("public", (current) => JSON.stringify(current.areaControllerSettings) === JSON.stringify(settingsByArea)
491
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => JSON.stringify(current.areaControllerSettings) === JSON.stringify(settingsByArea)
492
492
  ? current
493
493
  : { ...current, areaControllerSettings: settingsByArea });
494
494
  }
495
495
  /** What a Page tells its Controllers, by its Page meta draft key, as the server sent it with the Page. */
496
496
  export function setPhiDeveloperBuilderPageControllerSettings(draftKey, settings) {
497
- builderWorkspaceStore.patch("public", (current) => JSON.stringify(current.pageControllerSettings[draftKey]) === JSON.stringify(settings)
497
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => JSON.stringify(current.pageControllerSettings[draftKey]) === JSON.stringify(settings)
498
498
  ? current
499
499
  : { ...current, pageControllerSettings: { ...current.pageControllerSettings, [draftKey]: settings } });
500
500
  }
@@ -517,7 +517,7 @@ export function readPhiBuilderEffectiveAreaMeta(state, area) {
517
517
  * is an Area that stated it has nothing to say.
518
518
  */
519
519
  export function restorePhiDeveloperBuilderAreaMeta(area, meta) {
520
- builderWorkspaceStore.patch("public", (current) => {
520
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
521
521
  const next = { ...current.areaMetaDrafts };
522
522
  if (meta === undefined) {
523
523
  delete next[area];
@@ -540,7 +540,7 @@ export function mergePhiDeveloperRegionDrafts(drafts) {
540
540
  });
541
541
  }
542
542
  export function mergePhiDeveloperPagePresetDrafts(drafts) {
543
- builderWorkspaceStore.patch("public", (current) => ({
543
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
544
544
  ...current,
545
545
  pagePresetDrafts: {
546
546
  ...current.pagePresetDrafts,
@@ -550,7 +550,7 @@ export function mergePhiDeveloperPagePresetDrafts(drafts) {
550
550
  }
551
551
  export function mergePhiDeveloperPageMetaDrafts(area, drafts) {
552
552
  void area;
553
- builderWorkspaceStore.patch("public", (current) => {
553
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
554
554
  let hasChanges = false;
555
555
  const missingDrafts = {};
556
556
  for (const [draftKey, draft] of Object.entries(drafts)) {
@@ -573,7 +573,7 @@ export function mergePhiDeveloperPageMetaDrafts(area, drafts) {
573
573
  }
574
574
  export function mergePhiDeveloperDeletedPageDrafts(area, drafts) {
575
575
  void area;
576
- builderWorkspaceStore.patch("public", (current) => {
576
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
577
577
  let hasChanges = false;
578
578
  const nextDrafts = { ...current.deletedPageDrafts };
579
579
  for (const [draftKey, deleted] of Object.entries(drafts)) {
@@ -604,51 +604,52 @@ export function usePhiDeveloperRegionDraft(draftKey) {
604
604
  export function usePhiDeveloperRegionDraftsValue(selector) {
605
605
  return builderRegionDraftStore.useStoreSelector("default", selector);
606
606
  }
607
- const mergedWorkspaceSnapshots = new Map();
607
+ /*
608
+ * The Builder's state is one per workspace host, not one per Area (`PHI_WORKSPACE_CATALOG_SCOPE`). What
609
+ * belongs to an Area is keyed by Area inside it -- its Module selection, its Page catalog, its meta, its
610
+ * drafts -- and which Area is open is the catalog's `area`. A scope per Area was promised once and never
611
+ * used: every caller passed "public".
612
+ */
613
+ let mergedWorkspaceSnapshot = null;
608
614
  /**
609
615
  * Merging on every read would hand `useSyncExternalStore` a new object each time and never settle, so
610
616
  * the merged view is kept until one of the two sources actually changes.
611
617
  */
612
- function readMergedWorkspaceSnapshot(area) {
613
- const tool = builderWorkspaceStore.getSnapshot(area);
614
- const catalog = phiWorkspaceCatalogStore.getSnapshot(area);
615
- const cached = mergedWorkspaceSnapshots.get(area);
618
+ function readMergedWorkspaceSnapshot() {
619
+ const tool = builderWorkspaceStore.getSnapshot(PHI_WORKSPACE_CATALOG_SCOPE);
620
+ const catalog = phiWorkspaceCatalogStore.getSnapshot(PHI_WORKSPACE_CATALOG_SCOPE);
621
+ const cached = mergedWorkspaceSnapshot;
616
622
  if (cached && Object.is(cached.tool, tool) && Object.is(cached.catalog, catalog)) {
617
623
  return cached.merged;
618
624
  }
619
625
  const merged = { ...tool, ...catalog };
620
- mergedWorkspaceSnapshots.set(area, { tool, catalog, merged });
626
+ mergedWorkspaceSnapshot = { tool, catalog, merged };
621
627
  return merged;
622
628
  }
623
629
  /** The whole merged view, for the few places that work with the state as one object. */
624
- export function usePhiDeveloperBuilderWorkspaceState(area) {
625
- return usePhiDeveloperBuilderStateValue(area, (state) => state);
630
+ export function usePhiDeveloperBuilderWorkspaceState() {
631
+ return usePhiDeveloperBuilderStateValue((state) => state);
626
632
  }
627
- const mergedHydrationSnapshots = new Map();
633
+ let mergedHydrationSnapshot = null;
628
634
  /** Hydration must render what the server rendered, so it merges the two hydration values, not the live ones. */
629
- function readMergedHydrationSnapshot(area) {
630
- const cached = mergedHydrationSnapshots.get(area);
631
- if (cached) {
632
- return cached;
633
- }
634
- const merged = {
635
- ...builderWorkspaceStore.getHydrationSnapshot(area),
636
- ...phiWorkspaceCatalogStore.getHydrationSnapshot(area),
635
+ function readMergedHydrationSnapshot() {
636
+ mergedHydrationSnapshot ??= {
637
+ ...builderWorkspaceStore.getHydrationSnapshot(PHI_WORKSPACE_CATALOG_SCOPE),
638
+ ...phiWorkspaceCatalogStore.getHydrationSnapshot(PHI_WORKSPACE_CATALOG_SCOPE),
637
639
  };
638
- mergedHydrationSnapshots.set(area, merged);
639
- return merged;
640
+ return mergedHydrationSnapshot;
640
641
  }
641
- export function usePhiDeveloperBuilderStateValue(area, selector) {
642
+ export function usePhiDeveloperBuilderStateValue(selector) {
642
643
  const selectedRef = useRef(null);
643
644
  return useSyncExternalStore((listener) => {
644
- const unsubscribeTool = builderWorkspaceStore.subscribe(area, listener);
645
- const unsubscribeCatalog = phiWorkspaceCatalogStore.subscribe(area, listener);
645
+ const unsubscribeTool = builderWorkspaceStore.subscribe(PHI_WORKSPACE_CATALOG_SCOPE, listener);
646
+ const unsubscribeCatalog = phiWorkspaceCatalogStore.subscribe(PHI_WORKSPACE_CATALOG_SCOPE, listener);
646
647
  return () => {
647
648
  unsubscribeTool();
648
649
  unsubscribeCatalog();
649
650
  };
650
651
  }, () => {
651
- const source = readMergedWorkspaceSnapshot(area);
652
+ const source = readMergedWorkspaceSnapshot();
652
653
  const cached = selectedRef.current;
653
654
  if (cached && Object.is(cached.source, source)) {
654
655
  return cached.selected;
@@ -656,11 +657,204 @@ export function usePhiDeveloperBuilderStateValue(area, selector) {
656
657
  const selected = selector(source);
657
658
  selectedRef.current = { source, selected };
658
659
  return selected;
659
- }, () => selector(readMergedHydrationSnapshot(area)));
660
+ }, () => selector(readMergedHydrationSnapshot()));
660
661
  }
661
662
  export function getPhiDeveloperRegionDraftsSnapshot() {
662
663
  return builderRegionDraftStore.getSnapshot("default");
663
664
  }
664
- export function getPhiDeveloperBuilderStateSnapshot(area = "public") {
665
- return readMergedWorkspaceSnapshot(area);
665
+ export function getPhiDeveloperBuilderStateSnapshot() {
666
+ return readMergedWorkspaceSnapshot();
667
+ }
668
+ /*
669
+ * The Builder's own actions on its tool state.
670
+ *
671
+ * Everything that writes the tool store (`builderWorkspaceStore`) is in this file, by name: what a
672
+ * caller wants -- open a Page, show the Inspector, take the next node id -- not a patch of whatever
673
+ * fields it holds. Forty-two writes in nine files did this before, and the selection wrote an Area and a
674
+ * Page into a store whose merged view always took both from the catalog.
675
+ * `scripts/validate-builder-store-ownership.mjs` keeps it in one place.
676
+ */
677
+ /** Opens a Page of an Area: the catalog says what is open, the selection starts at the Page itself. */
678
+ export function openPhiBuilderPage(area, pageKey, options = {}) {
679
+ openPhiWorkspacePage(area, pageKey);
680
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => resetPhiBuilderSelectionToPage(current, pageKey, options));
681
+ }
682
+ /** Opens a Page of the Area that is open. */
683
+ export function openPhiBuilderPageInOpenArea(pageKey, options = {}) {
684
+ setPhiWorkspacePageKey(pageKey);
685
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => resetPhiBuilderSelectionToPage(current, pageKey, options));
686
+ }
687
+ /** Points the selection at a Page the catalog opened by itself, leaving the rest of the tool as it is. */
688
+ export function selectPhiBuilderPageNode(pageKey) {
689
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({ ...current, nodeKey: `page:${pageKey}` }));
690
+ }
691
+ function setToolField(key, value) {
692
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => (Object.is(current[key], value) ? current : { ...current, [key]: value }));
693
+ }
694
+ export function setPhiBuilderCommandWorkspace(commandWorkspace) {
695
+ setToolField("commandWorkspace", commandWorkspace);
696
+ }
697
+ export function setPhiBuilderMode(builderMode) {
698
+ setToolField("builderMode", builderMode);
699
+ }
700
+ export function setPhiBuilderDebugScaffold(debugScaffold) {
701
+ setToolField("debugScaffold", debugScaffold);
702
+ }
703
+ export function openPhiBuilderPagesPanel() {
704
+ setToolField("pagesOpen", true);
705
+ }
706
+ export function setPhiBuilderInspectorOpen(inspectorOpen) {
707
+ setToolField("inspectorOpen", inspectorOpen);
708
+ }
709
+ export function setPhiBuilderSelectedLayoutAnchor(anchor) {
710
+ setToolField("selectedLayoutAnchor", anchor);
711
+ }
712
+ /** A Page's meta as edited, laid over what its draft held. */
713
+ export function setPhiBuilderPageMetaDraft(draftKey, meta) {
714
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
715
+ ...current,
716
+ pageMetaDrafts: {
717
+ ...current.pageMetaDrafts,
718
+ [draftKey]: { ...(current.pageMetaDrafts[draftKey] ?? {}), ...meta },
719
+ },
720
+ }));
721
+ }
722
+ /** A Page created in the Builder: its meta, and that it is not deleted. */
723
+ export function startPhiBuilderNewPageDrafts(input) {
724
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
725
+ ...current,
726
+ pageMetaDrafts: { ...current.pageMetaDrafts, [input.metaDraftKey]: input.meta },
727
+ deletedPageDrafts: { ...current.deletedPageDrafts, [input.deleteDraftKey]: false },
728
+ }));
729
+ }
730
+ /** A Page marked for deletion with the next save. */
731
+ export function markPhiBuilderPageDeleted(deleteDraftKey) {
732
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
733
+ ...current,
734
+ deletedPageDrafts: { ...current.deletedPageDrafts, [deleteDraftKey]: true },
735
+ }));
736
+ }
737
+ /**
738
+ * The Areas whose Module selection has unsaved edits, kept as Builder tool state so the Modules
739
+ * workspace commands know which Areas a site-wide save or publish has to reach. Undo does not clear
740
+ * an Area from here -- a save of an unchanged selection is a no-op draft, while a missed Area would
741
+ * silently publish stale state.
742
+ */
743
+ export function markPhiBuilderModuleAreasDirty(areas) {
744
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (tool) => {
745
+ const next = new Set(tool.modulesDirtyAreas ?? []);
746
+ for (const area of areas)
747
+ next.add(area);
748
+ return next.size === (tool.modulesDirtyAreas ?? []).length ? tool : { ...tool, modulesDirtyAreas: [...next] };
749
+ });
750
+ }
751
+ export function clearPhiBuilderModuleAreasDirty() {
752
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (tool) => (tool.modulesDirtyAreas?.length ?? 0) === 0 ? tool : { ...tool, modulesDirtyAreas: [] });
753
+ }
754
+ const EMPTY_SIGNAL_WIRING_SESSION = {
755
+ senderAddress: null,
756
+ senderCapabilityId: null,
757
+ receiverAddress: null,
758
+ receiverCapabilityId: null,
759
+ fieldKey: null,
760
+ };
761
+ function readWiringValue(value) {
762
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
763
+ }
764
+ /** What the wiring dialog has chosen so far; a value that moved drops everything chosen below it. */
765
+ export function patchPhiBuilderSignalWiringSession(values) {
766
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
767
+ const senderAddress = readWiringValue(values.senderAddress) ?? current.signalWiring.senderAddress;
768
+ // A cascade only holds if the values below the one that moved are dropped: keeping a capability that
769
+ // belongs to the previous endpoint would build a route the runtime silently ignores.
770
+ const senderChanged = senderAddress !== current.signalWiring.senderAddress;
771
+ const senderCapabilityId = senderChanged
772
+ ? null
773
+ : readWiringValue(values.senderCapabilityId) ?? current.signalWiring.senderCapabilityId;
774
+ const capabilityChanged = senderCapabilityId !== current.signalWiring.senderCapabilityId;
775
+ const receiverAddress = senderChanged || capabilityChanged
776
+ ? null
777
+ : readWiringValue(values.receiverAddress) ?? current.signalWiring.receiverAddress;
778
+ const receiverChanged = receiverAddress !== current.signalWiring.receiverAddress;
779
+ return {
780
+ ...current,
781
+ signalWiring: {
782
+ senderAddress,
783
+ senderCapabilityId,
784
+ receiverAddress,
785
+ receiverCapabilityId: senderChanged || capabilityChanged || receiverChanged
786
+ ? null
787
+ : readWiringValue(values.receiverCapabilityId) ?? current.signalWiring.receiverCapabilityId,
788
+ // The field belongs to the receiver's input, so it falls with everything above it.
789
+ fieldKey: senderChanged || capabilityChanged || receiverChanged
790
+ ? null
791
+ : readWiringValue(values.fieldKey) ?? current.signalWiring.fieldKey,
792
+ },
793
+ };
794
+ });
795
+ }
796
+ export function resetPhiBuilderSignalWiringSession() {
797
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
798
+ ...current,
799
+ signalWiring: { ...EMPTY_SIGNAL_WIRING_SESSION },
800
+ }));
801
+ }
802
+ /**
803
+ * What a save or read answered about the Draft a region stands on. A later answer for the same revision
804
+ * never moves the version or the node sequence back.
805
+ */
806
+ export function storePhiBuilderDraftAllocation(allocationKey, saved) {
807
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
808
+ const currentAllocation = current.draftAllocations[allocationKey];
809
+ const sameRevision = currentAllocation?.revisionId === saved.revisionId;
810
+ return {
811
+ ...current,
812
+ draftAllocations: {
813
+ ...current.draftAllocations,
814
+ [allocationKey]: {
815
+ revisionId: saved.revisionId,
816
+ version: sameRevision ? Math.max(currentAllocation.version, saved.version) : saved.version,
817
+ nextNodeSequence: sameRevision
818
+ ? Math.max(currentAllocation.nextNodeSequence, saved.nextNodeSequence)
819
+ : saved.nextNodeSequence,
820
+ sourcePreset: saved.sourcePreset,
821
+ },
822
+ },
823
+ };
824
+ });
825
+ }
826
+ export function clearPhiBuilderDraftAllocation(allocationKey) {
827
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => {
828
+ if (!current.draftAllocations[allocationKey])
829
+ return current;
830
+ const draftAllocations = { ...current.draftAllocations };
831
+ delete draftAllocations[allocationKey];
832
+ return { ...current, draftAllocations };
833
+ });
834
+ }
835
+ /** Takes the next node sequence of a Draft, answering the allocation it was taken from. */
836
+ export function takePhiBuilderDraftNodeSequence(allocationKey) {
837
+ const allocation = builderWorkspaceStore.getSnapshot(PHI_WORKSPACE_CATALOG_SCOPE).draftAllocations[allocationKey];
838
+ if (!allocation) {
839
+ throw new Error(`Missing Draft allocation state for "${allocationKey}".`);
840
+ }
841
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({
842
+ ...current,
843
+ draftAllocations: {
844
+ ...current.draftAllocations,
845
+ [allocationKey]: { ...allocation, nextNodeSequence: allocation.nextNodeSequence + 1 },
846
+ },
847
+ }));
848
+ return allocation;
849
+ }
850
+ /**
851
+ * Puts a workspace snapshot back, each field into the store it lives in: the catalog's into the
852
+ * catalog, the Builder's here. An undo used to patch both stores from the command controller.
853
+ */
854
+ export function restorePhiBuilderWorkspaceState(state) {
855
+ const { catalog, tool } = splitWorkspacePatch(state);
856
+ restorePhiWorkspaceCatalog(catalog);
857
+ if (Object.keys(tool).length > 0) {
858
+ builderWorkspaceStore.patch(PHI_WORKSPACE_CATALOG_SCOPE, (current) => ({ ...current, ...tool }));
859
+ }
666
860
  }
@@ -6,7 +6,7 @@ import { usePhiDeveloperBuilderStateValue } from "./developer-workspace-store";
6
6
  import { PhiIcon } from "../../../components/shell/phi-icon";
7
7
  export function PhiEditScaffoldDrawer({ kind, nodeId, regionKey, scaffoldLabel, className, onOpenInsert, onClick, onKeyDown, children, chrome, style, }) {
8
8
  const { token } = usePhiConfig();
9
- const selected = usePhiDeveloperBuilderStateValue("public", (state) => state.selectedRootRegionKey === regionKey &&
9
+ const selected = usePhiDeveloperBuilderStateValue((state) => state.selectedRootRegionKey === regionKey &&
10
10
  state.nodeId === nodeId &&
11
11
  (state.nodeKind === "layout"));
12
12
  const isEmpty = children == null;
@@ -118,7 +118,9 @@ export declare const phiBuilderHistory: {
118
118
  } | null;
119
119
  };
120
120
  export declare function createPhiBuilderHistoryContext(input: {
121
- workspace: PhiBuilderHistoryWorkspace;
121
+ workspace: "modules";
122
+ } | {
123
+ workspace: Exclude<PhiBuilderHistoryWorkspace, "modules">;
122
124
  area: PhiDeveloperBuilderArea;
123
125
  pageKey?: string | null;
124
126
  navKey?: string | null;
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { PHI_WORKSPACE_CATALOG_SCOPE } from "../../../components/workspace/catalog-store";
2
3
  import { resolvePhiBuilderAreaAsCmsArea } from "../../../constants/cms-areas";
3
4
  import { isPhiRuntimeAreaBaseModuleId, resolvePhiRuntimeAreaDefinition, } from "../../../plugins/runtime-modules/area-definitions";
4
5
  import { isPhiCmsErrorPagePath } from "../../../constants/cms-error-pages";
@@ -259,8 +260,8 @@ function resolveAreaRootRouteOptions(context) {
259
260
  };
260
261
  }
261
262
  const builderProviderStore = {
262
- subscribe: (listener) => builderWorkspaceStore.subscribe("public", listener),
263
- getSnapshot: () => getPhiDeveloperBuilderStateSnapshot("public"),
263
+ subscribe: (listener) => builderWorkspaceStore.subscribe(PHI_WORKSPACE_CATALOG_SCOPE, listener),
264
+ getSnapshot: () => getPhiDeveloperBuilderStateSnapshot(),
264
265
  };
265
266
  /**
266
267
  * The renderers on offer for whatever this Collection View is bound to.
@@ -1,4 +1,6 @@
1
1
  "use client";
2
+ import { isPhiRecord } from "../../../helpers/is-record";
3
+ import { createPhiResponseReader, requirePhiField } from "../../../components/widgets/config/strict-config-reader";
2
4
  import { resolvePhiBuilderCatalogApiArea, } from "../../../helpers/cms-page-catalog";
3
5
  const persistedPageCatalogRequests = new Map();
4
6
  export function loadPhiBuilderPersistedPageCatalog(area, options = {}) {
@@ -41,15 +43,25 @@ export async function changePhiBuilderPagePath(input) {
41
43
  credentials: "same-origin",
42
44
  body: JSON.stringify({ area: resolvePhiBuilderCatalogApiArea(input.area), path: input.path }),
43
45
  });
44
- const body = (await response.json().catch(() => null));
45
- if (!response.ok || typeof body?.path !== "string") {
46
- throw new Error(body?.message ?? body?.error ?? `Page path change failed (${response.status}).`);
46
+ const body = await response.json().catch(() => null);
47
+ if (!response.ok) {
48
+ const failure = isPhiRecord(body) ? body : {};
49
+ const message = typeof failure.message === "string" ? failure.message : null;
50
+ const error = typeof failure.error === "string" ? failure.error : null;
51
+ throw new Error(message ?? error ?? `Page path change failed (${response.status}).`);
47
52
  }
53
+ /*
54
+ * Read, not cast. The server always counts the references that follow the Page (`references.total`);
55
+ * a missing count used to read as 0, which told the author nothing would follow a rename that moved
56
+ * links all over the Site.
57
+ */
58
+ const reader = createPhiResponseReader(body, "The Page path change");
59
+ const pendingPath = reader.record.pendingPath;
48
60
  return {
49
- pageScopeId: body.pageScopeId ?? input.pageScopeId,
61
+ pageScopeId: requirePhiField(reader, "pageScopeId", "a whole number", reader.integer),
50
62
  /* Still the address the Page answers on: a rename is a pending change until somebody publishes. */
51
- path: body.path,
52
- pendingPath: typeof body.pendingPath === "string" ? body.pendingPath : null,
53
- references: Number.isSafeInteger(body.references?.total) ? body.references.total : 0,
63
+ path: requirePhiField(reader, "path", "a path", reader.string),
64
+ pendingPath: pendingPath === null ? null : requirePhiField(reader, "pendingPath", "a path or null", reader.string),
65
+ references: requirePhiField(reader, "references", "the references that follow the Page", (key) => reader.object(key, (references) => requirePhiField(references, "total", "a whole number", references.integer))),
54
66
  };
55
67
  }
@@ -91,6 +91,7 @@ const viewportPropertyOptions = [
91
91
  const viewportAxisOptions = [
92
92
  labelledOption("x", "viewport.x", "X"),
93
93
  labelledOption("y", "viewport.y", "Y"),
94
+ labelledOption("z", "viewport.z", "Z"),
94
95
  ];
95
96
  /* px, % and deg are the units themselves; only "no unit" is a word. */
96
97
  const viewportUnitOptions = [
@@ -7,7 +7,7 @@ import { buildPhiBuilderPageReferenceTree } from "../../../helpers/page-referenc
7
7
  import { usePhiDeveloperBuilderStateValue } from "./developer-workspace-store";
8
8
  /** Offers the edited Area's Pages to the authoring tools' page picker. */
9
9
  export function PhiBuilderPageReferencesProvider({ children }) {
10
- const state = usePhiDeveloperBuilderStateValue("public", (value) => value);
10
+ const state = usePhiDeveloperBuilderStateValue((value) => value);
11
11
  const pages = usePhiBuilderOfferedPageCatalog(state, state.area);
12
12
  const options = useMemo(() => buildPhiBuilderPageReferenceTree(state.area, pages, pages), [pages, state.area]);
13
13
  return _jsx(PhiAuthoringPageReferencesProvider, { options: options, children: children });