@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,90 +0,0 @@
1
- /**
2
- * Unified mutation hook with automatic success/error toast notifications.
3
- *
4
- * Rebuilt on the {@link HostBridge}: toasts route through `bridge.toast` and
5
- * error copy is decoded by the bridge's error decoder (falling back to the
6
- * kernel {@link getUserFacingErrorMessage}). Biome code stays decoupled from
7
- * any particular toast library or error-envelope shape.
8
- *
9
- * Requires `@tanstack/react-query` (an optional peer of the kernel) and a
10
- * `QueryClientProvider` mounted above.
11
- *
12
- * Usage:
13
- * const mutation = useMutationWithErrorToast(
14
- * { mutationFn: (vars) => apiFn(vars), onSuccess: () => invalidate() },
15
- * { successTitle: 'Saved', errorTitle: 'Save failed' },
16
- * );
17
- */
18
- import { useMutation, type UseMutationOptions } from '@tanstack/react-query';
19
-
20
- import { getUserFacingErrorMessage } from './errors';
21
- import { useHostBridge } from './host-bridge';
22
-
23
- export type MutationCoreOptions<
24
- TData = unknown,
25
- TError = Error,
26
- TVariables = unknown,
27
- TContext = unknown,
28
- > = Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'onError'>;
29
-
30
- export interface MutationToastOptions<TError = Error> {
31
- /** Title for the success toast. Omit or pass null to suppress the success toast. */
32
- successTitle?: string | null;
33
- /** Optional description for the success toast. */
34
- successDescription?: string;
35
- /** Title for the error toast. Omit to suppress the error toast. */
36
- errorTitle?: string;
37
- /**
38
- * Custom error side-effect. Called before the auto-error-toast.
39
- * Return `true` to suppress the automatic toast.
40
- */
41
- onError?: (error: TError) => boolean | void;
42
- }
43
-
44
- export function useMutationWithErrorToast<
45
- TData = unknown,
46
- TError extends Error = Error,
47
- TVariables = unknown,
48
- TContext = unknown,
49
- >(
50
- mutationOptions: MutationCoreOptions<TData, TError, TVariables, TContext>,
51
- toastOptions?: MutationToastOptions<TError>,
52
- ) {
53
- const bridge = useHostBridge();
54
- const decodeError =
55
- bridge.errors?.getUserFacingErrorMessage ?? getUserFacingErrorMessage;
56
-
57
- const { onSuccess: userOnSuccess, ...coreMutationOptions } = mutationOptions;
58
- const {
59
- successTitle,
60
- successDescription,
61
- errorTitle,
62
- onError: customOnError,
63
- } = toastOptions ?? {};
64
-
65
- return useMutation({
66
- ...coreMutationOptions,
67
- onSuccess: (data, variables, context, mutation) => {
68
- if (successTitle !== null && successTitle !== undefined) {
69
- bridge.toast.success(successTitle, successDescription);
70
- }
71
- if (userOnSuccess) {
72
- userOnSuccess(data, variables, context, mutation);
73
- }
74
- },
75
- onError: (error: TError) => {
76
- if (errorTitle !== undefined) {
77
- const shouldSuppressToast = customOnError?.(error) === true;
78
- if (!shouldSuppressToast) {
79
- const errorMessage = decodeError(
80
- error,
81
- 'Unable to complete this action.',
82
- );
83
- bridge.toast.error(errorTitle, errorMessage);
84
- }
85
- } else {
86
- customOnError?.(error);
87
- }
88
- },
89
- });
90
- }
@@ -1,248 +0,0 @@
1
- /**
2
- * usePageState — page-level UI state synced to the URL query string.
3
- *
4
- * State survives refresh, back/forward, and is shareable via URL. Rebuilt on
5
- * the {@link HostBridge} navigation port (`useSearchParams` to read,
6
- * `useLocation` for the current path, `push`/`replace` to write) so biome code
7
- * never imports a router primitive (`next/navigation`, `react-router-dom`)
8
- * directly — the host owns history semantics.
9
- *
10
- * Usage:
11
- * const [activeTab, setActiveTab] = usePageState<string>('tab', 'runs');
12
- * const [selectedId, setSelectedId] = usePageState<string | null>('run', null);
13
- */
14
-
15
- import { useCallback, useEffect, useRef, useState } from 'react';
16
-
17
- import { useHostBridge } from './host-bridge';
18
-
19
- /** Default debounce for the URL mirror of a free-text input, in ms. */
20
- const INPUT_URL_MIRROR_DEBOUNCE_MS = 300;
21
-
22
- function buildPath(pathname: string, params: URLSearchParams): string {
23
- const queryString = params.toString();
24
- return queryString ? `${pathname}?${queryString}` : pathname;
25
- }
26
-
27
- function serializeValue<T>(value: T): string {
28
- if (typeof value === 'string') {
29
- return value;
30
- }
31
- if (typeof value === 'number' || typeof value === 'boolean') {
32
- return String(value);
33
- }
34
- return JSON.stringify(value);
35
- }
36
-
37
- function tryParseJson(rawValue: string): unknown {
38
- try {
39
- return JSON.parse(rawValue);
40
- } catch {
41
- return undefined;
42
- }
43
- }
44
-
45
- function deserializePrimitive<T>(
46
- rawValue: string,
47
- defaultValue: T,
48
- ): T | undefined {
49
- if (typeof defaultValue === 'string') {
50
- return rawValue as T;
51
- }
52
-
53
- if (typeof defaultValue === 'number') {
54
- const numericValue = Number(rawValue);
55
- return (Number.isNaN(numericValue) ? defaultValue : numericValue) as T;
56
- }
57
-
58
- if (typeof defaultValue === 'boolean') {
59
- if (rawValue === 'true') {
60
- return true as T;
61
- }
62
- if (rawValue === 'false') {
63
- return false as T;
64
- }
65
- return defaultValue;
66
- }
67
-
68
- return undefined;
69
- }
70
-
71
- function deserializeValue<T>(rawValue: string | null, defaultValue: T): T {
72
- if (rawValue === null) {
73
- return defaultValue;
74
- }
75
-
76
- const primitive = deserializePrimitive(rawValue, defaultValue);
77
- if (primitive !== undefined) {
78
- return primitive;
79
- }
80
-
81
- // Default is null/undefined or a complex type (array/object). The value was
82
- // written via JSON.stringify in serializeValue, so attempt to parse it back.
83
- // If parsing yields a structured value (array/object/null), return it;
84
- // otherwise fall back to the raw string (covers `T = string | null`).
85
- const parsedValue = tryParseJson(rawValue);
86
- if (parsedValue !== undefined && typeof parsedValue === 'object') {
87
- return parsedValue as T;
88
- }
89
-
90
- if (defaultValue === null || defaultValue === undefined) {
91
- return rawValue as T;
92
- }
93
-
94
- if (parsedValue !== undefined) {
95
- return parsedValue as T;
96
- }
97
-
98
- if (Array.isArray(defaultValue)) {
99
- return rawValue ? ([rawValue] as unknown as T) : defaultValue;
100
- }
101
-
102
- return defaultValue;
103
- }
104
-
105
- export function usePageState<T>(
106
- stateKey: string,
107
- defaultValue: T,
108
- ): [T, (value: T) => void] {
109
- const { navigation } = useHostBridge();
110
- const searchParams = navigation.useSearchParams();
111
- const { pathname } = navigation.useLocation();
112
-
113
- const rawValue = searchParams.get(stateKey);
114
- const value = deserializeValue(rawValue, defaultValue);
115
-
116
- const setValue = useCallback(
117
- (newValue: T) => {
118
- const next = new URLSearchParams(searchParams);
119
- if (
120
- newValue === null ||
121
- newValue === undefined ||
122
- serializeValue(newValue) === serializeValue(defaultValue)
123
- ) {
124
- next.delete(stateKey);
125
- } else {
126
- next.set(stateKey, serializeValue(newValue));
127
- }
128
- navigation.push(buildPath(pathname, next));
129
- },
130
- [defaultValue, stateKey, searchParams, pathname, navigation],
131
- );
132
-
133
- return [value, setValue];
134
- }
135
-
136
- /**
137
- * usePageInputState — like {@link usePageState}, but tuned for FREE-TEXT
138
- * `<Input>` values that are mirrored to the URL on every keystroke.
139
- *
140
- * The returned value lives in LOCAL React state, so typing is instant. The URL
141
- * is mirrored — debounced — via the host's `replace` (an in-place history entry
142
- * update), so the value stays shareable / refresh-survivable WITHOUT pushing a
143
- * new history entry per keystroke.
144
- *
145
- * The local value re-syncs whenever the underlying search param changes from
146
- * the outside (a real navigation, a "Clear" button writing the param, or
147
- * back/forward), so external resets still flow through.
148
- *
149
- * Use this ONLY for free-text inputs. Tabs, selects, and row selection are real
150
- * navigations — keep them on {@link usePageState}.
151
- *
152
- * Usage:
153
- * const [filter, setFilter] = usePageInputState('runFilter', '');
154
- * <Input value={filter} onChange={(e) => setFilter(e.target.value)} />
155
- */
156
- export function usePageInputState(
157
- stateKey: string,
158
- defaultValue: string,
159
- debounceMs: number = INPUT_URL_MIRROR_DEBOUNCE_MS,
160
- ): [string, (value: string) => void] {
161
- const { navigation } = useHostBridge();
162
- const searchParams = navigation.useSearchParams();
163
- const { pathname } = navigation.useLocation();
164
-
165
- const urlValue = searchParams.get(stateKey) ?? defaultValue;
166
-
167
- const [localValue, setLocalValue] = useState(urlValue);
168
-
169
- // Resync local state when the URL param changes from the outside (real
170
- // navigation, Clear button, back/forward). We compare against the value we
171
- // last mirrored so our own debounced writes don't bounce back and clobber
172
- // freshly-typed characters.
173
- const lastMirroredRef = useRef(urlValue);
174
- useEffect(() => {
175
- if (urlValue !== lastMirroredRef.current) {
176
- lastMirroredRef.current = urlValue;
177
- setLocalValue(urlValue);
178
- }
179
- }, [urlValue]);
180
-
181
- const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
182
- useEffect(
183
- () => () => {
184
- if (debounceTimerRef.current !== null) {
185
- clearTimeout(debounceTimerRef.current);
186
- }
187
- },
188
- [],
189
- );
190
-
191
- const setValue = useCallback(
192
- (newValue: string) => {
193
- // Instant: drive the input from local state synchronously.
194
- setLocalValue(newValue);
195
-
196
- if (debounceTimerRef.current !== null) {
197
- clearTimeout(debounceTimerRef.current);
198
- }
199
- debounceTimerRef.current = setTimeout(() => {
200
- debounceTimerRef.current = null;
201
- lastMirroredRef.current =
202
- newValue === defaultValue ? defaultValue : newValue;
203
- const next = new URLSearchParams(searchParams);
204
- if (newValue === defaultValue || newValue.length === 0) {
205
- next.delete(stateKey);
206
- } else {
207
- next.set(stateKey, newValue);
208
- }
209
- navigation.replace(buildPath(pathname, next));
210
- }, debounceMs);
211
- },
212
- [debounceMs, defaultValue, stateKey, searchParams, pathname, navigation],
213
- );
214
-
215
- return [localValue, setValue];
216
- }
217
-
218
- /**
219
- * Batch-update multiple page-state keys in a single navigation.
220
- *
221
- * Pass `null` for a value to delete the key (reset to default).
222
- *
223
- * Usage:
224
- * const batch = useBatchPageState();
225
- * batch({ selectedId: 'abc', mainTab: null, contextTab: null });
226
- */
227
- export function useBatchPageState(): (
228
- updates: Record<string, string | null>,
229
- ) => void {
230
- const { navigation } = useHostBridge();
231
- const searchParams = navigation.useSearchParams();
232
- const { pathname } = navigation.useLocation();
233
-
234
- return useCallback(
235
- (updates: Record<string, string | null>) => {
236
- const next = new URLSearchParams(searchParams);
237
- for (const [key, value] of Object.entries(updates)) {
238
- if (value === null) {
239
- next.delete(key);
240
- } else {
241
- next.set(key, value);
242
- }
243
- }
244
- navigation.push(buildPath(pathname, next));
245
- },
246
- [searchParams, pathname, navigation],
247
- );
248
- }
@@ -1,25 +0,0 @@
1
- import { createContext, useContext } from 'react';
2
-
3
- import type { MeCapabilitiesSnapshot } from './types';
4
-
5
- export interface CapabilityContextValue {
6
- readonly snapshot: MeCapabilitiesSnapshot | undefined;
7
- readonly isLoading: boolean;
8
- readonly error: unknown;
9
- }
10
-
11
- export const CapabilityContext =
12
- createContext<CapabilityContextValue | null>(null);
13
- CapabilityContext.displayName = 'CapabilityContext';
14
-
15
- export function useCapabilityContext(): CapabilityContextValue {
16
- const ctx = useContext(CapabilityContext);
17
- if (!ctx) {
18
- throw new Error(
19
- '[xema-ui-kernel] useCapability()/useCapabilities() called outside ' +
20
- '<CapabilityProvider>. The host (or biome shell) must mount the ' +
21
- 'capability provider above any capability-gated subtree.',
22
- );
23
- }
24
- return ctx;
25
- }
@@ -1,73 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query';
2
-
3
- import {
4
- CapabilityContext,
5
- type CapabilityContextValue,
6
- } from './capability-context';
7
- import { useHostBridge } from '../biome-host/host-bridge';
8
-
9
- import type { CapabilityPort, MeCapabilitiesSnapshot } from './types';
10
- import type { ReactNode } from 'react';
11
-
12
- /**
13
- * Stable query-key root for the capability snapshot. Folded with the active
14
- * org so each org caches independently and a re-fetch fires when the org
15
- * changes — same tenant-isolation discipline as `useScopedQuery`.
16
- */
17
- const CAPABILITIES_QUERY_ROOT = ['xema', 'capabilities', 'me'] as const;
18
-
19
- export interface CapabilityProviderProps {
20
- readonly children: ReactNode;
21
- /**
22
- * Override the host-provided port. Defaults to `bridge.capabilities`. Exists
23
- * for tests and for hosts that compose the provider outside a bridge subtree;
24
- * production wiring reads it OFF the bridge so biomes need no extra prop.
25
- */
26
- readonly port?: CapabilityPort;
27
- /**
28
- * Override the active org. Defaults to `null` (the host's
29
- * currently-selected org). When provided, the port is asked for that org and
30
- * the cache key includes it.
31
- */
32
- readonly orgId?: string | null;
33
- }
34
-
35
- /**
36
- * Fetches the caller's server-authoritative capability set once per active org
37
- * and exposes it (plus loading/error) to descendants via React context.
38
- *
39
- * Wiring: the {@link CapabilityPort} is read OFF `HostBridge.capabilities`
40
- * (not prop-drilled) — the same decoupling the rest of the kernel uses to
41
- * reach the host. The active org is read from the bridge's auth source. The
42
- * bridge's `QueryClient` (TanStack Query) owns caching/refetch; the kernel
43
- * rolls no bespoke state. The query re-runs when the org changes because the
44
- * org is part of the query key.
45
- *
46
- * Rendering is fail-soft (callers can show a loading state), but failures are
47
- * SURFACED via `error` rather than masquerading as an empty capability set.
48
- */
49
- export function CapabilityProvider(
50
- props: CapabilityProviderProps,
51
- ): JSX.Element {
52
- const bridge = useHostBridge();
53
- const port = props.port ?? bridge.capabilities;
54
- const orgId =
55
- props.orgId !== undefined ? props.orgId : bridge.auth.getOrgId();
56
-
57
- const query = useQuery<MeCapabilitiesSnapshot>({
58
- queryKey: [...CAPABILITIES_QUERY_ROOT, orgId],
59
- queryFn: () => port.list(orgId ? { orgId } : undefined),
60
- });
61
-
62
- const value: CapabilityContextValue = {
63
- snapshot: query.data,
64
- isLoading: query.isLoading,
65
- error: query.error,
66
- };
67
-
68
- return (
69
- <CapabilityContext.Provider value={value}>
70
- {props.children}
71
- </CapabilityContext.Provider>
72
- );
73
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Host-agnostic capabilities SDK for `@xemahq/ui-kernel`.
3
- *
4
- * Lets a biome UI gate on SERVER-AUTHORITATIVE capabilities (the caller's
5
- * effective set from `GET /bff/me/capabilities`) instead of cosmetic role
6
- * booleans. The kernel defines the contract and the React surface; it never
7
- * fetches — the HOST implements {@link CapabilityPort} (hung off
8
- * `HostBridge.capabilities`) and maps its Orval client response onto the
9
- * local {@link MeCapabilitiesSnapshot} shape.
10
- *
11
- * Biome usage:
12
- * import { CapabilityProvider, useCapability } from '@xemahq/ui-kernel';
13
- */
14
- export * from './types';
15
- export * from './capability-context';
16
- export * from './capability-provider';
17
- export * from './use-capability';
@@ -1,69 +0,0 @@
1
- import type { ExecutionEnvironmentKind } from '@xemahq/kernel-contracts/execution-environment';
2
-
3
- /**
4
- * One server-authoritative capability the caller holds, mirroring the
5
- * FROZEN `GET /bff/me/capabilities` wire entry.
6
- *
7
- * These types are defined LOCALLY in the kernel — deliberately NOT imported
8
- * from a backend DTO or an Orval client — so `@xemahq/ui-kernel` stays
9
- * host-agnostic (no fetch lib, no generated client). The concrete host
10
- * (`submodules/xema-host-web`) maps its client response onto this shape when
11
- * it implements {@link CapabilityPort}.
12
- */
13
- export interface CapabilityEntry {
14
- /**
15
- * The capability reference (e.g. `capability:session.launch`). Matched
16
- * verbatim by {@link useCapability}; the kernel does not parse it.
17
- */
18
- readonly ref: string;
19
- /**
20
- * The execution environments in which the caller holds this capability.
21
- * A gate that names an `environment` is allowed only if it appears here.
22
- */
23
- readonly environments: ExecutionEnvironmentKind[];
24
- /**
25
- * When `true`, holding the capability still requires a human approval step
26
- * before the side-effect runs — so a UI affordance should route through the
27
- * approval flow rather than acting directly.
28
- */
29
- readonly approvalRequired: boolean;
30
- }
31
-
32
- /**
33
- * The caller's effective capability set for one active org, mirroring the
34
- * FROZEN `MeCapabilitiesResponse` wire shape.
35
- */
36
- export interface MeCapabilitiesSnapshot {
37
- /** The subject (user) the snapshot was resolved for. */
38
- readonly subjectId: string;
39
- /** The org the snapshot is scoped to. */
40
- readonly orgId: string;
41
- /** Every capability the subject effectively holds in this org. */
42
- readonly capabilities: CapabilityEntry[];
43
- }
44
-
45
- /**
46
- * Host-implemented port that fetches the caller's effective capability set.
47
- *
48
- * The kernel never calls an API directly — the HOST implements this against
49
- * its Orval client (`GET /bff/me/capabilities`) and maps the response onto
50
- * {@link MeCapabilitiesSnapshot}. The implementation is hung off
51
- * {@link HostBridge.capabilities}; {@link CapabilityProvider} reads it from
52
- * the bridge and uses the bridge's `QueryClient` for caching/refetch.
53
- */
54
- export interface CapabilityPort {
55
- /**
56
- * Return the caller's effective capability set for the active org.
57
- *
58
- * @param options.orgId The org to resolve for. When omitted the host
59
- * resolves against its currently-selected org.
60
- * @param options.environment Optional environment hint the host MAY pass to
61
- * the backend to narrow the set. Callers gate by environment client-side
62
- * via {@link useCapability}, so a host MAY ignore this and return the full
63
- * set; it exists so a host can request a pre-filtered snapshot.
64
- */
65
- list(options?: {
66
- orgId?: string;
67
- environment?: ExecutionEnvironmentKind;
68
- }): Promise<MeCapabilitiesSnapshot>;
69
- }
@@ -1,79 +0,0 @@
1
- import {
2
- useCapabilityContext,
3
- type CapabilityContextValue,
4
- } from './capability-context';
5
-
6
- import type { MeCapabilitiesSnapshot } from './types';
7
- import type { ExecutionEnvironmentKind } from '@xemahq/kernel-contracts/execution-environment';
8
-
9
- export interface UseCapabilityOptions {
10
- /**
11
- * Gate against a specific execution environment. When set, the capability
12
- * counts only if the snapshot lists it for this environment.
13
- */
14
- readonly environment?: ExecutionEnvironmentKind;
15
- }
16
-
17
- export interface CapabilityDecision {
18
- /**
19
- * `true` only when the caller holds the capability, holds it in the named
20
- * environment (if one was given), AND it does NOT require approval. A gate
21
- * that should let the user act directly checks this.
22
- */
23
- readonly allowed: boolean;
24
- /**
25
- * `true` when the caller holds the capability (in the named environment, if
26
- * given) but it requires a human approval step first — render the
27
- * approval-routed affordance instead of acting directly.
28
- */
29
- readonly approvalRequired: boolean;
30
- /** The snapshot is still being fetched; treat as "unknown", not "denied". */
31
- readonly isLoading: boolean;
32
- /** The fetch failed; surfaced so callers never conflate failure with denial. */
33
- readonly error: unknown;
34
- }
35
-
36
- /**
37
- * Server-authoritative capability gate.
38
- *
39
- * Resolves `ref` against the cached {@link CapabilityProvider} snapshot:
40
- * - `allowed` = entry exists AND (no environment asked OR listed for it) AND NOT approvalRequired.
41
- * - `approvalRequired` = entry exists AND (no environment asked OR listed for it) AND approvalRequired.
42
- *
43
- * While the snapshot is loading, `allowed` is `false` and `isLoading` is
44
- * `true` — callers gate on `isLoading` to distinguish "unknown" from "denied".
45
- * On error, `allowed` is `false` and `error` is set; the hook never silently
46
- * degrades a failed fetch into a confident denial.
47
- */
48
- export function useCapability(
49
- ref: string,
50
- opts?: UseCapabilityOptions,
51
- ): CapabilityDecision {
52
- const { snapshot, isLoading, error } = useCapabilityContext();
53
-
54
- const entry = snapshot?.capabilities.find((c) => c.ref === ref);
55
- const inEnvironment =
56
- !!entry &&
57
- (!opts?.environment || entry.environments.includes(opts.environment));
58
-
59
- return {
60
- allowed: inEnvironment && !entry.approvalRequired,
61
- approvalRequired: inEnvironment && entry.approvalRequired,
62
- isLoading,
63
- error,
64
- };
65
- }
66
-
67
- /**
68
- * Accessor for the raw capability snapshot plus its loading/error state.
69
- * Use when a component needs to enumerate capabilities rather than gate on a
70
- * single `ref`.
71
- */
72
- export function useCapabilities(): {
73
- snapshot: MeCapabilitiesSnapshot | undefined;
74
- isLoading: boolean;
75
- error: unknown;
76
- } {
77
- const ctx: CapabilityContextValue = useCapabilityContext();
78
- return { snapshot: ctx.snapshot, isLoading: ctx.isLoading, error: ctx.error };
79
- }