@xemahq/ui-kernel 0.11.0 → 0.11.2

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 (237) hide show
  1. package/dist/ui/chrome/page-shell.d.ts +1 -1
  2. package/dist/ui/chrome/page-shell.d.ts.map +1 -1
  3. package/dist/ui/chrome/page-shell.js +9 -4
  4. package/dist/ui/chrome/page-shell.js.map +1 -1
  5. package/package.json +6 -4
  6. package/src/index.ts +0 -17
  7. package/src/lib/biome-host/agent-validation.ts +0 -239
  8. package/src/lib/biome-host/biome-builders.ts +0 -112
  9. package/src/lib/biome-host/biome-mode.ts +0 -6
  10. package/src/lib/biome-host/biome-navigation.ts +0 -37
  11. package/src/lib/biome-host/biome-registry.ts +0 -299
  12. package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
  13. package/src/lib/biome-host/biome-scope.tsx +0 -52
  14. package/src/lib/biome-host/biome-scoped-query.ts +0 -134
  15. package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
  16. package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
  17. package/src/lib/biome-host/define-web-biome.ts +0 -222
  18. package/src/lib/biome-host/errors.ts +0 -220
  19. package/src/lib/biome-host/frontend-biome.ts +0 -256
  20. package/src/lib/biome-host/host-bridge.ts +0 -281
  21. package/src/lib/biome-host/host-sources.ts +0 -54
  22. package/src/lib/biome-host/i18n-hooks.ts +0 -51
  23. package/src/lib/biome-host/i18n-port.test.ts +0 -50
  24. package/src/lib/biome-host/i18n-port.ts +0 -96
  25. package/src/lib/biome-host/index.ts +0 -35
  26. package/src/lib/biome-host/realtime-hooks.ts +0 -75
  27. package/src/lib/biome-host/realtime-port.ts +0 -115
  28. package/src/lib/biome-host/session-contributions.ts +0 -296
  29. package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
  30. package/src/lib/biome-host/use-page-state.ts +0 -248
  31. package/src/lib/capabilities/capability-context.ts +0 -25
  32. package/src/lib/capabilities/capability-provider.tsx +0 -73
  33. package/src/lib/capabilities/index.ts +0 -17
  34. package/src/lib/capabilities/types.ts +0 -69
  35. package/src/lib/capabilities/use-capability.ts +0 -79
  36. package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
  37. package/src/lib/contribution-registry/contribution-registry.ts +0 -204
  38. package/src/lib/contribution-registry/index.ts +0 -9
  39. package/src/lib/contribution-registry/renderable-registry.ts +0 -142
  40. package/src/lib/system-bus/capability-invoke.ts +0 -92
  41. package/src/lib/system-bus/deeplink.ts +0 -210
  42. package/src/lib/system-bus/enums.ts +0 -86
  43. package/src/lib/system-bus/host-ports.ts +0 -93
  44. package/src/lib/system-bus/index.ts +0 -16
  45. package/src/lib/system-bus/intent-registry.ts +0 -109
  46. package/src/lib/system-bus/intents.ts +0 -113
  47. package/src/lib/system-bus/palette.ts +0 -78
  48. package/src/lib/system-bus/system-bus-builder.ts +0 -159
  49. package/src/lib/system-bus/system-bus.ts +0 -37
  50. package/src/lib/system-bus/windows.ts +0 -51
  51. package/src/offline/id.ts +0 -16
  52. package/src/offline/index.ts +0 -54
  53. package/src/offline/mutation.ts +0 -75
  54. package/src/offline/offline-status-badge.tsx +0 -51
  55. package/src/offline/online-controller.ts +0 -105
  56. package/src/offline/outbox.test.ts +0 -172
  57. package/src/offline/outbox.ts +0 -348
  58. package/src/offline/react-hooks.ts +0 -40
  59. package/src/offline/react.tsx +0 -32
  60. package/src/offline/storage.ts +0 -112
  61. package/src/offline/types.ts +0 -77
  62. package/src/org-db/components/DbResultTable.tsx +0 -176
  63. package/src/org-db/index.ts +0 -1
  64. package/src/pdf/index.ts +0 -11
  65. package/src/registry/index.ts +0 -9
  66. package/src/registry/lib/agent-validation-host.ts +0 -36
  67. package/src/registry/lib/biome-slot.tsx +0 -50
  68. package/src/registry/lib/biomes-enabled-context.ts +0 -20
  69. package/src/registry/lib/extension-points.ts +0 -134
  70. package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
  71. package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
  72. package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
  73. package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
  74. package/src/registry/lib/primitives/index.ts +0 -18
  75. package/src/registry/lib/session-context-builder.ts +0 -84
  76. package/src/registry/lib/session-context-provider.tsx +0 -33
  77. package/src/registry/lib/session-context.ts +0 -21
  78. package/src/registry/lib/session-selectors.ts +0 -272
  79. package/src/session/comments/CommentRail.tsx +0 -164
  80. package/src/session/index.ts +0 -3
  81. package/src/session/lib/cn.ts +0 -11
  82. package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
  83. package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
  84. package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
  85. package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
  86. package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
  87. package/src/session-kit/approvals/Obligation.tsx +0 -111
  88. package/src/session-kit/approvals/ScopedBody.tsx +0 -392
  89. package/src/session-kit/approvals/approval-icons.ts +0 -31
  90. package/src/session-kit/approvals/approval-model.ts +0 -204
  91. package/src/session-kit/approvals/index.ts +0 -22
  92. package/src/session-kit/approvals/obligation-display.ts +0 -100
  93. package/src/session-kit/approvals/risk-accent.ts +0 -47
  94. package/src/session-kit/approvals/scope-icons.ts +0 -19
  95. package/src/session-kit/combobox/Combobox.tsx +0 -358
  96. package/src/session-kit/combobox/use-click-outside.ts +0 -21
  97. package/src/session-kit/display/ContextHeader.tsx +0 -148
  98. package/src/session-kit/display/FileDiffCard.tsx +0 -140
  99. package/src/session-kit/display/MD.tsx +0 -162
  100. package/src/session-kit/display/MessageTurn.tsx +0 -157
  101. package/src/session-kit/display/ThinkingPanel.tsx +0 -81
  102. package/src/session-kit/display/TodoChecklist.tsx +0 -120
  103. package/src/session-kit/display/TokenMeter.tsx +0 -89
  104. package/src/session-kit/display/ToolStrip.tsx +0 -278
  105. package/src/session-kit/display/TypingDots.tsx +0 -24
  106. package/src/session-kit/index.ts +0 -44
  107. package/src/session-kit/lib/enums.ts +0 -66
  108. package/src/session-kit/lib/portal-accent.ts +0 -30
  109. package/src/session-kit/lib/status-dot.ts +0 -68
  110. package/src/session-kit/primitives/Avatar.tsx +0 -44
  111. package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
  112. package/src/session-kit/primitives/RiskPill.tsx +0 -95
  113. package/src/session-kit/primitives/ScopeDot.tsx +0 -47
  114. package/src/session-kit/primitives/Segmented.tsx +0 -71
  115. package/src/ui/branding/app-branding-context.ts +0 -20
  116. package/src/ui/branding/app-branding-provider.tsx +0 -123
  117. package/src/ui/branding/sanitize.ts +0 -89
  118. package/src/ui/breakpoints.ts +0 -14
  119. package/src/ui/capture/document-preview.tsx +0 -166
  120. package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
  121. package/src/ui/capture/field-spec-to-zod.ts +0 -174
  122. package/src/ui/capture/field-spec-validate.ts +0 -236
  123. package/src/ui/capture/field-spec.test.ts +0 -49
  124. package/src/ui/capture/field-spec.ts +0 -274
  125. package/src/ui/capture/index.ts +0 -51
  126. package/src/ui/capture/schema-field-input.tsx +0 -620
  127. package/src/ui/capture/schema-form.tsx +0 -149
  128. package/src/ui/capture/submit-values.test.ts +0 -70
  129. package/src/ui/capture/submit-values.ts +0 -63
  130. package/src/ui/capture/use-prefill.ts +0 -186
  131. package/src/ui/capture/use-schema-validation.ts +0 -145
  132. package/src/ui/capture/use-wizard.ts +0 -180
  133. package/src/ui/capture/value-format.ts +0 -32
  134. package/src/ui/capture/wizard.tsx +0 -105
  135. package/src/ui/charts/bar-chart.tsx +0 -139
  136. package/src/ui/charts/chart-frame.tsx +0 -125
  137. package/src/ui/charts/chart-types.ts +0 -52
  138. package/src/ui/charts/chart-utils.ts +0 -190
  139. package/src/ui/charts/charts.test.tsx +0 -204
  140. package/src/ui/charts/donut-chart.tsx +0 -129
  141. package/src/ui/charts/index.ts +0 -27
  142. package/src/ui/charts/line-chart.tsx +0 -202
  143. package/src/ui/charts/sparkline.tsx +0 -109
  144. package/src/ui/chrome/AsyncBoundary.tsx +0 -74
  145. package/src/ui/chrome/EmptyState.tsx +0 -257
  146. package/src/ui/chrome/ErrorCard.tsx +0 -63
  147. package/src/ui/chrome/LoadingState.tsx +0 -61
  148. package/src/ui/chrome/PageHeader.tsx +0 -137
  149. package/src/ui/chrome/RecordCardList.tsx +0 -98
  150. package/src/ui/chrome/StateCard.tsx +0 -150
  151. package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
  152. package/src/ui/chrome/page-shell.tsx +0 -189
  153. package/src/ui/chrome/scope-badge.tsx +0 -40
  154. package/src/ui/chrome/status-badge.tsx +0 -79
  155. package/src/ui/cn.ts +0 -32
  156. package/src/ui/design-tokens.ts +0 -372
  157. package/src/ui/hooks/use-debounced-value.ts +0 -16
  158. package/src/ui/hooks/use-is-mobile.ts +0 -28
  159. package/src/ui/index.ts +0 -110
  160. package/src/ui/pdf/pdf-preview.tsx +0 -122
  161. package/src/ui/primitives/alert-dialog.tsx +0 -104
  162. package/src/ui/primitives/async-combobox.tsx +0 -182
  163. package/src/ui/primitives/badge-variants.ts +0 -21
  164. package/src/ui/primitives/badge.tsx +0 -25
  165. package/src/ui/primitives/button-variants.ts +0 -31
  166. package/src/ui/primitives/button.tsx +0 -29
  167. package/src/ui/primitives/card.tsx +0 -43
  168. package/src/ui/primitives/checkbox.tsx +0 -26
  169. package/src/ui/primitives/collapsible.tsx +0 -9
  170. package/src/ui/primitives/dialog.tsx +0 -103
  171. package/src/ui/primitives/dropdown-menu.tsx +0 -179
  172. package/src/ui/primitives/form-stepper.tsx +0 -94
  173. package/src/ui/primitives/form-stepper.types.ts +0 -15
  174. package/src/ui/primitives/input.tsx +0 -22
  175. package/src/ui/primitives/label.tsx +0 -17
  176. package/src/ui/primitives/overflow-tabs.tsx +0 -331
  177. package/src/ui/primitives/popover.tsx +0 -33
  178. package/src/ui/primitives/preview-device-frame.tsx +0 -213
  179. package/src/ui/primitives/radio-group.tsx +0 -36
  180. package/src/ui/primitives/resizable.tsx +0 -67
  181. package/src/ui/primitives/scroll-area.tsx +0 -38
  182. package/src/ui/primitives/select.tsx +0 -143
  183. package/src/ui/primitives/separator.tsx +0 -20
  184. package/src/ui/primitives/sheet.tsx +0 -107
  185. package/src/ui/primitives/skeleton.tsx +0 -99
  186. package/src/ui/primitives/switch.tsx +0 -27
  187. package/src/ui/primitives/table.tsx +0 -72
  188. package/src/ui/primitives/tabs.tsx +0 -53
  189. package/src/ui/primitives/tag-multi-select.tsx +0 -261
  190. package/src/ui/primitives/textarea.tsx +0 -21
  191. package/src/ui/primitives/tooltip.tsx +0 -30
  192. package/src/ui/primitives/url-preview-chrome.tsx +0 -103
  193. package/src/vista/GenericCatalogueSurface.tsx +0 -179
  194. package/src/vista/SurfaceFallbackCard.tsx +0 -109
  195. package/src/vista/VistaPanel.tsx +0 -163
  196. package/src/vista/form-payload.spec.ts +0 -139
  197. package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
  198. package/src/vista/html/CustomUiFrame.tsx +0 -206
  199. package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
  200. package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
  201. package/src/vista/html/custom-ui-frame-contract.ts +0 -88
  202. package/src/vista/html/custom-ui-intents.spec.ts +0 -135
  203. package/src/vista/html/custom-ui-intents.ts +0 -155
  204. package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
  205. package/src/vista/html/custom-ui-srcdoc.ts +0 -193
  206. package/src/vista/html/index.ts +0 -13
  207. package/src/vista/index.ts +0 -52
  208. package/src/vista/surface-catalogue.spec.ts +0 -140
  209. package/src/vista/surface-catalogue.ts +0 -90
  210. package/src/vista/surface-icons.ts +0 -57
  211. package/src/vista/surface-registry.ts +0 -56
  212. package/src/vista/surfaces/ChartSurface.tsx +0 -34
  213. package/src/vista/surfaces/FilesSurface.tsx +0 -202
  214. package/src/vista/surfaces/FormSurface.tsx +0 -380
  215. package/src/vista/surfaces/HtmlSurface.tsx +0 -43
  216. package/src/vista/surfaces/TableSurface.tsx +0 -100
  217. package/src/vista/surfaces/UrlSurface.tsx +0 -40
  218. package/src/vista/surfaces/blob-surfaces.tsx +0 -139
  219. package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
  220. package/src/vista/surfaces/elicitation-transport.ts +0 -367
  221. package/src/vista/surfaces/file-surface-utils.ts +0 -69
  222. package/src/vista/surfaces/index.ts +0 -24
  223. package/src/vista/surfaces/register-surfaces.tsx +0 -139
  224. package/src/vista/surfaces/text-surfaces.tsx +0 -132
  225. package/src/vista/use-vista-tabs.ts +0 -196
  226. package/src/vista/vista-code-renderer-context.ts +0 -16
  227. package/src/vista/vista-code-renderer.tsx +0 -35
  228. package/src/vista/vista-file-context.ts +0 -14
  229. package/src/vista/vista-file-source.tsx +0 -141
  230. package/src/vista/vista-form-context.ts +0 -20
  231. package/src/vista/vista-form-host.tsx +0 -48
  232. package/src/vista/vista-model.ts +0 -137
  233. package/src/vista/vista-open-context.tsx +0 -27
  234. package/src/vista/vista-open-state.ts +0 -11
  235. package/src/vista/vista-payloads.ts +0 -635
  236. package/src/vista/vista-tabs-manifest.ts +0 -138
  237. package/src/vista/vista-tools.ts +0 -232
