@cohesivesystems/presentation-react 0.1.0-alpha.1

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 (252) hide show
  1. package/LICENSE +55 -0
  2. package/dist/collection-chrome-slot-renderer-registry.d.ts +12 -0
  3. package/dist/collection-chrome-slot-renderer-registry.d.ts.map +1 -0
  4. package/dist/collection-chrome-slot-renderer-registry.js +12 -0
  5. package/dist/collection-chrome-slot-renderer-registry.js.map +1 -0
  6. package/dist/collection-selection-state-context.d.ts +6 -0
  7. package/dist/collection-selection-state-context.d.ts.map +1 -0
  8. package/dist/collection-selection-state-context.js +25 -0
  9. package/dist/collection-selection-state-context.js.map +1 -0
  10. package/dist/collection-selection-state.d.ts +3 -0
  11. package/dist/collection-selection-state.d.ts.map +1 -0
  12. package/dist/collection-selection-state.js +52 -0
  13. package/dist/collection-selection-state.js.map +1 -0
  14. package/dist/document-workspace-runtime.d.ts +262 -0
  15. package/dist/document-workspace-runtime.d.ts.map +1 -0
  16. package/dist/document-workspace-runtime.js +699 -0
  17. package/dist/document-workspace-runtime.js.map +1 -0
  18. package/dist/index.d.ts +51 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +51 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/input-form-endpoint-runtime.d.ts +140 -0
  23. package/dist/input-form-endpoint-runtime.d.ts.map +1 -0
  24. package/dist/input-form-endpoint-runtime.js +280 -0
  25. package/dist/input-form-endpoint-runtime.js.map +1 -0
  26. package/dist/local-flow-state-data-source-projection.d.ts +21 -0
  27. package/dist/local-flow-state-data-source-projection.d.ts.map +1 -0
  28. package/dist/local-flow-state-data-source-projection.js +65 -0
  29. package/dist/local-flow-state-data-source-projection.js.map +1 -0
  30. package/dist/navigation-runtime-context.d.ts +22 -0
  31. package/dist/navigation-runtime-context.d.ts.map +1 -0
  32. package/dist/navigation-runtime-context.js +23 -0
  33. package/dist/navigation-runtime-context.js.map +1 -0
  34. package/dist/navigation-shell-frame-renderer-registry.d.ts +20 -0
  35. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  36. package/dist/navigation-shell-frame-renderer-registry.js +9 -0
  37. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  38. package/dist/navigation-shell-region-component-registry.d.ts +50 -0
  39. package/dist/navigation-shell-region-component-registry.d.ts.map +1 -0
  40. package/dist/navigation-shell-region-component-registry.js +44 -0
  41. package/dist/navigation-shell-region-component-registry.js.map +1 -0
  42. package/dist/navigation-shell-slot-renderer-registry.d.ts +22 -0
  43. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  44. package/dist/navigation-shell-slot-renderer-registry.js +9 -0
  45. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  46. package/dist/page-host-projection.d.ts +127 -0
  47. package/dist/page-host-projection.d.ts.map +1 -0
  48. package/dist/page-host-projection.js +59 -0
  49. package/dist/page-host-projection.js.map +1 -0
  50. package/dist/page-host-renderer-registry.d.ts +72 -0
  51. package/dist/page-host-renderer-registry.d.ts.map +1 -0
  52. package/dist/page-host-renderer-registry.js +63 -0
  53. package/dist/page-host-renderer-registry.js.map +1 -0
  54. package/dist/presentation-action-group-runtime-options.d.ts +36 -0
  55. package/dist/presentation-action-group-runtime-options.d.ts.map +1 -0
  56. package/dist/presentation-action-group-runtime-options.js +50 -0
  57. package/dist/presentation-action-group-runtime-options.js.map +1 -0
  58. package/dist/presentation-action-runtime.d.ts +52 -0
  59. package/dist/presentation-action-runtime.d.ts.map +1 -0
  60. package/dist/presentation-action-runtime.js +78 -0
  61. package/dist/presentation-action-runtime.js.map +1 -0
  62. package/dist/presentation-data-source-binding-runtime.d.ts +3 -0
  63. package/dist/presentation-data-source-binding-runtime.d.ts.map +1 -0
  64. package/dist/presentation-data-source-binding-runtime.js +65 -0
  65. package/dist/presentation-data-source-binding-runtime.js.map +1 -0
  66. package/dist/presentation-data-source-binding.d.ts +12 -0
  67. package/dist/presentation-data-source-binding.d.ts.map +1 -0
  68. package/dist/presentation-data-source-binding.js +13 -0
  69. package/dist/presentation-data-source-binding.js.map +1 -0
  70. package/dist/presentation-flow-runtime.d.ts +139 -0
  71. package/dist/presentation-flow-runtime.d.ts.map +1 -0
  72. package/dist/presentation-flow-runtime.js +514 -0
  73. package/dist/presentation-flow-runtime.js.map +1 -0
  74. package/dist/presentation-module-context.d.ts +4 -0
  75. package/dist/presentation-module-context.d.ts.map +1 -0
  76. package/dist/presentation-module-context.js +6 -0
  77. package/dist/presentation-module-context.js.map +1 -0
  78. package/dist/presentation-module-provider.d.ts +7 -0
  79. package/dist/presentation-module-provider.d.ts.map +1 -0
  80. package/dist/presentation-module-provider.js +7 -0
  81. package/dist/presentation-module-provider.js.map +1 -0
  82. package/dist/presentation-projection-diagnostics-context.d.ts +8 -0
  83. package/dist/presentation-projection-diagnostics-context.d.ts.map +1 -0
  84. package/dist/presentation-projection-diagnostics-context.js +3 -0
  85. package/dist/presentation-projection-diagnostics-context.js.map +1 -0
  86. package/dist/presentation-projection-diagnostics-hooks.d.ts +4 -0
  87. package/dist/presentation-projection-diagnostics-hooks.d.ts.map +1 -0
  88. package/dist/presentation-projection-diagnostics-hooks.js +17 -0
  89. package/dist/presentation-projection-diagnostics-hooks.js.map +1 -0
  90. package/dist/presentation-projection-diagnostics-runtime.d.ts +12 -0
  91. package/dist/presentation-projection-diagnostics-runtime.d.ts.map +1 -0
  92. package/dist/presentation-projection-diagnostics-runtime.js +54 -0
  93. package/dist/presentation-projection-diagnostics-runtime.js.map +1 -0
  94. package/dist/presentation-projection-trace.d.ts +27 -0
  95. package/dist/presentation-projection-trace.d.ts.map +1 -0
  96. package/dist/presentation-projection-trace.js +54 -0
  97. package/dist/presentation-projection-trace.js.map +1 -0
  98. package/dist/presentation-query-form-runtime.d.ts +30 -0
  99. package/dist/presentation-query-form-runtime.d.ts.map +1 -0
  100. package/dist/presentation-query-form-runtime.js +75 -0
  101. package/dist/presentation-query-form-runtime.js.map +1 -0
  102. package/dist/presentation-query-form-state-adapter-registry.d.ts +4 -0
  103. package/dist/presentation-query-form-state-adapter-registry.d.ts.map +1 -0
  104. package/dist/presentation-query-form-state-adapter-registry.js +13 -0
  105. package/dist/presentation-query-form-state-adapter-registry.js.map +1 -0
  106. package/dist/presentation-query-form-state-context.d.ts +20 -0
  107. package/dist/presentation-query-form-state-context.d.ts.map +1 -0
  108. package/dist/presentation-query-form-state-context.js +20 -0
  109. package/dist/presentation-query-form-state-context.js.map +1 -0
  110. package/dist/presentation-query-form-state.d.ts +29 -0
  111. package/dist/presentation-query-form-state.d.ts.map +1 -0
  112. package/dist/presentation-query-form-state.js +99 -0
  113. package/dist/presentation-query-form-state.js.map +1 -0
  114. package/dist/presentation-renderer-registry.d.ts +109 -0
  115. package/dist/presentation-renderer-registry.d.ts.map +1 -0
  116. package/dist/presentation-renderer-registry.js +25 -0
  117. package/dist/presentation-renderer-registry.js.map +1 -0
  118. package/dist/presentation-standards-context.d.ts +4 -0
  119. package/dist/presentation-standards-context.d.ts.map +1 -0
  120. package/dist/presentation-standards-context.js +12 -0
  121. package/dist/presentation-standards-context.js.map +1 -0
  122. package/dist/presentation-standards-provider.d.ts +14 -0
  123. package/dist/presentation-standards-provider.d.ts.map +1 -0
  124. package/dist/presentation-standards-provider.js +16 -0
  125. package/dist/presentation-standards-provider.js.map +1 -0
  126. package/dist/projected-document-action-runtime-bindings.d.ts +20 -0
  127. package/dist/projected-document-action-runtime-bindings.d.ts.map +1 -0
  128. package/dist/projected-document-action-runtime-bindings.js +88 -0
  129. package/dist/projected-document-action-runtime-bindings.js.map +1 -0
  130. package/dist/projected-document-action-runtime.d.ts +76 -0
  131. package/dist/projected-document-action-runtime.d.ts.map +1 -0
  132. package/dist/projected-document-action-runtime.js +224 -0
  133. package/dist/projected-document-action-runtime.js.map +1 -0
  134. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts +26 -0
  135. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts.map +1 -0
  136. package/dist/projected-document-process-preview-action-runtime-bindings.js +106 -0
  137. package/dist/projected-document-process-preview-action-runtime-bindings.js.map +1 -0
  138. package/dist/projected-document-process-preview-action-runtime.d.ts +321 -0
  139. package/dist/projected-document-process-preview-action-runtime.d.ts.map +1 -0
  140. package/dist/projected-document-process-preview-action-runtime.js +729 -0
  141. package/dist/projected-document-process-preview-action-runtime.js.map +1 -0
  142. package/dist/projected-document-process-preview-capability-runtime.d.ts +47 -0
  143. package/dist/projected-document-process-preview-capability-runtime.d.ts.map +1 -0
  144. package/dist/projected-document-process-preview-capability-runtime.js +49 -0
  145. package/dist/projected-document-process-preview-capability-runtime.js.map +1 -0
  146. package/dist/projected-document-workspace-action-runtime.d.ts +87 -0
  147. package/dist/projected-document-workspace-action-runtime.d.ts.map +1 -0
  148. package/dist/projected-document-workspace-action-runtime.js +150 -0
  149. package/dist/projected-document-workspace-action-runtime.js.map +1 -0
  150. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts +18 -0
  151. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts.map +1 -0
  152. package/dist/projected-document-workspace-projection-renderer-diagnostics.js +207 -0
  153. package/dist/projected-document-workspace-projection-renderer-diagnostics.js.map +1 -0
  154. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts +19 -0
  155. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts.map +1 -0
  156. package/dist/projected-document-workspace-runtime-binding-diagnostics.js +124 -0
  157. package/dist/projected-document-workspace-runtime-binding-diagnostics.js.map +1 -0
  158. package/dist/projected-navigation-provider.d.ts +16 -0
  159. package/dist/projected-navigation-provider.d.ts.map +1 -0
  160. package/dist/projected-navigation-provider.js +51 -0
  161. package/dist/projected-navigation-provider.js.map +1 -0
  162. package/dist/projected-presentation-flow-host.d.ts +16 -0
  163. package/dist/projected-presentation-flow-host.d.ts.map +1 -0
  164. package/dist/projected-presentation-flow-host.js +14 -0
  165. package/dist/projected-presentation-flow-host.js.map +1 -0
  166. package/dist/projected-query-form-state-adapter.d.ts +12 -0
  167. package/dist/projected-query-form-state-adapter.d.ts.map +1 -0
  168. package/dist/projected-query-form-state-adapter.js +361 -0
  169. package/dist/projected-query-form-state-adapter.js.map +1 -0
  170. package/dist/react-router-binding.d.ts +133 -0
  171. package/dist/react-router-binding.d.ts.map +1 -0
  172. package/dist/react-router-binding.js +76 -0
  173. package/dist/react-router-binding.js.map +1 -0
  174. package/dist/react-router-path.d.ts +4 -0
  175. package/dist/react-router-path.d.ts.map +1 -0
  176. package/dist/react-router-path.js +11 -0
  177. package/dist/react-router-path.js.map +1 -0
  178. package/dist/scope-selection-context.d.ts +28 -0
  179. package/dist/scope-selection-context.d.ts.map +1 -0
  180. package/dist/scope-selection-context.js +13 -0
  181. package/dist/scope-selection-context.js.map +1 -0
  182. package/dist/scope-selection-provider.d.ts +33 -0
  183. package/dist/scope-selection-provider.d.ts.map +1 -0
  184. package/dist/scope-selection-provider.js +162 -0
  185. package/dist/scope-selection-provider.js.map +1 -0
  186. package/dist/scope-selection-request.d.ts +6 -0
  187. package/dist/scope-selection-request.d.ts.map +1 -0
  188. package/dist/scope-selection-request.js +12 -0
  189. package/dist/scope-selection-request.js.map +1 -0
  190. package/dist/standard-navigation-shell-frame-renderers.d.ts +15 -0
  191. package/dist/standard-navigation-shell-frame-renderers.d.ts.map +1 -0
  192. package/dist/standard-navigation-shell-frame-renderers.js +22 -0
  193. package/dist/standard-navigation-shell-frame-renderers.js.map +1 -0
  194. package/dist/standard-navigation-shell-slot-renderers.d.ts +59 -0
  195. package/dist/standard-navigation-shell-slot-renderers.d.ts.map +1 -0
  196. package/dist/standard-navigation-shell-slot-renderers.js +67 -0
  197. package/dist/standard-navigation-shell-slot-renderers.js.map +1 -0
  198. package/dist/view-chrome-slot-renderer-registry.d.ts +12 -0
  199. package/dist/view-chrome-slot-renderer-registry.d.ts.map +1 -0
  200. package/dist/view-chrome-slot-renderer-registry.js +12 -0
  201. package/dist/view-chrome-slot-renderer-registry.js.map +1 -0
  202. package/package.json +60 -0
  203. package/src/collection-chrome-slot-renderer-registry.ts +66 -0
  204. package/src/collection-selection-state-context.ts +51 -0
  205. package/src/collection-selection-state.tsx +101 -0
  206. package/src/document-workspace-runtime.tsx +1505 -0
  207. package/src/index.ts +50 -0
  208. package/src/input-form-endpoint-runtime.ts +632 -0
  209. package/src/local-flow-state-data-source-projection.ts +133 -0
  210. package/src/navigation-runtime-context.ts +40 -0
  211. package/src/navigation-shell-frame-renderer-registry.ts +54 -0
  212. package/src/navigation-shell-region-component-registry.tsx +127 -0
  213. package/src/navigation-shell-slot-renderer-registry.ts +88 -0
  214. package/src/page-host-projection.ts +356 -0
  215. package/src/page-host-renderer-registry.tsx +353 -0
  216. package/src/presentation-action-group-runtime-options.ts +121 -0
  217. package/src/presentation-action-runtime.ts +190 -0
  218. package/src/presentation-data-source-binding-runtime.ts +109 -0
  219. package/src/presentation-data-source-binding.tsx +24 -0
  220. package/src/presentation-flow-runtime.tsx +835 -0
  221. package/src/presentation-module-context.ts +10 -0
  222. package/src/presentation-module-provider.tsx +19 -0
  223. package/src/presentation-projection-diagnostics-context.ts +17 -0
  224. package/src/presentation-projection-diagnostics-hooks.ts +29 -0
  225. package/src/presentation-projection-diagnostics-runtime.tsx +95 -0
  226. package/src/presentation-projection-trace.ts +117 -0
  227. package/src/presentation-query-form-runtime.ts +148 -0
  228. package/src/presentation-query-form-state-adapter-registry.ts +25 -0
  229. package/src/presentation-query-form-state-context.ts +56 -0
  230. package/src/presentation-query-form-state.tsx +250 -0
  231. package/src/presentation-renderer-registry.ts +214 -0
  232. package/src/presentation-standards-context.ts +21 -0
  233. package/src/presentation-standards-provider.tsx +44 -0
  234. package/src/projected-document-action-runtime-bindings.ts +132 -0
  235. package/src/projected-document-action-runtime.ts +414 -0
  236. package/src/projected-document-process-preview-action-runtime-bindings.ts +231 -0
  237. package/src/projected-document-process-preview-action-runtime.ts +1725 -0
  238. package/src/projected-document-process-preview-capability-runtime.ts +216 -0
  239. package/src/projected-document-workspace-action-runtime.ts +402 -0
  240. package/src/projected-document-workspace-projection-renderer-diagnostics.ts +342 -0
  241. package/src/projected-document-workspace-runtime-binding-diagnostics.ts +208 -0
  242. package/src/projected-navigation-provider.tsx +104 -0
  243. package/src/projected-presentation-flow-host.tsx +46 -0
  244. package/src/projected-query-form-state-adapter.ts +578 -0
  245. package/src/react-router-binding.tsx +417 -0
  246. package/src/react-router-path.ts +16 -0
  247. package/src/scope-selection-context.ts +58 -0
  248. package/src/scope-selection-provider.tsx +324 -0
  249. package/src/scope-selection-request.ts +21 -0
  250. package/src/standard-navigation-shell-frame-renderers.tsx +106 -0
  251. package/src/standard-navigation-shell-slot-renderers.tsx +289 -0
  252. package/src/view-chrome-slot-renderer-registry.ts +66 -0
