@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,835 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react'
2
+
3
+ import {
4
+ findPresentationAction,
5
+ findPresentationFlow,
6
+ findPresentationView,
7
+ readObjectPath,
8
+ type ActionDefinition,
9
+ type FlowDefinition,
10
+ type FlowStateDefinition,
11
+ type FlowTransitionDefinition,
12
+ type PresentationModuleDefinition,
13
+ type ViewDefinition,
14
+ } from '@cohesivesystems/presentation-core'
15
+ import {
16
+ flowStateKindLabels,
17
+ flowStateKinds,
18
+ preparationKinds,
19
+ } from '@cohesivesystems/presentation-contracts'
20
+
21
+ export interface PresentationFlowInstance {
22
+ readonly data: Readonly<Record<string, unknown>>
23
+ readonly flowId: string
24
+ readonly stateId: string
25
+ }
26
+
27
+ export interface PresentationFlowStartOptions {
28
+ readonly actionId?: string | null
29
+ readonly data?: Readonly<Record<string, unknown>>
30
+ readonly event?: string | null
31
+ readonly flowId: string
32
+ readonly stateId?: string | null
33
+ }
34
+
35
+ export interface PresentationFlowDispatchOptions {
36
+ readonly actionId?: string | null
37
+ readonly data?: Readonly<Record<string, unknown>>
38
+ }
39
+
40
+ export interface PresentationFlowTransitionToOptions {
41
+ readonly data?: Readonly<Record<string, unknown>>
42
+ readonly flowId?: string | null
43
+ }
44
+
45
+ export interface PresentationFlowTransitionContext {
46
+ readonly flow: FlowDefinition
47
+ readonly fromState: FlowStateDefinition | null
48
+ readonly instance: PresentationFlowInstance
49
+ readonly toState: FlowStateDefinition
50
+ readonly transition: FlowTransitionDefinition | null
51
+ }
52
+
53
+ export interface PresentationFlowUnhandledEventContext {
54
+ readonly actionId: string | null
55
+ readonly event: string
56
+ readonly flow: FlowDefinition
57
+ readonly instance: PresentationFlowInstance
58
+ }
59
+
60
+ export interface UsePresentationFlowRuntimeOptions {
61
+ readonly initialData?: Readonly<Record<string, unknown>>
62
+ readonly initialFlowId?: string | null
63
+ readonly initialStateId?: string | null
64
+ readonly module: PresentationModuleDefinition | null
65
+ readonly onTransition?: (context: PresentationFlowTransitionContext) => void
66
+ readonly onUnhandledEvent?: (context: PresentationFlowUnhandledEventContext) => void
67
+ }
68
+
69
+ export interface PresentationActionPreparedFlow {
70
+ readonly canCancel: boolean
71
+ readonly flowId: string
72
+ readonly promptViewId: string | null
73
+ readonly requiresExplicitCommit: boolean
74
+ }
75
+
76
+ export interface PresentationFlowRuntimeSnapshot {
77
+ readonly activeFlow: FlowDefinition | null
78
+ readonly activeInstance: PresentationFlowInstance | null
79
+ readonly activeState: FlowStateDefinition | null
80
+ readonly activeView: ViewDefinition | null
81
+ readonly clearFlow: () => void
82
+ readonly dispatchAction: (actionId: string, options?: PresentationFlowDispatchOptions) => boolean
83
+ readonly dispatchEvent: (event: string, options?: PresentationFlowDispatchOptions) => boolean
84
+ readonly dispatchTransition: (transitionId: string, options?: PresentationFlowDispatchOptions) => boolean
85
+ readonly isFlowActive: (flowId: string) => boolean
86
+ readonly isStateActive: (stateId: string) => boolean
87
+ readonly startFlow: (options: PresentationFlowStartOptions) => boolean
88
+ readonly transitionTo: (stateId: string, options?: PresentationFlowTransitionToOptions) => boolean
89
+ }
90
+
91
+ export interface PresentationFlowRuntimeEntry {
92
+ readonly flow: FlowDefinition
93
+ readonly instance: PresentationFlowInstance
94
+ readonly state: FlowStateDefinition
95
+ readonly view: ViewDefinition | null
96
+ }
97
+
98
+ export interface PresentationFlowRuntimeRegistrySnapshot {
99
+ readonly activeEntries: readonly PresentationFlowRuntimeEntry[]
100
+ readonly clearFlow: (flowId: string) => void
101
+ readonly getRuntime: (flowId: string) => PresentationFlowRuntimeSnapshot
102
+ }
103
+
104
+ /**
105
+ * Tests flow-state kind values across generated numeric constants and backend
106
+ * string enum serialization.
107
+ */
108
+ export function isPresentationFlowStateKind(
109
+ value: unknown,
110
+ ...kinds: readonly (number | string)[]
111
+ ) {
112
+ return kinds.some((kind) => matchesFlowStateKind(value, kind))
113
+ }
114
+
115
+ /**
116
+ * Returns true when a flow instance is active for a specific data key/value and
117
+ * the active state is a visible prompt-like state.
118
+ */
119
+ export function isPresentationFlowSurfaceOpenForData({
120
+ dataKey,
121
+ dataValue,
122
+ flowId,
123
+ runtime,
124
+ }: {
125
+ readonly dataKey: string
126
+ readonly dataValue: string | null
127
+ readonly flowId: string
128
+ readonly runtime: PresentationFlowRuntimeSnapshot
129
+ }) {
130
+ if (!dataValue || !runtime.isFlowActive(flowId) || !runtime.activeState?.ViewId) {
131
+ return false
132
+ }
133
+
134
+ return runtime.activeInstance?.data[dataKey] === dataValue &&
135
+ isPresentationFlowStateKind(
136
+ runtime.activeState.Kind,
137
+ flowStateKinds.pending,
138
+ flowStateKinds.prompt,
139
+ flowStateKinds.error,
140
+ )
141
+ }
142
+
143
+ /** Finds the first visible prompt/pending/error state in a flow definition. */
144
+ export function findPresentationFlowSurfaceState(
145
+ flow: FlowDefinition | null | undefined,
146
+ ) {
147
+ return flow?.States.find((state) =>
148
+ Boolean(state.ViewId) &&
149
+ isPresentationFlowStateKind(
150
+ state.Kind,
151
+ flowStateKinds.pending,
152
+ flowStateKinds.prompt,
153
+ flowStateKinds.error,
154
+ ),
155
+ ) ?? null
156
+ }
157
+
158
+ /** Finds the first visible error state in a flow definition. */
159
+ export function findPresentationFlowErrorSurfaceState(
160
+ flow: FlowDefinition | null | undefined,
161
+ ) {
162
+ return flow?.States.find((state) =>
163
+ Boolean(state.ViewId) &&
164
+ isPresentationFlowStateKind(state.Kind, flowStateKinds.error),
165
+ ) ?? null
166
+ }
167
+
168
+ /** Finds the first state matching one of the requested semantic flow kinds. */
169
+ export function findPresentationFlowStateByKind(
170
+ flow: FlowDefinition | null | undefined,
171
+ ...kinds: readonly (number | string)[]
172
+ ) {
173
+ return flow?.States.find((state) =>
174
+ isPresentationFlowStateKind(state.Kind, ...kinds),
175
+ ) ?? null
176
+ }
177
+
178
+ /**
179
+ * Advances an active runtime instance to a state with the requested semantic
180
+ * kind, preferring a valid declared transition from the current state.
181
+ */
182
+ export function advancePresentationFlowToStateKind({
183
+ allowStateFallback = true,
184
+ data,
185
+ flow,
186
+ runtime,
187
+ stateKinds,
188
+ }: {
189
+ readonly allowStateFallback?: boolean
190
+ readonly data?: Readonly<Record<string, unknown>>
191
+ readonly flow: FlowDefinition | null | undefined
192
+ readonly runtime: PresentationFlowRuntimeSnapshot
193
+ readonly stateKinds: readonly (number | string)[]
194
+ }) {
195
+ if (!flow || !runtime.activeInstance) {
196
+ return false
197
+ }
198
+
199
+ const transition = findPresentationFlowTransitionToStateKind({
200
+ data: {
201
+ ...runtime.activeInstance.data,
202
+ ...(data ?? {}),
203
+ },
204
+ flow,
205
+ fromStateId: runtime.activeInstance.stateId,
206
+ stateKinds,
207
+ })
208
+ if (transition && runtime.dispatchTransition(transition.Id, { data })) {
209
+ return true
210
+ }
211
+
212
+ if (!allowStateFallback) {
213
+ return false
214
+ }
215
+
216
+ const state = findPresentationFlowStateByKind(flow, ...stateKinds)
217
+ return state
218
+ ? runtime.transitionTo(state.Id, { data, flowId: flow.Id })
219
+ : false
220
+ }
221
+
222
+ /** Finds a valid transition from a state to any state with the requested kind. */
223
+ export function findPresentationFlowTransitionToStateKind({
224
+ data,
225
+ flow,
226
+ fromStateId,
227
+ stateKinds,
228
+ }: {
229
+ readonly data: Readonly<Record<string, unknown>>
230
+ readonly flow: FlowDefinition
231
+ readonly fromStateId: string
232
+ readonly stateKinds: readonly (number | string)[]
233
+ }) {
234
+ const targetStateIds = new Set(
235
+ flow.States
236
+ .filter((state) => isPresentationFlowStateKind(state.Kind, ...stateKinds))
237
+ .map((state) => state.Id),
238
+ )
239
+ return flow.Transitions.find((transition) =>
240
+ transition.FromStateId === fromStateId &&
241
+ targetStateIds.has(transition.ToStateId) &&
242
+ matchesPresentationFlowTransitionGuard(transition, data),
243
+ ) ?? null
244
+ }
245
+
246
+ /** Finds a valid action/event transition from the given flow state. */
247
+ export function findPresentationFlowTransition({
248
+ actionId,
249
+ data,
250
+ event,
251
+ flow,
252
+ fromStateId,
253
+ }: {
254
+ readonly actionId?: string | null
255
+ readonly data: Readonly<Record<string, unknown>>
256
+ readonly event?: string | null
257
+ readonly flow: FlowDefinition
258
+ readonly fromStateId: string
259
+ }) {
260
+ const candidates = flow.Transitions.filter((transition) =>
261
+ transition.FromStateId === fromStateId &&
262
+ (
263
+ (event ? transition.Event === event : false) ||
264
+ (actionId ? transition.ActionId === actionId : false)
265
+ ),
266
+ )
267
+
268
+ return candidates.find((transition) =>
269
+ matchesPresentationFlowTransitionGuard(transition, data),
270
+ ) ?? null
271
+ }
272
+
273
+ /**
274
+ * Interprets client-resident Presentation IR flows into local React state.
275
+ *
276
+ * The runtime deliberately stays semantic: it tracks the active flow, resolves
277
+ * transitions by event or action id, exposes the active prompt view, and leaves
278
+ * endpoint execution, persistence, and custom rendering to host-specific escape
279
+ * hatches. That lets app code migrate from imperative conditionals toward IR
280
+ * projection without requiring every flow side effect to be generalized first.
281
+ */
282
+ export function usePresentationFlowRuntime({
283
+ initialData,
284
+ initialFlowId,
285
+ initialStateId,
286
+ module,
287
+ onTransition,
288
+ onUnhandledEvent,
289
+ }: UsePresentationFlowRuntimeOptions): PresentationFlowRuntimeSnapshot {
290
+ const [instance, setInstance] = useState<PresentationFlowInstance | null>(() => {
291
+ if (!initialFlowId) {
292
+ return null
293
+ }
294
+
295
+ const flow = findPresentationFlow<FlowDefinition>(module, initialFlowId)
296
+ if (!flow) {
297
+ return null
298
+ }
299
+
300
+ return {
301
+ data: initialData ?? {},
302
+ flowId: initialFlowId,
303
+ stateId: initialStateId ?? flow.InitialStateId,
304
+ }
305
+ })
306
+
307
+ const activeFlow = useMemo(
308
+ () =>
309
+ instance
310
+ ? findPresentationFlow<FlowDefinition>(module, instance.flowId)
311
+ : null,
312
+ [instance, module],
313
+ )
314
+ const activeState = useMemo(
315
+ () => activeFlow?.States.find((state) => state.Id === instance?.stateId) ?? null,
316
+ [activeFlow, instance],
317
+ )
318
+ const activeView = useMemo(
319
+ () =>
320
+ activeState?.ViewId
321
+ ? findPresentationView<ViewDefinition>(module, activeState.ViewId)
322
+ : null,
323
+ [activeState, module],
324
+ )
325
+
326
+ function startFlow(options: PresentationFlowStartOptions) {
327
+ const flow = findPresentationFlow<FlowDefinition>(module, options.flowId)
328
+ if (!flow) {
329
+ return false
330
+ }
331
+
332
+ const data = options.data ?? {}
333
+ const fromStateId = options.stateId ?? flow.InitialStateId
334
+ const transition = options.stateId
335
+ ? null
336
+ : resolveFlowTransition({
337
+ actionId: options.actionId,
338
+ data,
339
+ event: options.event,
340
+ flow,
341
+ fromStateId,
342
+ })
343
+ if (!options.stateId && (options.event || options.actionId) && !transition) {
344
+ return false
345
+ }
346
+ const toStateId = transition?.ToStateId ?? fromStateId
347
+ const toState = flow.States.find((state) => state.Id === toStateId)
348
+ if (!toState) {
349
+ return false
350
+ }
351
+
352
+ const nextInstance = {
353
+ data,
354
+ flowId: flow.Id,
355
+ stateId: toState.Id,
356
+ } satisfies PresentationFlowInstance
357
+ setInstance(nextInstance)
358
+ onTransition?.({
359
+ flow,
360
+ fromState: flow.States.find((state) => state.Id === fromStateId) ?? null,
361
+ instance: nextInstance,
362
+ toState,
363
+ transition,
364
+ })
365
+ return true
366
+ }
367
+
368
+ function dispatchEvent(event: string, options?: PresentationFlowDispatchOptions) {
369
+ if (!instance || !activeFlow) {
370
+ return false
371
+ }
372
+
373
+ const data = { ...instance.data, ...(options?.data ?? {}) }
374
+ const transition = resolveFlowTransition({
375
+ actionId: options?.actionId,
376
+ data,
377
+ event,
378
+ flow: activeFlow,
379
+ fromStateId: instance.stateId,
380
+ })
381
+ if (!transition) {
382
+ onUnhandledEvent?.({
383
+ actionId: options?.actionId ?? null,
384
+ event,
385
+ flow: activeFlow,
386
+ instance,
387
+ })
388
+ return false
389
+ }
390
+
391
+ return applyFlowState(activeFlow, transition.ToStateId, {
392
+ data,
393
+ transition,
394
+ })
395
+ }
396
+
397
+ function dispatchAction(actionId: string, options?: PresentationFlowDispatchOptions) {
398
+ const action = findPresentationAction<ActionDefinition>(module, actionId)
399
+ return dispatchEvent(action?.Binding.Id ?? actionId, {
400
+ ...options,
401
+ actionId,
402
+ })
403
+ }
404
+
405
+ function dispatchTransition(transitionId: string, options?: PresentationFlowDispatchOptions) {
406
+ if (!instance || !activeFlow) {
407
+ return false
408
+ }
409
+
410
+ const data = { ...instance.data, ...(options?.data ?? {}) }
411
+ const transition = activeFlow.Transitions.find((candidate) =>
412
+ candidate.Id === transitionId &&
413
+ candidate.FromStateId === instance.stateId &&
414
+ matchesPresentationFlowTransitionGuard(candidate, data),
415
+ )
416
+ if (!transition) {
417
+ return false
418
+ }
419
+
420
+ return applyFlowState(activeFlow, transition.ToStateId, {
421
+ data,
422
+ transition,
423
+ })
424
+ }
425
+
426
+ function transitionTo(stateId: string, options?: PresentationFlowTransitionToOptions) {
427
+ const flowId = options?.flowId ?? instance?.flowId
428
+ const flow = flowId ? findPresentationFlow<FlowDefinition>(module, flowId) : null
429
+ if (!flow) {
430
+ return false
431
+ }
432
+
433
+ return applyFlowState(flow, stateId, {
434
+ data: {
435
+ ...(instance?.flowId === flow.Id ? instance.data : {}),
436
+ ...(options?.data ?? {}),
437
+ },
438
+ transition: null,
439
+ })
440
+ }
441
+
442
+ function applyFlowState(
443
+ flow: FlowDefinition,
444
+ stateId: string,
445
+ options: {
446
+ readonly data: Readonly<Record<string, unknown>>
447
+ readonly transition: FlowTransitionDefinition | null
448
+ },
449
+ ) {
450
+ const toState = flow.States.find((state) => state.Id === stateId)
451
+ if (!toState) {
452
+ return false
453
+ }
454
+
455
+ const nextInstance = {
456
+ data: options.data,
457
+ flowId: flow.Id,
458
+ stateId: toState.Id,
459
+ } satisfies PresentationFlowInstance
460
+ const fromState = instance?.flowId === flow.Id
461
+ ? flow.States.find((state) => state.Id === instance.stateId) ?? null
462
+ : null
463
+ setInstance(nextInstance)
464
+ onTransition?.({
465
+ flow,
466
+ fromState,
467
+ instance: nextInstance,
468
+ toState,
469
+ transition: options.transition,
470
+ })
471
+ return true
472
+ }
473
+
474
+ return {
475
+ activeFlow,
476
+ activeInstance: instance,
477
+ activeState,
478
+ activeView,
479
+ clearFlow: () => setInstance(null),
480
+ dispatchAction,
481
+ dispatchEvent,
482
+ dispatchTransition,
483
+ isFlowActive: (flowId) => instance?.flowId === flowId,
484
+ isStateActive: (stateId) => instance?.stateId === stateId,
485
+ startFlow,
486
+ transitionTo,
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Maintains the active instances for all client-resident Presentation IR flows
492
+ * in a host. Callers can still ask for a flow-scoped runtime, but prompt
493
+ * projection can iterate active entries instead of wiring each flow manually.
494
+ */
495
+ export function usePresentationFlowRuntimeRegistry({
496
+ module,
497
+ onTransition,
498
+ onUnhandledEvent,
499
+ }: Pick<UsePresentationFlowRuntimeOptions, 'module' | 'onTransition' | 'onUnhandledEvent'>): PresentationFlowRuntimeRegistrySnapshot {
500
+ const [instances, setInstances] = useState<Readonly<Record<string, PresentationFlowInstance>>>({})
501
+ const instancesRef = useRef(instances)
502
+
503
+ useEffect(() => {
504
+ instancesRef.current = instances
505
+ }, [instances])
506
+
507
+ const activeEntries = useMemo(
508
+ () =>
509
+ Object.values(instances)
510
+ .map((instance) => resolvePresentationFlowRuntimeEntry(module, instance))
511
+ .filter((entry): entry is PresentationFlowRuntimeEntry => entry !== null),
512
+ [instances, module],
513
+ )
514
+
515
+ function setFlowInstance(instance: PresentationFlowInstance | null, flowId: string) {
516
+ const current = instancesRef.current
517
+ const next = { ...current }
518
+ if (instance) {
519
+ next[flowId] = instance
520
+ } else {
521
+ delete next[flowId]
522
+ }
523
+ instancesRef.current = next
524
+ setInstances(next)
525
+ }
526
+
527
+ function clearFlow(flowId: string) {
528
+ setFlowInstance(null, flowId)
529
+ }
530
+
531
+ function getRuntime(flowId: string): PresentationFlowRuntimeSnapshot {
532
+ const instance = instances[flowId] ?? null
533
+ const activeFlow = instance
534
+ ? findPresentationFlow<FlowDefinition>(module, instance.flowId)
535
+ : null
536
+ const activeState =
537
+ activeFlow?.States.find((state) => state.Id === instance?.stateId) ?? null
538
+ const activeView =
539
+ activeState?.ViewId
540
+ ? findPresentationView<ViewDefinition>(module, activeState.ViewId)
541
+ : null
542
+
543
+ function startFlow(options: PresentationFlowStartOptions) {
544
+ const flow = findPresentationFlow<FlowDefinition>(module, options.flowId)
545
+ if (!flow) {
546
+ return false
547
+ }
548
+
549
+ const data = options.data ?? {}
550
+ const fromStateId = options.stateId ?? flow.InitialStateId
551
+ const transition = options.stateId
552
+ ? null
553
+ : resolveFlowTransition({
554
+ actionId: options.actionId,
555
+ data,
556
+ event: options.event,
557
+ flow,
558
+ fromStateId,
559
+ })
560
+ if (!options.stateId && (options.event || options.actionId) && !transition) {
561
+ return false
562
+ }
563
+
564
+ const toStateId = transition?.ToStateId ?? fromStateId
565
+ return applyRegistryFlowState(flow, toStateId, {
566
+ data,
567
+ previousInstance: instancesRef.current[flow.Id] ?? null,
568
+ transition,
569
+ })
570
+ }
571
+
572
+ function dispatchEvent(event: string, options?: PresentationFlowDispatchOptions) {
573
+ const currentInstance = instancesRef.current[flowId] ?? null
574
+ const flow = currentInstance
575
+ ? findPresentationFlow<FlowDefinition>(module, currentInstance.flowId)
576
+ : null
577
+ if (!currentInstance || !flow) {
578
+ return false
579
+ }
580
+
581
+ const data = { ...currentInstance.data, ...(options?.data ?? {}) }
582
+ const transition = resolveFlowTransition({
583
+ actionId: options?.actionId,
584
+ data,
585
+ event,
586
+ flow,
587
+ fromStateId: currentInstance.stateId,
588
+ })
589
+ if (!transition) {
590
+ onUnhandledEvent?.({
591
+ actionId: options?.actionId ?? null,
592
+ event,
593
+ flow,
594
+ instance: currentInstance,
595
+ })
596
+ return false
597
+ }
598
+
599
+ return applyRegistryFlowState(flow, transition.ToStateId, {
600
+ data,
601
+ previousInstance: currentInstance,
602
+ transition,
603
+ })
604
+ }
605
+
606
+ function dispatchAction(actionId: string, options?: PresentationFlowDispatchOptions) {
607
+ const action = findPresentationAction<ActionDefinition>(module, actionId)
608
+ return dispatchEvent(action?.Binding.Id ?? actionId, {
609
+ ...options,
610
+ actionId,
611
+ })
612
+ }
613
+
614
+ function dispatchTransition(
615
+ transitionId: string,
616
+ options?: PresentationFlowDispatchOptions,
617
+ ) {
618
+ const currentInstance = instancesRef.current[flowId] ?? null
619
+ const flow = currentInstance
620
+ ? findPresentationFlow<FlowDefinition>(module, currentInstance.flowId)
621
+ : null
622
+ if (!currentInstance || !flow) {
623
+ return false
624
+ }
625
+
626
+ const data = { ...currentInstance.data, ...(options?.data ?? {}) }
627
+ const transition = flow.Transitions.find((candidate) =>
628
+ candidate.Id === transitionId &&
629
+ candidate.FromStateId === currentInstance.stateId &&
630
+ matchesPresentationFlowTransitionGuard(candidate, data),
631
+ )
632
+ if (!transition) {
633
+ return false
634
+ }
635
+
636
+ return applyRegistryFlowState(flow, transition.ToStateId, {
637
+ data,
638
+ previousInstance: currentInstance,
639
+ transition,
640
+ })
641
+ }
642
+
643
+ function transitionTo(stateId: string, options?: PresentationFlowTransitionToOptions) {
644
+ const currentInstance = instancesRef.current[flowId] ?? null
645
+ const targetFlowId = options?.flowId ?? currentInstance?.flowId ?? flowId
646
+ const flow = findPresentationFlow<FlowDefinition>(module, targetFlowId)
647
+ if (!flow) {
648
+ return false
649
+ }
650
+
651
+ return applyRegistryFlowState(flow, stateId, {
652
+ data: {
653
+ ...(currentInstance?.flowId === flow.Id ? currentInstance.data : {}),
654
+ ...(options?.data ?? {}),
655
+ },
656
+ previousInstance: currentInstance?.flowId === flow.Id ? currentInstance : null,
657
+ transition: null,
658
+ })
659
+ }
660
+
661
+ function applyRegistryFlowState(
662
+ flow: FlowDefinition,
663
+ stateId: string,
664
+ options: {
665
+ readonly data: Readonly<Record<string, unknown>>
666
+ readonly previousInstance: PresentationFlowInstance | null
667
+ readonly transition: FlowTransitionDefinition | null
668
+ },
669
+ ) {
670
+ const toState = flow.States.find((state) => state.Id === stateId)
671
+ if (!toState) {
672
+ return false
673
+ }
674
+
675
+ const nextInstance = {
676
+ data: options.data,
677
+ flowId: flow.Id,
678
+ stateId: toState.Id,
679
+ } satisfies PresentationFlowInstance
680
+ setFlowInstance(nextInstance, flow.Id)
681
+ onTransition?.({
682
+ flow,
683
+ fromState: options.previousInstance
684
+ ? flow.States.find((state) => state.Id === options.previousInstance?.stateId) ?? null
685
+ : null,
686
+ instance: nextInstance,
687
+ toState,
688
+ transition: options.transition,
689
+ })
690
+ return true
691
+ }
692
+
693
+ return {
694
+ activeFlow,
695
+ activeInstance: instance,
696
+ activeState,
697
+ activeView,
698
+ clearFlow: () => clearFlow(flowId),
699
+ dispatchAction,
700
+ dispatchEvent,
701
+ dispatchTransition,
702
+ isFlowActive: (candidateFlowId) =>
703
+ instancesRef.current[flowId]?.flowId === candidateFlowId,
704
+ isStateActive: (stateId) => instancesRef.current[flowId]?.stateId === stateId,
705
+ startFlow,
706
+ transitionTo,
707
+ }
708
+ }
709
+
710
+ return {
711
+ activeEntries,
712
+ clearFlow,
713
+ getRuntime,
714
+ }
715
+ }
716
+
717
+ export function resolvePresentationActionPreparedFlow(
718
+ action: ActionDefinition | null,
719
+ ): PresentationActionPreparedFlow | null {
720
+ const preparation = action?.Preparation
721
+ if (!preparation?.FlowId || !isFlowPreparationKind(preparation.Kind)) {
722
+ return null
723
+ }
724
+
725
+ return {
726
+ canCancel: preparation.CanCancel,
727
+ flowId: preparation.FlowId,
728
+ promptViewId: preparation.PromptViewId ?? null,
729
+ requiresExplicitCommit: preparation.RequiresExplicitCommit,
730
+ }
731
+ }
732
+
733
+ function resolvePresentationFlowRuntimeEntry(
734
+ module: PresentationModuleDefinition | null,
735
+ instance: PresentationFlowInstance,
736
+ ): PresentationFlowRuntimeEntry | null {
737
+ const flow = findPresentationFlow<FlowDefinition>(module, instance.flowId)
738
+ if (!flow) {
739
+ return null
740
+ }
741
+
742
+ const state = flow.States.find((candidate) => candidate.Id === instance.stateId)
743
+ if (!state) {
744
+ return null
745
+ }
746
+
747
+ return {
748
+ flow,
749
+ instance,
750
+ state,
751
+ view: state.ViewId
752
+ ? findPresentationView<ViewDefinition>(module, state.ViewId)
753
+ : null,
754
+ }
755
+ }
756
+
757
+ function resolveFlowTransition({
758
+ actionId,
759
+ data,
760
+ event,
761
+ flow,
762
+ fromStateId,
763
+ }: {
764
+ readonly actionId?: string | null
765
+ readonly data: Readonly<Record<string, unknown>>
766
+ readonly event?: string | null
767
+ readonly flow: FlowDefinition
768
+ readonly fromStateId: string
769
+ }) {
770
+ return findPresentationFlowTransition({
771
+ actionId,
772
+ data,
773
+ event,
774
+ flow,
775
+ fromStateId,
776
+ })
777
+ }
778
+
779
+ export function matchesPresentationFlowTransitionGuard(
780
+ transition: FlowTransitionDefinition,
781
+ data: Readonly<Record<string, unknown>>,
782
+ ) {
783
+ if (!transition.Guard) {
784
+ return true
785
+ }
786
+
787
+ const match = transition.Guard.match(
788
+ /^\s*([A-Za-z0-9_.]+)\s*==\s*(true|false|null|'[^']*'|"[^"]*"|-?\d+(?:\.\d+)?)\s*$/,
789
+ )
790
+ if (!match) {
791
+ return false
792
+ }
793
+
794
+ return readObjectPath(data, match[1]) === parseGuardLiteral(match[2])
795
+ }
796
+
797
+ function parseGuardLiteral(value: string) {
798
+ if (value === 'true') {
799
+ return true
800
+ }
801
+ if (value === 'false') {
802
+ return false
803
+ }
804
+ if (value === 'null') {
805
+ return null
806
+ }
807
+ if (
808
+ (value.startsWith("'") && value.endsWith("'")) ||
809
+ (value.startsWith('"') && value.endsWith('"'))
810
+ ) {
811
+ return value.slice(1, -1)
812
+ }
813
+
814
+ const numberValue = Number(value)
815
+ return Number.isFinite(numberValue) ? numberValue : value
816
+ }
817
+
818
+ function isFlowPreparationKind(value: unknown) {
819
+ return value === preparationKinds.prompt ||
820
+ value === preparationKinds.previewFlow ||
821
+ String(value).toLowerCase() === 'prompt' ||
822
+ String(value).toLowerCase() === 'previewflow'
823
+ }
824
+
825
+ function matchesFlowStateKind(value: unknown, kind: string | number) {
826
+ const label = typeof kind === 'number'
827
+ ? flowStateKindLabels[kind as keyof typeof flowStateKindLabels]
828
+ : kind
829
+ const normalizedValue = String(value).toLocaleLowerCase()
830
+ return (
831
+ value === kind ||
832
+ normalizedValue === String(kind).toLocaleLowerCase() ||
833
+ normalizedValue === label?.toLocaleLowerCase()
834
+ )
835
+ }