@@ -1,299 +0,0 @@
1
- import { Registry } from '@xemahq/registry';
2
- import { useSyncExternalStore } from 'react';
3
-
4
- import {
5
- DuplicateContributionPolicy,
6
- createContributionRegistry,
7
- } from '../contribution-registry';
8
-
9
- import type {
10
- FrontendBiome,
11
- FrontendBiomeFactory,
12
- OutputRendererContribution,
13
- PanelContribution,
14
- } from './frontend-biome';
15
- import type { HostBridge } from './host-bridge';
16
-
17
- /**
18
- * Singleton frontend biome registry. Populated at app bootstrap by
19
- * `registerFrontendBiome()` (factory form) or `registerFrontendBiomeInstance()`
20
- * (already-built form). Bootstrap order: fetch enabled biome ids from
21
- * `GET /platform/biomes/web`, dynamically `import()` each biome module
22
- * from the auto-generated loader map (`xema:web-biome-loaders`), then
23
- * call its default export with the host's concrete `HostBridge` and
24
- * register the result.
25
- *
26
- * Reactivity: components consume registry data via `useBiomeRegistryRevision()`
27
- * (or `<BiomeSlot>` / `useBiomeRoutes()` which use it internally) so a
28
- * re-render fires automatically when a biome registers or unregisters —
29
- * no race between Sidebar mount and async bootstrap, no manual subscription
30
- * wiring per consumer.
31
- *
32
- * Master-disable: hosts that embed `@xemahq/ui-kernel` without
33
- * participating in Xema's biome ecosystem flip
34
- * `biomeRegistry.setEnabled(false)` (typically via
35
- * `<BiomeHostProvider biomes="disabled">`). When disabled, every
36
- * register/unregister/bootstrap call is a no-op — a single flag controls
37
- * the entire surface.
38
- */
39
- class FrontendBiomeRegistry extends Registry<
40
- string,
41
- FrontendBiome & { key: string }
42
- > {
43
- private revisionCount = 0;
44
- private readonly listeners = new Set<() => void>();
45
- /**
46
- * Biome ids whose `init()` callback has been invoked. Tracked
47
- * separately from the registry entries so `init()` runs exactly once
48
- * per biome per SPA session even if `registerBiome` is called
49
- * multiple times (HMR, strict-mode double-mount, retries). Biome
50
- * authors get an at-most-once execution guarantee.
51
- */
52
- private readonly initialized = new Set<string>();
53
- private enabled = true;
54
-
55
- constructor() {
56
- super({ name: 'FrontendBiomeRegistry' });
57
- }
58
-
59
- registerBiome(biome: FrontendBiome): void {
60
- if (!this.enabled) {
61
- return;
62
- }
63
- this.register({ ...biome, key: biome.id });
64
- this.revisionCount += 1;
65
- this.notify();
66
- }
67
-
68
- /**
69
- * Invoke a biome's `init()` callback exactly once per SPA session.
70
- * The caller (bootstrap) is responsible for catching/logging promise
71
- * rejections — the registry intentionally does not swallow them.
72
- */
73
- async runInit(biomeId: string, bridge: HostBridge): Promise<void> {
74
- const entry = this.get(biomeId);
75
- if (!entry || !entry.init) {
76
- return;
77
- }
78
- if (this.initialized.has(biomeId)) {
79
- return;
80
- }
81
- this.initialized.add(biomeId);
82
- await entry.init({ bridge, biomeId });
83
- }
84
-
85
- unregisterBiome(id: string): boolean {
86
- if (!this.enabled) {
87
- return false;
88
- }
89
- const entry = this.get(id);
90
- const removed = this.unregister(id);
91
- if (removed) {
92
- this.revisionCount += 1;
93
- this.initialized.delete(id);
94
- if (entry?.dispose) {
95
- // Fire-and-forget; teardown errors are logged by callers but
96
- // never block the unregister path — disposal is best-effort
97
- // because the biome is being torn down anyway.
98
- void Promise.resolve(entry.dispose()).catch((error: unknown) => {
99
- // Surface for devtools without crashing the unregister flow.
100
- console.error(
101
- `[xema-ui-kernel] dispose() failed for '${id}':`,
102
- error,
103
- );
104
- });
105
- }
106
- this.notify();
107
- }
108
- return removed;
109
- }
110
-
111
- /**
112
- * Snapshot of currently-registered biome ids. Useful for the admin UI
113
- * to detect "registered in this session" vs "enabled in catalog".
114
- */
115
- snapshot(): readonly string[] {
116
- return this.list().map((b) => b.id);
117
- }
118
-
119
- setEnabled(enabled: boolean): void {
120
- if (this.enabled === enabled) {
121
- return;
122
- }
123
- this.enabled = enabled;
124
- if (!enabled) {
125
- // When master-disabling, drop any previously-registered biomes so
126
- // BiomeSlot/useBiomeRoutes consumers re-render to empty.
127
- const had = this.size() > 0;
128
- this.clear();
129
- this.initialized.clear();
130
- if (had) {
131
- this.revisionCount += 1;
132
- this.notify();
133
- }
134
- }
135
- }
136
-
137
- isEnabled(): boolean {
138
- return this.enabled;
139
- }
140
-
141
- revision(): number {
142
- return this.revisionCount;
143
- }
144
-
145
- /**
146
- * Subscribe to revision-counter changes. Returns an unsubscribe
147
- * function. Used internally by `useBiomeRegistryRevision()` (via
148
- * `useSyncExternalStore`) so components automatically re-render when
149
- * a biome is added or removed.
150
- */
151
- subscribe(listener: () => void): () => void {
152
- this.listeners.add(listener);
153
- return () => {
154
- this.listeners.delete(listener);
155
- };
156
- }
157
-
158
- panelsForSlot(slot: string): readonly PanelContribution[] {
159
- if (!this.enabled) {
160
- return [];
161
- }
162
- const out: PanelContribution[] = [];
163
- for (const biome of this.list()) {
164
- for (const panel of biome.panels ?? []) {
165
- if (panel.slot === slot) {
166
- out.push(panel);
167
- }
168
- }
169
- }
170
- return out.sort((a, b) => (a.weight ?? 100) - (b.weight ?? 100));
171
- }
172
-
173
- /**
174
- * Flatten every registered biome's `outputRenderers[]` contributions
175
- * into a `(outputKind → factory)` map. The host's renderer registry
176
- * overlays this on top of the built-in set so biome-defined kinds
177
- * light up the moment the biome's frontend loads. Duplicate kinds
178
- * across biomes are surfaced as a console error and the first
179
- * registration wins (deterministic + fail-loud, not silent).
180
- *
181
- * Built on the shared `createContributionRegistry` core — the same
182
- * closed+open two-layer bookkeeping the ChatWidget, reply-variant and
183
- * Vista-surface registries use. This overlay is rebuilt on every call
184
- * (it is read during render), so it takes the `FirstWins` duplicate
185
- * policy rather than `Throw`: a duplicate must not take down a render.
186
- * `FirstWins` still REQUIRES the `onDuplicate` reporter below — the
187
- * rejected contribution is never dropped silently.
188
- */
189
- outputRenderers(): ReadonlyMap<string, OutputRendererContribution> {
190
- if (!this.enabled) {
191
- return new Map<string, OutputRendererContribution>();
192
- }
193
- // `onDuplicate` fires SYNCHRONOUSLY from inside `register()`, so this
194
- // cursor always names the biome whose contribution was rejected — the
195
- // contribution object itself does not carry its owner.
196
- let contributingBiomeId = '';
197
- const overlay = createContributionRegistry<OutputRendererContribution>({
198
- name: 'biome outputRenderer',
199
- duplicatePolicy: DuplicateContributionPolicy.FirstWins,
200
- onDuplicate: (outputKind) => {
201
- console.error(
202
- `[xema-ui-kernel] duplicate outputRenderer for kind '${outputKind}' ` +
203
- `from biome '${contributingBiomeId}'; first registration wins.`,
204
- );
205
- },
206
- });
207
- for (const biome of this.list()) {
208
- contributingBiomeId = biome.id;
209
- for (const entry of biome.outputRenderers ?? []) {
210
- overlay.register(entry.outputKind, entry);
211
- }
212
- }
213
- return overlay.entries();
214
- }
215
-
216
- private notify(): void {
217
- for (const listener of this.listeners) {
218
- listener();
219
- }
220
- }
221
- }
222
-
223
- export const biomeRegistry = new FrontendBiomeRegistry();
224
-
225
- function isFrontendBiome(value: unknown): value is FrontendBiome {
226
- return (
227
- value !== null &&
228
- typeof value === 'object' &&
229
- 'id' in value &&
230
- typeof value.id === 'string' &&
231
- 'displayName' in value &&
232
- typeof value.displayName === 'string'
233
- );
234
- }
235
-
236
- /**
237
- * Register an already-built `FrontendBiome` instance. Prefer
238
- * `registerFrontendBiome(factory, bridge)` for biomes shipped via
239
- * the factory contract.
240
- */
241
- export function registerFrontendBiomeInstance(biome: FrontendBiome): void {
242
- biomeRegistry.registerBiome(biome);
243
- }
244
-
245
- /**
246
- * Invoke a `FrontendBiomeFactory` with the host's `HostBridge` and
247
- * register the resulting `FrontendBiome`. This is the standard
248
- * registration path for biomes shipped as workspace packages.
249
- *
250
- * Fail-fast: a factory that returns a non-`FrontendBiome` value, or
251
- * that produces a duplicate id, propagates the underlying registry
252
- * error — no swallowing.
253
- */
254
- export function registerFrontendBiome(
255
- factory: FrontendBiomeFactory,
256
- bridge: HostBridge,
257
- ): void {
258
- if (!biomeRegistry.isEnabled()) {
259
- return;
260
- }
261
- const biome: unknown = factory(bridge);
262
- if (!isFrontendBiome(biome)) {
263
- throw new Error(
264
- '[xema-ui-kernel] FrontendBiomeFactory returned a non-FrontendBiome value. ' +
265
- 'A biome module must default-export `(bridge) => FrontendBiome`.',
266
- );
267
- }
268
- biomeRegistry.registerBiome(biome);
269
- }
270
-
271
- /**
272
- * Unregister a biome by id. Triggered by the admin UI's runtime
273
- * disable flow so BiomeSlot / useBiomeRoutes consumers reflow without
274
- * a page reload. Returns `true` if a registration was removed.
275
- */
276
- export function unregisterFrontendBiome(id: string): boolean {
277
- return biomeRegistry.unregisterBiome(id);
278
- }
279
-
280
- /**
281
- * React hook that subscribes the calling component to the biome
282
- * registry's revision counter. Returns the current revision (a number
283
- * that increments by 1 on every biome registration or unregistration).
284
- * Use the return value as a `useMemo` dep when computing data derived
285
- * from the registry — the memo recomputes on every change.
286
- *
287
- * Implemented with `useSyncExternalStore` so it integrates cleanly with
288
- * React 18 concurrent rendering and SSR.
289
- *
290
- * `<BiomeSlot>` uses this hook internally so any host page that mounts
291
- * a slot re-renders automatically when biomes arrive or leave.
292
- */
293
- export function useBiomeRegistryRevision(): number {
294
- return useSyncExternalStore(
295
- (listener) => biomeRegistry.subscribe(listener),
296
- () => biomeRegistry.revision(),
297
- () => biomeRegistry.revision(),
298
- );
299
- }
@@ -1,41 +0,0 @@
1
- import { useHostBridge } from './host-bridge';
2
-
3
- export interface ProjectScope {
4
- readonly orgId: string | null;
5
- readonly projectId: string | null;
6
- }
7
-
8
- export interface RequiredProjectScope {
9
- readonly orgId: string;
10
- readonly projectId: string;
11
- }
12
-
13
- function normalize(value: string | null | undefined): string | null {
14
- if (typeof value !== 'string') {
15
- return null;
16
- }
17
- const trimmed = value.trim();
18
- return trimmed.length > 0 ? trimmed : null;
19
- }
20
-
21
- export function useProjectScope(): ProjectScope {
22
- const bridge = useHostBridge();
23
- const params = bridge.navigation.useRouteParams<{ projectId?: string }>();
24
- const orgId = normalize(bridge.auth.getOrgId());
25
- const projectId =
26
- normalize(params.projectId) ?? normalize(bridge.auth.getProjectId());
27
- return { orgId, projectId };
28
- }
29
-
30
- export function useRequiredScope(): RequiredProjectScope {
31
- const { orgId, projectId } = useProjectScope();
32
- if (!orgId || !projectId) {
33
- throw new Error(
34
- `[xema-ui-kernel] useRequiredScope() requires both orgId and projectId, ` +
35
- `got orgId=${orgId ?? 'null'} projectId=${projectId ?? 'null'}. ` +
36
- `Render this component under <ScopeGuard> or use useProjectScope() for ` +
37
- `org-scoped surfaces.`,
38
- );
39
- }
40
- return { orgId, projectId };
41
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Bridge-backed org/project scope hooks for frontend biomes.
3
- *
4
- * Biomes need the active `{ orgId, projectId }` everywhere they call a scoped
5
- * API or build a scoped query key. Today each biome rolls its own read — some
6
- * pull `bridge.auth.getOrgId()`, some read `projectId` off route params, some
7
- * mix both — which drifts and quietly produces null/undefined holes. This
8
- * module collapses those into ONE normalized read and ONE guard component.
9
- *
10
- * Host-framework-agnostic: it only reads the host-implemented bridge surface
11
- * (`auth.getOrgId` / `auth.getProjectId` and `navigation.useRouteParams`). No
12
- * Next.js, no React-Router.
13
- */
14
- import { Fragment, type ReactElement, type ReactNode } from 'react';
15
-
16
- import { useProjectScope, type ProjectScope } from './biome-scope-hooks';
17
-
18
- export interface ScopeGuardProps {
19
- /**
20
- * What to render while the scope is incomplete. Defaults to `null` (render
21
- * nothing) — pass a skeleton or an empty-state to be explicit.
22
- */
23
- readonly fallback?: ReactNode;
24
- /**
25
- * When `true` (the default) both `orgId` AND `projectId` are required. Set
26
- * `false` to gate only on `orgId` — for org-scoped surfaces that still want
27
- * to wait for an active org before rendering.
28
- */
29
- readonly requireProject?: boolean;
30
- /**
31
- * Children to render once the scope is complete. May be a render-prop that
32
- * receives the resolved {@link ProjectScope}, or plain nodes.
33
- */
34
- readonly children: ReactNode | ((scope: ProjectScope) => ReactNode);
35
- }
36
-
37
- /**
38
- * Gate a subtree on a complete active scope. Renders `fallback` until the
39
- * required scope fields are present, then renders `children` (passing the
40
- * resolved scope when `children` is a render-prop). Collapses the repeated
41
- * `if (!orgId || !projectId) return null` guard biome pages hand-write.
42
- */
43
- export function ScopeGuard(props: ScopeGuardProps): ReactElement {
44
- const { fallback = null, requireProject = true, children } = props;
45
- const scope = useProjectScope();
46
- const ready = Boolean(scope.orgId) && (!requireProject || Boolean(scope.projectId));
47
- if (!ready) {
48
- return <Fragment>{fallback}</Fragment>;
49
- }
50
- const resolved = typeof children === 'function' ? children(scope) : children;
51
- return <Fragment>{resolved}</Fragment>;
52
- }
@@ -1,134 +0,0 @@
1
- /**
2
- * Bridge-backed scoped-fetch helpers for frontend biomes.
3
- *
4
- * Every scoped biome data hook hand-repeats the same three things: build the
5
- * org/project request headers (`reqOpts`), gate the query on the scope being
6
- * present (`enabled`), and fold org/project into the query key so cache entries
7
- * don't bleed across tenants/projects. This module folds those into
8
- * `useScopedClient()` + `useScopedQuery()` so a biome hook becomes one call.
9
- *
10
- * Host-framework-agnostic. TanStack Query is an OPTIONAL peer dependency — it
11
- * is imported only here, behind the `useScopedQuery` surface, so biomes that
12
- * don't use it pay nothing and the kernel never hard-requires it.
13
- */
14
- import {
15
- useQuery,
16
- type QueryKey,
17
- type UseQueryOptions,
18
- type UseQueryResult,
19
- } from '@tanstack/react-query';
20
-
21
- import { useProjectScope, type ProjectScope } from './biome-scope-hooks';
22
-
23
- /**
24
- * The canonical Xema-platform tenant header. Every platform biome-api reads it
25
- * via `RequestContext`. Mirrors the host's `reqOpts` (see
26
- * `submodules/xema-host-web/src/lib/api/orval/configure-clients.ts`).
27
- */
28
- const ORG_HEADER = 'X-Xema-Org-Id';
29
- const PROJECT_HEADER = 'X-Project-Id';
30
-
31
- /**
32
- * Build a `RequestInit` carrying the active org/project headers, to pass as the
33
- * per-call `options` argument of any Orval endpoint function. Single source of
34
- * the header names so biomes stop hand-rolling `reqOpts` per package.
35
- */
36
- export function buildScopedRequestInit(scope: ProjectScope): RequestInit {
37
- const headers: Record<string, string> = {};
38
- if (scope.orgId) {
39
- headers[ORG_HEADER] = scope.orgId;
40
- }
41
- if (scope.projectId) {
42
- headers[PROJECT_HEADER] = scope.projectId;
43
- }
44
- return { headers };
45
- }
46
-
47
- export interface ScopedClient {
48
- /** The resolved active scope (null-normalized). */
49
- readonly scope: ProjectScope;
50
- /**
51
- * Per-call Orval options bound to the active scope. Pass straight into an
52
- * endpoint function: `await fooControllerList(client.reqOpts())`.
53
- */
54
- reqOpts(): RequestInit;
55
- }
56
-
57
- /**
58
- * A scope-bound request-options factory. Reads the active `{ orgId, projectId }`
59
- * from the bridge and exposes a `reqOpts()` that stamps the canonical tenant
60
- * headers — the per-biome `reqOpts(orgId, projectId)` helper, centralized.
61
- */
62
- export function useScopedClient(): ScopedClient {
63
- const scope = useProjectScope();
64
- return {
65
- scope,
66
- reqOpts: () => buildScopedRequestInit(scope),
67
- };
68
- }
69
-
70
- /**
71
- * Extra options accepted by {@link useScopedQuery} on top of the underlying
72
- * TanStack `useQuery` options.
73
- */
74
- export interface UseScopedQueryExtras {
75
- /**
76
- * When `true` (the default) the query is gated on `orgId` AND `projectId`.
77
- * Set `false` to gate only on `orgId` — for org-scoped reads that don't need
78
- * a project. The gate ANDs with any caller-supplied `enabled`.
79
- */
80
- readonly requireProject?: boolean;
81
- }
82
-
83
- /**
84
- * The `queryFn` shape `useScopedQuery` calls — it receives the resolved scope
85
- * plus the scope-bound `reqOpts()` so the body never re-reads the bridge.
86
- */
87
- export type ScopedQueryFn<TData> = (ctx: {
88
- readonly scope: ProjectScope;
89
- reqOpts(): RequestInit;
90
- }) => TData | Promise<TData>;
91
-
92
- /**
93
- * Scope-aware `useQuery`. Folds the active org/project into:
94
- * - the **query key** — `key` is prefixed with `['scope', orgId, projectId]`
95
- * so cache entries never leak across tenants/projects;
96
- * - the **`enabled` gate** — ANDed with the scope-present check (and any
97
- * caller `enabled`), so the query stays idle until the scope resolves;
98
- * - the **`queryFn` context** — the resolved scope + a bound `reqOpts()`.
99
- *
100
- * Everything else (`staleTime`, `select`, `retry`, …) passes through to the
101
- * underlying `useQuery`.
102
- */
103
- export function useScopedQuery<
104
- TQueryFnData,
105
- TError = Error,
106
- TData = TQueryFnData,
107
- >(
108
- key: QueryKey,
109
- queryFn: ScopedQueryFn<TQueryFnData>,
110
- options: Omit<
111
- UseQueryOptions<TQueryFnData, TError, TData>,
112
- 'queryKey' | 'queryFn'
113
- > &
114
- UseScopedQueryExtras = {},
115
- ): UseQueryResult<TData, TError> {
116
- const scope = useProjectScope();
117
- const { requireProject = true, enabled, ...rest } = options;
118
-
119
- const scopeReady =
120
- Boolean(scope.orgId) && (!requireProject || Boolean(scope.projectId));
121
-
122
- return useQuery<TQueryFnData, TError, TData>({
123
- ...rest,
124
- queryKey: ['scope', scope.orgId, scope.projectId, ...key],
125
- enabled: scopeReady && (enabled ?? true),
126
- queryFn: () =>
127
- Promise.resolve(
128
- queryFn({
129
- scope,
130
- reqOpts: () => buildScopedRequestInit(scope),
131
- }),
132
- ),
133
- });
134
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * Shared Orval-client wiring for frontend biomes.
3
- *
4
- * Every biome that calls a platform API through an Orval-generated client must
5
- * configure that client with the SAME auth/header/401 behavior the host shell
6
- * uses for its own clients. Hand-copying a `configureOrgDbClient`-style helper
7
- * into each biome (as `org-databases` did across three hook files) duplicates
8
- * the correlation-id + org-header + token logic and lets it drift from the
9
- * host. This factory centralizes it: a biome builds one config from its
10
- * `HostBridge` and feeds it straight into the client's `configureClient(...)`.
11
- *
12
- * Auth/refresh/redirect stay HOST-owned — this only READS the bridge's
13
- * host-implemented methods (`getActorToken`, `ensureFreshToken`,
14
- * `onUnauthorized`, `getOrgId`). It owns no auth state of its own.
15
- */
16
- import type { HostBridge } from './host-bridge';
17
-
18
- /**
19
- * The Orval client config shape every generated `configureClient(...)` accepts.
20
- * Kept structural (not imported from a client package) so the kernel stays free
21
- * of any concrete service-client dependency — biomes pass the result straight
22
- * into their own client's `configureClient`, which validates the shape.
23
- */
24
- export interface BiomeOrvalConfig {
25
- readonly baseUrl: string;
26
- readonly maxRetries: number;
27
- getAuthToken(): Promise<string>;
28
- getHeaders(): Promise<Record<string, string>>;
29
- onUnauthorized(): Promise<void>;
30
- }
31
-
32
- export interface CreateBiomeOrvalConfigOptions {
33
- /** Absolute base URL of the target service. */
34
- readonly baseUrl: string;
35
- /**
36
- * Retry budget for the underlying fetch wrapper. Defaults to 1 — a single
37
- * retry covers the `onUnauthorized` refresh-then-retry path without turning
38
- * a hard failure into a retry storm.
39
- */
40
- readonly maxRetries?: number;
41
- }
42
-
43
- /**
44
- * Build an Orval client config from a `HostBridge`. Wires:
45
- * - `getAuthToken` → `bridge.auth.ensureFreshToken()` (proactive refresh;
46
- * the host owns the refresh + login-redirect single-flight).
47
- * - `getHeaders` → a fresh `X-Correlation-Id` per call, plus `X-Xema-Org-Id`
48
- * (the canonical Xema-platform tenant header every platform service reads
49
- * via `RequestContext`) when an org is selected. Biome web clients only ever
50
- * call Xema-platform biome-apis, so this is the single correct tenant
51
- * header; the previously-sent legacy `X-Org-Id` was dead (the central
52
- * read-side was decommissioned) and left context-derived reads without an
53
- * org. Per-call headers set by the caller still win in the client's own
54
- * header merge.
55
- * - `onUnauthorized` → `bridge.auth.onUnauthorized()` (host force-refresh +
56
- * redirect-on-failure).
57
- *
58
- * @example
59
- * configureClient(createBiomeOrvalConfig(bridge, { baseUrl: API_BASE_URL }));
60
- */
61
- export function createBiomeOrvalConfig(
62
- bridge: HostBridge,
63
- opts: CreateBiomeOrvalConfigOptions,
64
- ): BiomeOrvalConfig {
65
- return {
66
- baseUrl: opts.baseUrl,
67
- maxRetries: opts.maxRetries ?? 1,
68
- getAuthToken: () => bridge.auth.ensureFreshToken(),
69
- getHeaders: () => {
70
- const headers: Record<string, string> = {
71
- 'X-Correlation-Id': crypto.randomUUID(),
72
- };
73
- const orgId = bridge.auth.getOrgId();
74
- if (orgId) {
75
- // Canonical header read by every Xema-platform service's RequestContext.
76
- headers['X-Xema-Org-Id'] = orgId;
77
- }
78
- return Promise.resolve(headers);
79
- },
80
- onUnauthorized: () => bridge.auth.onUnauthorized(),
81
- };
82
- }