@@ -0,0 +1,1505 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useMemo,
8
+ useState,
9
+ type ReactNode,
10
+ type SetStateAction,
11
+ } from 'react'
12
+
13
+ import {
14
+ findPresentationDocumentProfile,
15
+ findPresentationView,
16
+ findPresentationWorkspace,
17
+ type DocumentProfileDefinition,
18
+ type PresentationModuleDefinition,
19
+ type ProjectionDefinition,
20
+ type ViewDefinition,
21
+ type WorkspaceDefinition,
22
+ type WorkspaceRefDefinition,
23
+ } from '@cohesivesystems/presentation-core'
24
+ import { usePresentationModule } from './presentation-module-context'
25
+ import {
26
+ createPresentationEnumDiscriminator,
27
+ findPresentationComponentBinding,
28
+ matchesPresentationEnum,
29
+ } from '@cohesivesystems/presentation-core'
30
+ import {
31
+ coordinationActionKinds,
32
+ coordinationTriggerKinds,
33
+ presentationBindingKinds,
34
+ presentationTargetKinds,
35
+ projectionKindLabels,
36
+ projectionSubjectKindLabels,
37
+ semanticReferenceKindLabels,
38
+ } from '@cohesivesystems/presentation-contracts'
39
+ import type { CoordinationDefinition } from '@cohesivesystems/presentation-contracts'
40
+
41
+ /**
42
+ * Client-side display mode for a document workspace. The available modes are
43
+ * declared by the IR, but the active mode is runtime state owned by the
44
+ * projection boundary.
45
+ */
46
+ export type DocumentWorkspaceLayout = 'single' | 'split'
47
+
48
+ /**
49
+ * A semantic path is a renderer-neutral addressing for a document location. Tree
50
+ * renderers publish these paths so sibling projections can coordinate without
51
+ * depending on each other's concrete component state.
52
+ */
53
+ export interface DocumentWorkspaceSemanticPathSegment {
54
+ readonly Kind?: string | number | null
55
+ readonly Id: string
56
+ readonly Label?: string | null
57
+ }
58
+
59
+ export interface DocumentWorkspaceSemanticPath {
60
+ readonly Segments: readonly DocumentWorkspaceSemanticPathSegment[]
61
+ }
62
+
63
+ export interface DocumentWorkspaceSemanticReference {
64
+ readonly Kind?: string | number | null
65
+ readonly Id: string
66
+ readonly Path?: DocumentWorkspaceSemanticPath | null
67
+ }
68
+
69
+ export interface DocumentWorkspaceSemanticSelection {
70
+ readonly Target: DocumentWorkspaceSemanticReference
71
+ readonly SourceProjectionId?: string | null
72
+ readonly SemanticPath?: DocumentWorkspaceSemanticPath | null
73
+ }
74
+
75
+ export interface DocumentWorkspaceSemanticTreeNode {
76
+ readonly itemId: string
77
+ readonly relatedSemanticReferences?: readonly DocumentWorkspaceSemanticReference[]
78
+ readonly semanticReference: DocumentWorkspaceSemanticReference
79
+ }
80
+
81
+ export interface DocumentWorkspaceNavigationHistory {
82
+ readonly entries: readonly string[]
83
+ readonly index: number
84
+ }
85
+
86
+ export interface DocumentWorkspaceTreeSearchState {
87
+ readonly activeMatchIndex: number
88
+ readonly isOpen: boolean
89
+ readonly query: string
90
+ }
91
+
92
+ export interface DocumentWorkspaceTreeStateSnapshot {
93
+ readonly expandedItemIds: readonly string[]
94
+ readonly navigationHistory: DocumentWorkspaceNavigationHistory
95
+ readonly search: DocumentWorkspaceTreeSearchState
96
+ }
97
+
98
+ export interface DocumentWorkspaceRuntimeState {
99
+ /**
100
+ * Cross-projection selections, such as a structure-tree item that another
101
+ * document projection may highlight or navigate to.
102
+ */
103
+ readonly semanticSelections: readonly DocumentWorkspaceSemanticSelection[]
104
+ /**
105
+ * Projection-local interaction state keyed by semantic projection id. Keeping
106
+ * this here lets tree projections remount without losing expansion/search
107
+ * state while the same document workspace instance is active.
108
+ */
109
+ readonly treeStates: Readonly<Record<string, DocumentWorkspaceTreeStateSnapshot>>
110
+ }
111
+
112
+ /**
113
+ * Fully resolved runtime contract for a semantic document workspace. Route
114
+ * hosts should treat this as the projection boundary: React components receive
115
+ * this snapshot after the IR has resolved the page, workspace, document profile,
116
+ * initial projections, and active client state.
117
+ */
118
+ export interface DocumentWorkspaceRuntimeSnapshot {
119
+ readonly activeLayoutModeId: string
120
+ readonly activeProjection: ProjectionDefinition | null
121
+ readonly activeProjectionId: string | null
122
+ readonly activeViewId: string | null
123
+ readonly documentProfile: DocumentProfileDefinition
124
+ readonly initialProjectionIds: readonly string[]
125
+ /**
126
+ * Stable key for state that belongs to one opened workspace/document pair.
127
+ * Changing this key intentionally resets local interaction state.
128
+ */
129
+ readonly instanceKey: string
130
+ readonly layout: DocumentWorkspaceLayout
131
+ /**
132
+ * The route/page-level semantic view. This is distinct from workspaceView:
133
+ * pageView owns page chrome such as document summary metrics and actions.
134
+ */
135
+ readonly pageView: ViewDefinition
136
+ /**
137
+ * View ids corresponding to the active document profile projections. These
138
+ * ids drive document tabs/switchers, while projection ids remain the semantic
139
+ * identities used by renderers and persisted projection state.
140
+ */
141
+ readonly projectionViewIds: readonly string[]
142
+ readonly projections: readonly ProjectionDefinition[]
143
+ readonly setActiveViewId: (viewId: string) => void
144
+ readonly setLayout: (layout: DocumentWorkspaceLayout) => void
145
+ readonly state: DocumentWorkspaceRuntimeState
146
+ readonly workspace: WorkspaceDefinition
147
+ readonly workspaceRef: WorkspaceRefDefinition
148
+ /**
149
+ * The semantic workspace surface hosted by the page. This view owns workspace
150
+ * chrome such as view switching, layout switching, and heading trailing state.
151
+ */
152
+ readonly workspaceView: ViewDefinition
153
+ }
154
+
155
+ /**
156
+ * Context passed to concrete projection renderers after semantic projection
157
+ * resolution. `extra` is intentionally supplied by the app-level binding so the
158
+ * runtime can stay independent from domain-specific document data.
159
+ */
160
+ export interface DocumentWorkspaceProjectionRendererContext<
161
+ TExtra,
162
+ TComponentSystem = unknown,
163
+ TDesignSystem = unknown,
164
+ > {
165
+ readonly componentSystem: TComponentSystem
166
+ readonly designSystem: TDesignSystem
167
+ readonly extra: TExtra
168
+ readonly projection: ProjectionDefinition
169
+ readonly runtime: DocumentWorkspaceRuntimeSnapshot
170
+ }
171
+
172
+ /**
173
+ * React renderer for one semantic document workspace projection.
174
+ *
175
+ * Implementations should interpret the resolved `projection` from the supplied
176
+ * context and render only that projection's view/content. Cross-projection
177
+ * coordination, active workspace state, component-system bindings, and
178
+ * app-specific document data are provided through the context so projection
179
+ * renderers can remain ordinary pure React adapters.
180
+ *
181
+ * @typeParam TExtra - App-specific document data and callbacks supplied by the host.
182
+ * @typeParam TComponentSystem - Concrete component-system type used by the renderer.
183
+ * @typeParam TDesignSystem - Concrete design-system type used by the renderer.
184
+ * @param context - Fully resolved projection rendering context.
185
+ * @returns React content for the projection, or `null` when the projection cannot render.
186
+ */
187
+ export type DocumentWorkspaceProjectionRenderer<
188
+ TExtra,
189
+ TComponentSystem = unknown,
190
+ TDesignSystem = unknown,
191
+ > = (
192
+ context: DocumentWorkspaceProjectionRendererContext<
193
+ TExtra,
194
+ TComponentSystem,
195
+ TDesignSystem
196
+ >,
197
+ ) => ReactNode
198
+
199
+ /**
200
+ * Registry of frontend interpretations for document projections. Resolution is
201
+ * target-first: ordinary projections bind through ProjectionRenderer target
202
+ * bindings declared by the backend IR and interpreted by a frontend component
203
+ * pack. Semantic-kind lookup is retained as a diagnostic fallback for older
204
+ * projections whose target component semantics have not been declared yet.
205
+ */
206
+ export interface DocumentWorkspaceProjectionRendererRegistry<
207
+ TExtra,
208
+ TComponentSystem = unknown,
209
+ TDesignSystem = unknown,
210
+ > {
211
+ /** Renderers keyed by semantic component roles from ProjectionRenderer target bindings. */
212
+ readonly byComponentRole?: Readonly<
213
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
214
+ >
215
+ /** Concrete component-key overrides from ProjectionRenderer target bindings. */
216
+ readonly byComponentKey?: Readonly<
217
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
218
+ >
219
+ /** Compatibility renderers keyed by projection kind enum value or label. */
220
+ readonly byProjectionKind?: Readonly<
221
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
222
+ >
223
+ /** Compatibility renderers keyed by projection subject kind enum value or label. */
224
+ readonly bySubjectKind?: Readonly<
225
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
226
+ >
227
+ /** Compatibility renderers keyed by coordinate semantic-reference kind enum value or label. */
228
+ readonly bySemanticReferenceKind?: Readonly<
229
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
230
+ >
231
+ /** Last-resort escape hatch keyed by projection id. */
232
+ readonly byProjectionId?: Readonly<
233
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
234
+ >
235
+ /** Last-resort escape hatch keyed by projection view id. */
236
+ readonly byViewId?: Readonly<
237
+ Record<string, DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>>
238
+ >
239
+ /** Renderer used when no semantic or explicit binding matches. */
240
+ readonly fallback?: DocumentWorkspaceProjectionRenderer<
241
+ TExtra,
242
+ TComponentSystem,
243
+ TDesignSystem
244
+ >
245
+ /** Frontend projection capabilities used by IR coordination diagnostics. */
246
+ readonly capabilities?: DocumentWorkspaceProjectionCapabilityRegistry
247
+ }
248
+
249
+ export interface DocumentWorkspaceProjectionCapabilityRegistry {
250
+ readonly revealSemanticSelection?: DocumentWorkspaceProjectionCapabilityBindings
251
+ }
252
+
253
+ export interface DocumentWorkspaceProjectionCapabilityBindings {
254
+ /** Capability keyed by semantic component roles from ProjectionRenderer target bindings. */
255
+ readonly byComponentRole?: Readonly<Record<string, boolean>>
256
+ /** Capability keyed by concrete component-key overrides from ProjectionRenderer target bindings. */
257
+ readonly byComponentKey?: Readonly<Record<string, boolean>>
258
+ /** Capability keyed by projection kind enum value or label. */
259
+ readonly byProjectionKind?: Readonly<Record<string, boolean>>
260
+ /** Capability keyed by projection subject kind enum value or label. */
261
+ readonly bySubjectKind?: Readonly<Record<string, boolean>>
262
+ /** Capability keyed by coordinate semantic-reference kind enum value or label. */
263
+ readonly bySemanticReferenceKind?: Readonly<Record<string, boolean>>
264
+ /** Last-resort escape hatch keyed by projection id. */
265
+ readonly byProjectionId?: Readonly<Record<string, boolean>>
266
+ /** Last-resort escape hatch keyed by projection view id. */
267
+ readonly byViewId?: Readonly<Record<string, boolean>>
268
+ }
269
+
270
+ export type DocumentWorkspaceProjectionRendererResolutionSource =
271
+ | 'component-key'
272
+ | 'component-role'
273
+ | 'fallback'
274
+ | 'projection-id'
275
+ | 'projection-kind'
276
+ | 'semantic-reference-kind'
277
+ | 'subject-kind'
278
+ | 'view-id'
279
+
280
+ export interface DocumentWorkspaceProjectionRendererResolution<
281
+ TExtra,
282
+ TComponentSystem = unknown,
283
+ TDesignSystem = unknown,
284
+ > {
285
+ readonly componentKey: string | null
286
+ readonly componentRole: string | null
287
+ readonly projection: ProjectionDefinition | null
288
+ readonly renderer:
289
+ | DocumentWorkspaceProjectionRenderer<TExtra, TComponentSystem, TDesignSystem>
290
+ | null
291
+ readonly rendererKey: string | null
292
+ readonly resolutionSource: DocumentWorkspaceProjectionRendererResolutionSource | null
293
+ }
294
+
295
+ export interface RenderDocumentWorkspaceProjectionOptions<
296
+ TComponentSystem = unknown,
297
+ TDesignSystem = unknown,
298
+ > {
299
+ readonly componentSet?: string | null
300
+ readonly componentSystem: TComponentSystem
301
+ readonly designSystem: TDesignSystem
302
+ readonly module?: PresentationModuleDefinition | null
303
+ }
304
+
305
+ interface DocumentWorkspaceRuntimeContextValue {
306
+ readonly coordination: readonly CoordinationDefinition[]
307
+ readonly instanceKey: string
308
+ readonly selectSemanticReference: (
309
+ selection: DocumentWorkspaceSemanticSelection,
310
+ ) => void
311
+ readonly setTreeExpandedItemIds: (
312
+ projectionId: string,
313
+ value: SetStateAction<readonly string[]>,
314
+ ) => void
315
+ readonly setTreeNavigationHistory: (
316
+ projectionId: string,
317
+ value: SetStateAction<DocumentWorkspaceNavigationHistory>,
318
+ ) => void
319
+ readonly setTreeSearch: (
320
+ projectionId: string,
321
+ value: SetStateAction<DocumentWorkspaceTreeSearchState>,
322
+ ) => void
323
+ readonly state: DocumentWorkspaceRuntimeState
324
+ }
325
+
326
+ const DocumentWorkspaceRuntimeContext =
327
+ createContext<DocumentWorkspaceRuntimeContextValue | null>(null)
328
+
329
+ export interface DocumentWorkspaceRuntimeProps {
330
+ readonly children: (runtime: DocumentWorkspaceRuntimeSnapshot) => ReactNode
331
+ readonly documentInstanceId?: string | null
332
+ readonly fallback?: ReactNode
333
+ /**
334
+ * Route/page semantic view id. The runtime resolves the workspace hosted by
335
+ * this page rather than assuming the page itself is the workspace surface.
336
+ */
337
+ readonly pageViewId: string
338
+ }
339
+
340
+ /**
341
+ * Resolves the document workspace declared by the presentation IR and provides
342
+ * the active client-side runtime snapshot to projection renderers. This is the
343
+ * point where a route page becomes a semantic workspace instance.
344
+ */
345
+ export function DocumentWorkspaceRuntime({
346
+ children,
347
+ documentInstanceId,
348
+ fallback = null,
349
+ pageViewId,
350
+ }: DocumentWorkspaceRuntimeProps) {
351
+ const module = usePresentationModule()
352
+ const resolution = useMemo(
353
+ () => resolveDocumentWorkspace(module, pageViewId),
354
+ [module, pageViewId],
355
+ )
356
+ const documentProfile = resolution?.documentProfile ?? null
357
+ const projections = useMemo(
358
+ () => documentProfile?.Projections ?? [],
359
+ [documentProfile],
360
+ )
361
+ const initialProjectionIds = useMemo(
362
+ () => resolveInitialProjectionIds(resolution?.workspaceRef ?? null, projections),
363
+ [projections, resolution?.workspaceRef],
364
+ )
365
+ const projectionViewIds = useMemo(
366
+ () =>
367
+ initialProjectionIds
368
+ .map((projectionId) => projections.find((projection) => projection.Id === projectionId))
369
+ .map((projection) => projection?.ViewId ?? projection?.Id ?? null)
370
+ .filter((viewId): viewId is string => Boolean(viewId)),
371
+ [initialProjectionIds, projections],
372
+ )
373
+ const defaultProjectionId = initialProjectionIds[0] ?? projections[0]?.Id ?? null
374
+ // Scope runtime state to the semantic workspace/profile and the concrete
375
+ // document instance so navigating between documents does not leak state.
376
+ const instanceKey = [
377
+ resolution?.workspaceRef.WorkspaceId ?? pageViewId,
378
+ resolution?.documentProfile.Id ?? 'default',
379
+ documentInstanceId ?? 'transient',
380
+ ].join(':')
381
+ const [activeProjectionState, setActiveProjectionState] = useState<{
382
+ readonly instanceKey: string
383
+ readonly projectionId: string | null
384
+ }>(() => ({ instanceKey, projectionId: defaultProjectionId }))
385
+ const [layoutState, setLayoutState] = useState<{
386
+ readonly instanceKey: string
387
+ readonly value: DocumentWorkspaceLayout
388
+ }>(() => ({
389
+ instanceKey,
390
+ value: resolveDefaultLayout(resolution?.documentProfile.Layout ?? null),
391
+ }))
392
+ const [runtimeState, setRuntimeState] = useState<{
393
+ readonly instanceKey: string
394
+ readonly value: DocumentWorkspaceRuntimeState
395
+ }>(() => ({ instanceKey, value: createInitialRuntimeState() }))
396
+
397
+ const runtimeStateValue =
398
+ runtimeState.instanceKey === instanceKey
399
+ ? runtimeState.value
400
+ : createInitialRuntimeState()
401
+ const coordinationDefinitions = resolution?.documentProfile.Coordination
402
+ const coordination = useMemo(
403
+ () => coordinationDefinitions ?? [],
404
+ [coordinationDefinitions],
405
+ )
406
+
407
+ const setTreeExpandedItemIds = useCallback(
408
+ (projectionId: string, value: SetStateAction<readonly string[]>) => {
409
+ setRuntimeState((current) =>
410
+ updateRuntimeTreeState(current, instanceKey, projectionId, (treeState) => ({
411
+ ...treeState,
412
+ expandedItemIds: applySetStateAction(value, treeState.expandedItemIds),
413
+ })),
414
+ )
415
+ },
416
+ [instanceKey],
417
+ )
418
+ const setTreeNavigationHistory = useCallback(
419
+ (
420
+ projectionId: string,
421
+ value: SetStateAction<DocumentWorkspaceNavigationHistory>,
422
+ ) => {
423
+ setRuntimeState((current) =>
424
+ updateRuntimeTreeState(current, instanceKey, projectionId, (treeState) => ({
425
+ ...treeState,
426
+ navigationHistory: applySetStateAction(value, treeState.navigationHistory),
427
+ })),
428
+ )
429
+ },
430
+ [instanceKey],
431
+ )
432
+ const setTreeSearch = useCallback(
433
+ (projectionId: string, value: SetStateAction<DocumentWorkspaceTreeSearchState>) => {
434
+ setRuntimeState((current) =>
435
+ updateRuntimeTreeState(current, instanceKey, projectionId, (treeState) => ({
436
+ ...treeState,
437
+ search: applySetStateAction(value, treeState.search),
438
+ })),
439
+ )
440
+ },
441
+ [instanceKey],
442
+ )
443
+ const selectSemanticReference = useCallback(
444
+ (selection: DocumentWorkspaceSemanticSelection) => {
445
+ setRuntimeState((current) => {
446
+ const base =
447
+ current.instanceKey === instanceKey
448
+ ? current.value
449
+ : createInitialRuntimeState()
450
+
451
+ return {
452
+ instanceKey,
453
+ value: {
454
+ ...base,
455
+ semanticSelections: createCoordinatedSemanticSelections(selection, coordination),
456
+ },
457
+ }
458
+ })
459
+ },
460
+ [coordination, instanceKey],
461
+ )
462
+ const contextValue = useMemo<DocumentWorkspaceRuntimeContextValue>(
463
+ () => ({
464
+ coordination,
465
+ instanceKey,
466
+ selectSemanticReference,
467
+ setTreeExpandedItemIds,
468
+ setTreeNavigationHistory,
469
+ setTreeSearch,
470
+ state: runtimeStateValue,
471
+ }),
472
+ [
473
+ coordination,
474
+ instanceKey,
475
+ runtimeStateValue,
476
+ selectSemanticReference,
477
+ setTreeExpandedItemIds,
478
+ setTreeNavigationHistory,
479
+ setTreeSearch,
480
+ ],
481
+ )
482
+
483
+ if (!resolution || !defaultProjectionId) {
484
+ return <>{fallback}</>
485
+ }
486
+
487
+ const activeProjectionId =
488
+ activeProjectionState.instanceKey === instanceKey &&
489
+ activeProjectionState.projectionId &&
490
+ initialProjectionIds.includes(activeProjectionState.projectionId)
491
+ ? activeProjectionState.projectionId
492
+ : defaultProjectionId
493
+ const activeProjection =
494
+ projections.find((projection) => projection.Id === activeProjectionId) ?? null
495
+ const activeViewId = activeProjection?.ViewId ?? activeProjection?.Id ?? null
496
+ const layout =
497
+ layoutState.instanceKey === instanceKey
498
+ ? layoutState.value
499
+ : resolveDefaultLayout(resolution.documentProfile.Layout ?? null)
500
+ const activeLayoutModeId = resolveActiveLayoutModeId(
501
+ layout,
502
+ resolution.documentProfile.Layout ?? null,
503
+ )
504
+
505
+ function setActiveViewId(viewId: string) {
506
+ const projection = projections.find(
507
+ (candidate) => candidate.ViewId === viewId || candidate.Id === viewId,
508
+ )
509
+ if (!projection) {
510
+ return
511
+ }
512
+
513
+ setActiveProjectionState({ instanceKey, projectionId: projection.Id })
514
+ }
515
+
516
+ function setLayout(value: DocumentWorkspaceLayout) {
517
+ setLayoutState({ instanceKey, value })
518
+ }
519
+
520
+ const snapshot: DocumentWorkspaceRuntimeSnapshot = {
521
+ activeLayoutModeId,
522
+ activeProjection,
523
+ activeProjectionId,
524
+ activeViewId,
525
+ documentProfile: resolution.documentProfile,
526
+ initialProjectionIds,
527
+ instanceKey,
528
+ layout,
529
+ pageView: resolution.pageView,
530
+ projectionViewIds,
531
+ projections,
532
+ setActiveViewId,
533
+ setLayout,
534
+ state: runtimeStateValue,
535
+ workspace: resolution.workspace,
536
+ workspaceRef: resolution.workspaceRef,
537
+ workspaceView: resolution.workspaceView,
538
+ }
539
+
540
+ return (
541
+ <DocumentWorkspaceRuntimeContext.Provider value={contextValue}>
542
+ {children(snapshot)}
543
+ </DocumentWorkspaceRuntimeContext.Provider>
544
+ )
545
+ }
546
+
547
+ export function renderDocumentWorkspaceProjection<
548
+ TExtra,
549
+ TComponentSystem,
550
+ TDesignSystem,
551
+ >(
552
+ runtime: DocumentWorkspaceRuntimeSnapshot,
553
+ renderers: DocumentWorkspaceProjectionRendererRegistry<
554
+ TExtra,
555
+ TComponentSystem,
556
+ TDesignSystem
557
+ >,
558
+ rendererContext: TExtra,
559
+ viewId: string,
560
+ options: RenderDocumentWorkspaceProjectionOptions<TComponentSystem, TDesignSystem>,
561
+ ) {
562
+ const resolution = resolveDocumentWorkspaceProjectionRenderer({
563
+ componentSet: options.componentSet,
564
+ module: options.module ?? null,
565
+ registry: renderers,
566
+ runtime,
567
+ viewId,
568
+ })
569
+
570
+ if (!resolution.projection || !resolution.renderer) {
571
+ return null
572
+ }
573
+
574
+ return resolution.renderer({
575
+ componentSystem: options.componentSystem,
576
+ designSystem: options.designSystem,
577
+ extra: rendererContext,
578
+ projection: resolution.projection,
579
+ runtime,
580
+ })
581
+ }
582
+
583
+ export function resolveDocumentWorkspaceProjectionRenderer<
584
+ TExtra,
585
+ TComponentSystem = unknown,
586
+ TDesignSystem = unknown,
587
+ >({
588
+ componentSet,
589
+ module,
590
+ registry,
591
+ runtime,
592
+ viewId,
593
+ }: {
594
+ readonly componentSet?: string | null
595
+ readonly module?: PresentationModuleDefinition | null
596
+ readonly registry: DocumentWorkspaceProjectionRendererRegistry<
597
+ TExtra,
598
+ TComponentSystem,
599
+ TDesignSystem
600
+ >
601
+ readonly runtime: DocumentWorkspaceRuntimeSnapshot
602
+ readonly viewId: string
603
+ }): DocumentWorkspaceProjectionRendererResolution<TExtra, TComponentSystem, TDesignSystem> {
604
+ // View ids are used by workspace chrome, while projection ids and renderer
605
+ // keys are semantic/runtime binding points. Accept either id at this boundary.
606
+ const projection = runtime.projections.find(
607
+ (candidate) => candidate.ViewId === viewId || candidate.Id === viewId,
608
+ )
609
+ if (!projection) {
610
+ return createDocumentProjectionRendererResolution(null)
611
+ }
612
+
613
+ const componentBinding = resolveDocumentWorkspaceProjectionComponentBinding(
614
+ module ?? null,
615
+ projection,
616
+ componentSet,
617
+ )
618
+ const componentKey = componentBinding?.componentKey ?? null
619
+ const componentRole = componentBinding?.componentRole ?? null
620
+ const rendererKey = projection.RendererKey ?? null
621
+
622
+ const componentRoleRenderer = componentRole
623
+ ? registry.byComponentRole?.[componentRole]
624
+ : undefined
625
+ if (componentRoleRenderer) {
626
+ return {
627
+ componentKey,
628
+ componentRole,
629
+ projection,
630
+ renderer: componentRoleRenderer,
631
+ rendererKey,
632
+ resolutionSource: 'component-role',
633
+ }
634
+ }
635
+
636
+ const componentRenderer = componentKey
637
+ ? registry.byComponentKey?.[componentKey]
638
+ : undefined
639
+ if (componentRenderer) {
640
+ return {
641
+ componentKey,
642
+ componentRole,
643
+ projection,
644
+ renderer: componentRenderer,
645
+ rendererKey,
646
+ resolutionSource: 'component-key',
647
+ }
648
+ }
649
+
650
+ const semanticReferenceRenderer = findDiscriminatorRenderer(
651
+ registry.bySemanticReferenceKind,
652
+ projection.Coordinates?.SemanticReferenceKind,
653
+ semanticReferenceKindLabels,
654
+ )
655
+ if (semanticReferenceRenderer) {
656
+ return {
657
+ componentKey,
658
+ componentRole,
659
+ projection,
660
+ renderer: semanticReferenceRenderer,
661
+ rendererKey,
662
+ resolutionSource: 'semantic-reference-kind',
663
+ }
664
+ }
665
+
666
+ const subjectKindRenderer = findDiscriminatorRenderer(
667
+ registry.bySubjectKind,
668
+ projection.Subject.Kind,
669
+ projectionSubjectKindLabels,
670
+ )
671
+ if (subjectKindRenderer) {
672
+ return {
673
+ componentKey,
674
+ componentRole,
675
+ projection,
676
+ renderer: subjectKindRenderer,
677
+ rendererKey,
678
+ resolutionSource: 'subject-kind',
679
+ }
680
+ }
681
+
682
+ const projectionKindRenderer = findDiscriminatorRenderer(
683
+ registry.byProjectionKind,
684
+ projection.Kind,
685
+ projectionKindLabels,
686
+ )
687
+ if (projectionKindRenderer) {
688
+ return {
689
+ componentKey,
690
+ componentRole,
691
+ projection,
692
+ renderer: projectionKindRenderer,
693
+ rendererKey,
694
+ resolutionSource: 'projection-kind',
695
+ }
696
+ }
697
+
698
+ const projectionIdRenderer = registry.byProjectionId?.[projection.Id]
699
+ if (projectionIdRenderer) {
700
+ return {
701
+ componentKey,
702
+ componentRole,
703
+ projection,
704
+ renderer: projectionIdRenderer,
705
+ rendererKey,
706
+ resolutionSource: 'projection-id',
707
+ }
708
+ }
709
+
710
+ const viewIdRenderer = projection.ViewId ? registry.byViewId?.[projection.ViewId] : undefined
711
+ if (viewIdRenderer) {
712
+ return {
713
+ componentKey,
714
+ componentRole,
715
+ projection,
716
+ renderer: viewIdRenderer,
717
+ rendererKey,
718
+ resolutionSource: 'view-id',
719
+ }
720
+ }
721
+
722
+ if (registry.fallback) {
723
+ return {
724
+ componentKey,
725
+ componentRole,
726
+ projection,
727
+ renderer: registry.fallback,
728
+ rendererKey,
729
+ resolutionSource: 'fallback',
730
+ }
731
+ }
732
+
733
+ return {
734
+ componentKey,
735
+ componentRole,
736
+ projection,
737
+ renderer: null,
738
+ rendererKey,
739
+ resolutionSource: null,
740
+ }
741
+ }
742
+
743
+ export function resolveDocumentWorkspaceProjectionCapability<
744
+ TExtra,
745
+ TComponentSystem = unknown,
746
+ TDesignSystem = unknown,
747
+ >({
748
+ capability,
749
+ componentSet,
750
+ module,
751
+ projection,
752
+ registry,
753
+ }: {
754
+ readonly capability: keyof DocumentWorkspaceProjectionCapabilityRegistry
755
+ readonly componentSet?: string | null
756
+ readonly module?: PresentationModuleDefinition | null
757
+ readonly projection: ProjectionDefinition
758
+ readonly registry: DocumentWorkspaceProjectionRendererRegistry<
759
+ TExtra,
760
+ TComponentSystem,
761
+ TDesignSystem
762
+ >
763
+ }) {
764
+ const bindings = registry.capabilities?.[capability]
765
+ if (!bindings) {
766
+ return false
767
+ }
768
+
769
+ const componentBinding = resolveDocumentWorkspaceProjectionComponentBinding(
770
+ module ?? null,
771
+ projection,
772
+ componentSet,
773
+ )
774
+ const componentKey = componentBinding?.componentKey ?? null
775
+ const componentRole = componentBinding?.componentRole ?? null
776
+
777
+ if (componentRole && bindings.byComponentRole?.[componentRole]) {
778
+ return true
779
+ }
780
+
781
+ if (componentKey && bindings.byComponentKey?.[componentKey]) {
782
+ return true
783
+ }
784
+
785
+ if (findDiscriminatorRenderer(
786
+ bindings.bySemanticReferenceKind,
787
+ projection.Coordinates?.SemanticReferenceKind,
788
+ semanticReferenceKindLabels,
789
+ )) {
790
+ return true
791
+ }
792
+
793
+ if (
794
+ findDiscriminatorRenderer(
795
+ bindings.bySubjectKind,
796
+ projection.Subject.Kind,
797
+ projectionSubjectKindLabels,
798
+ )
799
+ ) {
800
+ return true
801
+ }
802
+
803
+ if (
804
+ findDiscriminatorRenderer(
805
+ bindings.byProjectionKind,
806
+ projection.Kind,
807
+ projectionKindLabels,
808
+ )
809
+ ) {
810
+ return true
811
+ }
812
+
813
+ return Boolean(
814
+ bindings.byProjectionId?.[projection.Id] ||
815
+ (projection.ViewId ? bindings.byViewId?.[projection.ViewId] : false),
816
+ )
817
+ }
818
+
819
+ /**
820
+ * Resolves the frontend renderer role and optional concrete component override
821
+ * declared by a presentation target binding for a document projection.
822
+ */
823
+ function resolveDocumentWorkspaceProjectionComponentBinding(
824
+ module: PresentationModuleDefinition | null,
825
+ projection: ProjectionDefinition,
826
+ componentSet?: string | null,
827
+ ) {
828
+ if (!module) {
829
+ return null
830
+ }
831
+
832
+ const binding = findPresentationComponentBinding(module, {
833
+ bindingKind: createPresentationEnumDiscriminator(
834
+ presentationBindingKinds,
835
+ 'projectionRenderer',
836
+ 'ProjectionRenderer',
837
+ ),
838
+ componentSet,
839
+ id: projection.Id,
840
+ targetKind: createPresentationEnumDiscriminator(
841
+ presentationTargetKinds,
842
+ 'react',
843
+ 'React',
844
+ ),
845
+ })
846
+
847
+ if (!binding) {
848
+ return null
849
+ }
850
+
851
+ return {
852
+ componentKey: binding.ComponentKey ?? null,
853
+ componentRole: binding.ComponentRole ?? null,
854
+ }
855
+ }
856
+
857
+ /**
858
+ * Shared tree-state hook for document projections. It uses workspace runtime
859
+ * state when mounted under DocumentWorkspaceRuntime, and falls back to local
860
+ * component state when a tree renderer is tested or rendered in isolation.
861
+ */
862
+ export function useDocumentWorkspaceTreeState({
863
+ createSemanticSelection,
864
+ initialExpandedItemIds,
865
+ projectionId,
866
+ semanticReferenceKind,
867
+ }: {
868
+ readonly createSemanticSelection?: (
869
+ itemId: string,
870
+ ) => DocumentWorkspaceSemanticSelection | null
871
+ readonly initialExpandedItemIds: readonly string[]
872
+ readonly projectionId?: string | null
873
+ readonly semanticReferenceKind?: string | number | null
874
+ }) {
875
+ const runtime = useContext(DocumentWorkspaceRuntimeContext)
876
+ const runtimeState = runtime?.state
877
+ const selectSemanticReference = runtime?.selectSemanticReference
878
+ const setRuntimeTreeExpandedItemIds = runtime?.setTreeExpandedItemIds
879
+ const setRuntimeTreeNavigationHistory = runtime?.setTreeNavigationHistory
880
+ const setRuntimeTreeSearch = runtime?.setTreeSearch
881
+ const initialExpandedItemKey = initialExpandedItemIds.join('\u0000')
882
+ const [localExpandedItemState, setLocalExpandedItemState] = useState(() => ({
883
+ expandedItemIds: initialExpandedItemIds,
884
+ initialExpandedItemKey,
885
+ isUserControlled: false,
886
+ }))
887
+ const [localNavigationHistory, setLocalNavigationHistory] =
888
+ useState<DocumentWorkspaceNavigationHistory>(createInitialNavigationHistory)
889
+ const [localSearch, setLocalSearch] =
890
+ useState<DocumentWorkspaceTreeSearchState>(createInitialTreeSearchState)
891
+ const persistedTreeState =
892
+ runtimeState && projectionId ? runtimeState.treeStates[projectionId] : undefined
893
+ const treeState =
894
+ persistedTreeState ??
895
+ (runtimeState && projectionId ? createInitialTreeState() : null)
896
+ const localExpandedItemIds =
897
+ localExpandedItemState.isUserControlled ||
898
+ localExpandedItemState.initialExpandedItemKey === initialExpandedItemKey
899
+ ? localExpandedItemState.expandedItemIds
900
+ : initialExpandedItemIds
901
+ const expandedItemIds =
902
+ persistedTreeState?.expandedItemIds ??
903
+ (runtimeState && projectionId
904
+ ? initialExpandedItemIds
905
+ : localExpandedItemIds)
906
+ const navigationHistory = treeState?.navigationHistory ?? localNavigationHistory
907
+ const search = treeState?.search ?? localSearch
908
+
909
+ const setExpandedItemIds = useCallback(
910
+ (value: SetStateAction<readonly string[]>) => {
911
+ if (setRuntimeTreeExpandedItemIds && projectionId) {
912
+ setRuntimeTreeExpandedItemIds(projectionId, value)
913
+ } else {
914
+ setLocalExpandedItemState((current) => ({
915
+ expandedItemIds: applySetStateAction(
916
+ value,
917
+ current.isUserControlled ||
918
+ current.initialExpandedItemKey === initialExpandedItemKey
919
+ ? current.expandedItemIds
920
+ : initialExpandedItemIds,
921
+ ),
922
+ initialExpandedItemKey,
923
+ isUserControlled: true,
924
+ }))
925
+ }
926
+ },
927
+ [
928
+ initialExpandedItemIds,
929
+ initialExpandedItemKey,
930
+ projectionId,
931
+ setRuntimeTreeExpandedItemIds,
932
+ ],
933
+ )
934
+ const setNavigationHistory = useCallback(
935
+ (value: SetStateAction<DocumentWorkspaceNavigationHistory>) => {
936
+ if (setRuntimeTreeNavigationHistory && projectionId) {
937
+ setRuntimeTreeNavigationHistory(projectionId, value)
938
+ } else {
939
+ setLocalNavigationHistory(value)
940
+ }
941
+ },
942
+ [projectionId, setRuntimeTreeNavigationHistory],
943
+ )
944
+ const setSearch = useCallback(
945
+ (value: SetStateAction<DocumentWorkspaceTreeSearchState>) => {
946
+ if (setRuntimeTreeSearch && projectionId) {
947
+ setRuntimeTreeSearch(projectionId, value)
948
+ } else {
949
+ setLocalSearch(value)
950
+ }
951
+ },
952
+ [projectionId, setRuntimeTreeSearch],
953
+ )
954
+ const publishSelectedItem = useCallback(
955
+ (itemId: string | null) => {
956
+ if (!selectSemanticReference || !projectionId || !itemId) {
957
+ return
958
+ }
959
+
960
+ selectSemanticReference(
961
+ normalizeTreeSemanticSelection(
962
+ createSemanticSelection?.(itemId) ??
963
+ createDefaultTreeSemanticSelection(itemId, projectionId, semanticReferenceKind),
964
+ projectionId,
965
+ semanticReferenceKind,
966
+ ),
967
+ )
968
+ },
969
+ [createSemanticSelection, projectionId, selectSemanticReference, semanticReferenceKind],
970
+ )
971
+ const semanticSelections = useMemo(
972
+ () =>
973
+ filterSemanticSelectionsForProjection({
974
+ coordination: runtime?.coordination ?? [],
975
+ projectionId,
976
+ selections: runtimeState?.semanticSelections ?? [],
977
+ }),
978
+ [projectionId, runtime?.coordination, runtimeState?.semanticSelections],
979
+ )
980
+
981
+ return {
982
+ expandedItemIds,
983
+ navigationHistory,
984
+ onSelectedItemIdChange: publishSelectedItem,
985
+ search,
986
+ semanticSelections,
987
+ setExpandedItemIds,
988
+ setNavigationHistory,
989
+ setSearch,
990
+ }
991
+ }
992
+
993
+ export function useDocumentWorkspaceSemanticSelections({
994
+ projectionId,
995
+ }: {
996
+ readonly projectionId?: string | null
997
+ }) {
998
+ const runtime = useContext(DocumentWorkspaceRuntimeContext)
999
+ const runtimeState = runtime?.state
1000
+ return useMemo(
1001
+ () =>
1002
+ filterSemanticSelectionsForProjection({
1003
+ coordination: runtime?.coordination ?? [],
1004
+ projectionId,
1005
+ selections: runtimeState?.semanticSelections ?? [],
1006
+ }),
1007
+ [projectionId, runtime?.coordination, runtimeState?.semanticSelections],
1008
+ )
1009
+ }
1010
+
1011
+ export function useDocumentWorkspaceSemanticSelectionPublisher({
1012
+ projectionId,
1013
+ semanticReferenceKind,
1014
+ }: {
1015
+ readonly projectionId?: string | null
1016
+ readonly semanticReferenceKind?: string | number | null
1017
+ }) {
1018
+ const runtime = useContext(DocumentWorkspaceRuntimeContext)
1019
+ const selectSemanticReference = runtime?.selectSemanticReference
1020
+ return useCallback(
1021
+ (selection: DocumentWorkspaceSemanticSelection) => {
1022
+ if (!projectionId || !selectSemanticReference) {
1023
+ return
1024
+ }
1025
+
1026
+ selectSemanticReference(
1027
+ normalizeTreeSemanticSelection(
1028
+ selection,
1029
+ projectionId,
1030
+ semanticReferenceKind,
1031
+ ),
1032
+ )
1033
+ },
1034
+ [projectionId, selectSemanticReference, semanticReferenceKind],
1035
+ )
1036
+ }
1037
+
1038
+ export function createDocumentWorkspaceSemanticPath(
1039
+ segments: readonly DocumentWorkspaceSemanticPathSegment[],
1040
+ ): DocumentWorkspaceSemanticPath {
1041
+ return { Segments: segments }
1042
+ }
1043
+
1044
+ export function createDocumentWorkspaceSemanticReference({
1045
+ id,
1046
+ kind,
1047
+ label,
1048
+ path,
1049
+ }: {
1050
+ readonly id: string
1051
+ readonly kind?: string | number | null
1052
+ readonly label?: string | null
1053
+ readonly path?: DocumentWorkspaceSemanticPath | null
1054
+ }): DocumentWorkspaceSemanticReference {
1055
+ return {
1056
+ Id: id,
1057
+ Kind: kind,
1058
+ Path:
1059
+ path ??
1060
+ createDocumentWorkspaceSemanticPath([
1061
+ {
1062
+ Id: id,
1063
+ Kind: kind,
1064
+ Label: label ?? id,
1065
+ },
1066
+ ]),
1067
+ }
1068
+ }
1069
+
1070
+ export function documentWorkspaceSemanticReferenceMatchesSelection(
1071
+ reference: DocumentWorkspaceSemanticReference,
1072
+ selection: DocumentWorkspaceSemanticSelection,
1073
+ ) {
1074
+ return (
1075
+ semanticReferencesMatch(reference, selection.Target) ||
1076
+ semanticPathMatches(reference.Path ?? null, selection.SemanticPath ?? null) ||
1077
+ semanticPathMatches(reference.Path ?? null, selection.Target.Path ?? null) ||
1078
+ semanticPathContainsReference(selection.SemanticPath ?? null, reference) ||
1079
+ semanticPathContainsReference(selection.Target.Path ?? null, reference)
1080
+ )
1081
+ }
1082
+
1083
+ export function findDocumentWorkspaceSemanticTreeItemId(
1084
+ selection: DocumentWorkspaceSemanticSelection,
1085
+ nodes: Iterable<DocumentWorkspaceSemanticTreeNode>,
1086
+ ) {
1087
+ for (const node of nodes) {
1088
+ const references = [
1089
+ node.semanticReference,
1090
+ ...(node.relatedSemanticReferences ?? []),
1091
+ ]
1092
+ if (
1093
+ references.some((reference) =>
1094
+ documentWorkspaceSemanticReferenceMatchesSelection(reference, selection),
1095
+ )
1096
+ ) {
1097
+ return node.itemId
1098
+ }
1099
+ }
1100
+
1101
+ return null
1102
+ }
1103
+
1104
+ function resolveDocumentWorkspace(
1105
+ module: PresentationModuleDefinition | null,
1106
+ pageViewId: string,
1107
+ ) {
1108
+ // A route addresses a page view. The workspace may be the page itself or a
1109
+ // hosted child surface within one of the page regions.
1110
+ const pageView = findPresentationView(module, pageViewId)
1111
+ if (!pageView) {
1112
+ return null
1113
+ }
1114
+
1115
+ const workspaceView = findWorkspaceHostView(module, pageView)
1116
+ const workspaceRef = workspaceView?.Workspace ?? null
1117
+ if (!workspaceView || !workspaceRef) {
1118
+ return null
1119
+ }
1120
+
1121
+ const workspace = findPresentationWorkspace(module, workspaceRef.WorkspaceId)
1122
+ if (!workspace) {
1123
+ return null
1124
+ }
1125
+
1126
+ const documentProfileId =
1127
+ workspaceRef.DocumentProfileId ??
1128
+ workspace.DefaultDocumentProfileId ??
1129
+ workspace.DocumentProfiles?.[0]?.Id ??
1130
+ null
1131
+ const documentProfile = documentProfileId
1132
+ ? findPresentationDocumentProfile(workspace, documentProfileId)
1133
+ : null
1134
+ if (!documentProfile) {
1135
+ return null
1136
+ }
1137
+
1138
+ return { documentProfile, pageView, workspace, workspaceRef, workspaceView }
1139
+ }
1140
+
1141
+ function findWorkspaceHostView(
1142
+ module: PresentationModuleDefinition | null,
1143
+ pageView: ViewDefinition | null,
1144
+ ) {
1145
+ if (pageView?.Workspace) {
1146
+ return pageView
1147
+ }
1148
+
1149
+ for (const region of pageView?.Regions ?? []) {
1150
+ for (const viewId of region.ViewIds ?? []) {
1151
+ const view = findPresentationView(module, viewId)
1152
+ if (view?.Workspace) {
1153
+ return view
1154
+ }
1155
+ }
1156
+ }
1157
+
1158
+ return null
1159
+ }
1160
+
1161
+ function resolveInitialProjectionIds(
1162
+ workspaceRef: WorkspaceRefDefinition | null,
1163
+ projections: readonly ProjectionDefinition[],
1164
+ ) {
1165
+ // Workspace refs can narrow the active profile to a preferred projection set.
1166
+ // If that declaration is absent or stale, expose every profile projection.
1167
+ const projectionIds = new Set(projections.map((projection) => projection.Id))
1168
+ const initialProjectionIds = (workspaceRef?.InitialProjectionIds ?? []).filter(
1169
+ (projectionId) => projectionIds.has(projectionId),
1170
+ )
1171
+
1172
+ return initialProjectionIds.length > 0
1173
+ ? initialProjectionIds
1174
+ : projections.map((projection) => projection.Id)
1175
+ }
1176
+
1177
+ function resolveDefaultLayout(
1178
+ layout: { readonly DefaultModeId?: string | null } | null,
1179
+ ): DocumentWorkspaceLayout {
1180
+ return layout?.DefaultModeId === 'split' ? 'split' : 'single'
1181
+ }
1182
+
1183
+ function resolveActiveLayoutModeId(
1184
+ layout: DocumentWorkspaceLayout,
1185
+ layoutDefinition: {
1186
+ readonly DefaultModeId?: string | null
1187
+ readonly Modes?: readonly { readonly Id: string }[]
1188
+ } | null,
1189
+ ) {
1190
+ if (layout === 'split') {
1191
+ return layoutDefinition?.Modes?.find(
1192
+ (mode) => mode.Id.toLocaleLowerCase() === 'split',
1193
+ )?.Id ?? 'split'
1194
+ }
1195
+
1196
+ return layoutDefinition?.DefaultModeId ?? layoutDefinition?.Modes?.[0]?.Id ?? 'single'
1197
+ }
1198
+
1199
+ function createInitialRuntimeState(): DocumentWorkspaceRuntimeState {
1200
+ return {
1201
+ semanticSelections: [],
1202
+ treeStates: {},
1203
+ }
1204
+ }
1205
+
1206
+ function createInitialTreeState(): DocumentWorkspaceTreeStateSnapshot {
1207
+ return {
1208
+ expandedItemIds: [],
1209
+ navigationHistory: createInitialNavigationHistory(),
1210
+ search: createInitialTreeSearchState(),
1211
+ }
1212
+ }
1213
+
1214
+ function createInitialNavigationHistory(): DocumentWorkspaceNavigationHistory {
1215
+ return { entries: [], index: -1 }
1216
+ }
1217
+
1218
+ function createInitialTreeSearchState(): DocumentWorkspaceTreeSearchState {
1219
+ return { activeMatchIndex: 0, isOpen: false, query: '' }
1220
+ }
1221
+
1222
+ function createDefaultTreeSemanticSelection(
1223
+ itemId: string,
1224
+ projectionId: string,
1225
+ semanticReferenceKind?: string | number | null,
1226
+ ): DocumentWorkspaceSemanticSelection {
1227
+ const path = createDocumentWorkspaceSemanticPath([
1228
+ { Id: projectionId, Kind: semanticReferenceKind, Label: projectionId },
1229
+ { Id: itemId, Kind: semanticReferenceKind, Label: itemId },
1230
+ ])
1231
+ return {
1232
+ SourceProjectionId: projectionId,
1233
+ SemanticPath: path,
1234
+ Target: {
1235
+ Id: itemId,
1236
+ Kind: semanticReferenceKind,
1237
+ Path: path,
1238
+ },
1239
+ }
1240
+ }
1241
+
1242
+ function normalizeTreeSemanticSelection(
1243
+ selection: DocumentWorkspaceSemanticSelection,
1244
+ projectionId: string,
1245
+ semanticReferenceKind?: string | number | null,
1246
+ ): DocumentWorkspaceSemanticSelection {
1247
+ const targetPath =
1248
+ selection.Target.Path ??
1249
+ selection.SemanticPath ??
1250
+ createDocumentWorkspaceSemanticPath([
1251
+ {
1252
+ Id: selection.Target.Id,
1253
+ Kind: selection.Target.Kind ?? semanticReferenceKind,
1254
+ Label: selection.Target.Id,
1255
+ },
1256
+ ])
1257
+ return {
1258
+ SourceProjectionId: selection.SourceProjectionId ?? projectionId,
1259
+ SemanticPath: selection.SemanticPath ?? targetPath,
1260
+ Target: {
1261
+ ...selection.Target,
1262
+ Kind: selection.Target.Kind ?? semanticReferenceKind,
1263
+ Path: targetPath,
1264
+ },
1265
+ }
1266
+ }
1267
+
1268
+ function createCoordinatedSemanticSelections(
1269
+ selection: DocumentWorkspaceSemanticSelection,
1270
+ coordination: readonly CoordinationDefinition[],
1271
+ ): readonly DocumentWorkspaceSemanticSelection[] {
1272
+ if (!hasSemanticSelectionCoordination(coordination, selection.SourceProjectionId ?? null)) {
1273
+ return [selection]
1274
+ }
1275
+
1276
+ return [selection]
1277
+ }
1278
+
1279
+ function filterSemanticSelectionsForProjection({
1280
+ coordination,
1281
+ projectionId,
1282
+ selections,
1283
+ }: {
1284
+ readonly coordination: readonly CoordinationDefinition[]
1285
+ readonly projectionId?: string | null
1286
+ readonly selections: readonly DocumentWorkspaceSemanticSelection[]
1287
+ }): readonly DocumentWorkspaceSemanticSelection[] {
1288
+ if (!projectionId) {
1289
+ return selections
1290
+ }
1291
+
1292
+ return selections.filter((selection) =>
1293
+ canRevealSemanticSelectionForProjection(coordination, projectionId, selection),
1294
+ )
1295
+ }
1296
+
1297
+ function hasSemanticSelectionCoordination(
1298
+ coordination: readonly CoordinationDefinition[],
1299
+ sourceProjectionId: string | null,
1300
+ ) {
1301
+ return coordination.some(
1302
+ (definition) =>
1303
+ matchesCoordinationTrigger(definition, sourceProjectionId) &&
1304
+ matchesPresentationEnum(definition.Action.Kind, setSemanticSelectionActionKind),
1305
+ )
1306
+ }
1307
+
1308
+ function canRevealSemanticSelectionForProjection(
1309
+ coordination: readonly CoordinationDefinition[],
1310
+ projectionId: string,
1311
+ selection: DocumentWorkspaceSemanticSelection,
1312
+ ) {
1313
+ const revealDefinitions = coordination.filter(
1314
+ (definition) =>
1315
+ matchesCoordinationTrigger(definition, selection.SourceProjectionId ?? null) &&
1316
+ matchesPresentationEnum(definition.Action.Kind, revealSemanticSelectionActionKind),
1317
+ )
1318
+ if (revealDefinitions.length === 0) {
1319
+ return true
1320
+ }
1321
+
1322
+ return revealDefinitions.some((definition) =>
1323
+ definition.Action.TargetProjectionIds.includes(projectionId),
1324
+ )
1325
+ }
1326
+
1327
+ function matchesCoordinationTrigger(
1328
+ definition: CoordinationDefinition,
1329
+ sourceProjectionId: string | null,
1330
+ ) {
1331
+ return (
1332
+ matchesPresentationEnum(definition.Trigger.Kind, selectionChangedTriggerKind) &&
1333
+ (!definition.Trigger.SourceProjectionId ||
1334
+ definition.Trigger.SourceProjectionId === sourceProjectionId)
1335
+ )
1336
+ }
1337
+
1338
+ function semanticReferencesMatch(
1339
+ left: DocumentWorkspaceSemanticReference,
1340
+ right: DocumentWorkspaceSemanticReference,
1341
+ ) {
1342
+ return (
1343
+ left.Id === right.Id &&
1344
+ semanticReferenceKindsMatch(left.Kind ?? null, right.Kind ?? null)
1345
+ )
1346
+ }
1347
+
1348
+ function semanticPathMatches(
1349
+ left: DocumentWorkspaceSemanticPath | null,
1350
+ right: DocumentWorkspaceSemanticPath | null,
1351
+ ) {
1352
+ if (!left || !right || left.Segments.length !== right.Segments.length) {
1353
+ return false
1354
+ }
1355
+
1356
+ return left.Segments.every((leftSegment, index) => {
1357
+ const rightSegment = right.Segments[index]
1358
+ return (
1359
+ leftSegment.Id === rightSegment.Id &&
1360
+ semanticReferenceKindsMatch(leftSegment.Kind ?? null, rightSegment.Kind ?? null)
1361
+ )
1362
+ })
1363
+ }
1364
+
1365
+ function semanticPathContainsReference(
1366
+ path: DocumentWorkspaceSemanticPath | null,
1367
+ reference: DocumentWorkspaceSemanticReference,
1368
+ ) {
1369
+ return (
1370
+ path?.Segments.some(
1371
+ (segment) =>
1372
+ segment.Id === reference.Id &&
1373
+ semanticReferenceKindsMatch(segment.Kind ?? null, reference.Kind ?? null),
1374
+ ) ?? false
1375
+ )
1376
+ }
1377
+
1378
+ function semanticReferenceKindsMatch(
1379
+ left: string | number | null,
1380
+ right: string | number | null,
1381
+ ) {
1382
+ if (left === null || right === null) {
1383
+ return true
1384
+ }
1385
+
1386
+ const leftKeys = createDiscriminatorKeys(left, semanticReferenceKindLabels)
1387
+ const rightKeys = createDiscriminatorKeys(right, semanticReferenceKindLabels)
1388
+ for (const key of leftKeys) {
1389
+ if (rightKeys.has(key)) {
1390
+ return true
1391
+ }
1392
+ }
1393
+
1394
+ return false
1395
+ }
1396
+
1397
+ function createDocumentProjectionRendererResolution<
1398
+ TExtra,
1399
+ TComponentSystem = unknown,
1400
+ TDesignSystem = unknown,
1401
+ >(
1402
+ projection: ProjectionDefinition | null,
1403
+ ): DocumentWorkspaceProjectionRendererResolution<TExtra, TComponentSystem, TDesignSystem> {
1404
+ return {
1405
+ componentKey: null,
1406
+ componentRole: null,
1407
+ projection,
1408
+ renderer: null,
1409
+ rendererKey: projection?.RendererKey ?? null,
1410
+ resolutionSource: null,
1411
+ }
1412
+ }
1413
+
1414
+ const selectionChangedTriggerKind = createPresentationEnumDiscriminator(
1415
+ coordinationTriggerKinds,
1416
+ 'selectionChanged',
1417
+ 'SelectionChanged',
1418
+ )
1419
+ const setSemanticSelectionActionKind = createPresentationEnumDiscriminator(
1420
+ coordinationActionKinds,
1421
+ 'setSemanticSelection',
1422
+ 'SetSemanticSelection',
1423
+ )
1424
+ const revealSemanticSelectionActionKind = createPresentationEnumDiscriminator(
1425
+ coordinationActionKinds,
1426
+ 'revealSemanticSelection',
1427
+ 'RevealSemanticSelection',
1428
+ )
1429
+
1430
+ function findDiscriminatorRenderer<TValue>(
1431
+ renderers: Readonly<Record<string, TValue>> | undefined,
1432
+ value: string | number | null | undefined,
1433
+ labels: Readonly<Record<string | number, string>>,
1434
+ ) {
1435
+ if (!renderers || value === null || value === undefined) {
1436
+ return undefined
1437
+ }
1438
+
1439
+ for (const key of createDiscriminatorKeys(value, labels)) {
1440
+ const renderer = renderers[key]
1441
+ if (renderer) {
1442
+ return renderer
1443
+ }
1444
+ }
1445
+
1446
+ return undefined
1447
+ }
1448
+
1449
+ function createDiscriminatorKeys(
1450
+ value: string | number,
1451
+ labels: Readonly<Record<string | number, string>>,
1452
+ ) {
1453
+ const keys = new Set<string>([String(value)])
1454
+ const directLabel = labels[value]
1455
+ if (directLabel) {
1456
+ keys.add(directLabel)
1457
+ }
1458
+
1459
+ if (typeof value === 'string') {
1460
+ const normalizedValue = value.toLocaleLowerCase()
1461
+ for (const [enumValue, label] of Object.entries(labels)) {
1462
+ if (label.toLocaleLowerCase() === normalizedValue) {
1463
+ keys.add(enumValue)
1464
+ keys.add(label)
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ return keys
1470
+ }
1471
+
1472
+ function updateRuntimeTreeState(
1473
+ current: {
1474
+ readonly instanceKey: string
1475
+ readonly value: DocumentWorkspaceRuntimeState
1476
+ },
1477
+ instanceKey: string,
1478
+ projectionId: string,
1479
+ update: (
1480
+ state: DocumentWorkspaceTreeStateSnapshot,
1481
+ ) => DocumentWorkspaceTreeStateSnapshot,
1482
+ ) {
1483
+ // Treat an instance-key change as a new workspace session. This prevents tree
1484
+ // state from one document/profile from being reused by another.
1485
+ const base =
1486
+ current.instanceKey === instanceKey ? current.value : createInitialRuntimeState()
1487
+ const currentTreeState = base.treeStates[projectionId] ?? createInitialTreeState()
1488
+
1489
+ return {
1490
+ instanceKey,
1491
+ value: {
1492
+ ...base,
1493
+ treeStates: {
1494
+ ...base.treeStates,
1495
+ [projectionId]: update(currentTreeState),
1496
+ },
1497
+ },
1498
+ }
1499
+ }
1500
+
1501
+ function applySetStateAction<T>(action: SetStateAction<T>, current: T) {
1502
+ return typeof action === 'function'
1503
+ ? (action as (current: T) => T)(current)
1504
+ : action
1505
+ }