@xemahq/ui-kernel 0.11.1 → 0.12.0

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 (257) hide show
  1. package/dist/errors/http-error-status.d.ts +17 -0
  2. package/dist/errors/http-error-status.d.ts.map +1 -0
  3. package/dist/errors/http-error-status.js +59 -0
  4. package/dist/errors/http-error-status.js.map +1 -0
  5. package/dist/errors/index.d.ts +2 -0
  6. package/dist/errors/index.d.ts.map +1 -0
  7. package/dist/errors/index.js +2 -0
  8. package/dist/errors/index.js.map +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/biome-host/errors.d.ts.map +1 -1
  14. package/dist/lib/biome-host/errors.js +1 -19
  15. package/dist/lib/biome-host/errors.js.map +1 -1
  16. package/dist/offline/index.d.ts.map +1 -1
  17. package/dist/offline/index.js.map +1 -1
  18. package/dist/offline/online-controller.d.ts.map +1 -1
  19. package/dist/offline/online-controller.js +6 -1
  20. package/dist/offline/online-controller.js.map +1 -1
  21. package/dist/offline/outbox.d.ts.map +1 -1
  22. package/dist/offline/outbox.js +2 -24
  23. package/dist/offline/outbox.js.map +1 -1
  24. package/package.json +1 -2
  25. package/src/index.ts +0 -17
  26. package/src/lib/biome-host/agent-validation.ts +0 -239
  27. package/src/lib/biome-host/biome-builders.ts +0 -112
  28. package/src/lib/biome-host/biome-mode.ts +0 -6
  29. package/src/lib/biome-host/biome-navigation.ts +0 -37
  30. package/src/lib/biome-host/biome-registry.ts +0 -299
  31. package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
  32. package/src/lib/biome-host/biome-scope.tsx +0 -52
  33. package/src/lib/biome-host/biome-scoped-query.ts +0 -134
  34. package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
  35. package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
  36. package/src/lib/biome-host/define-web-biome.ts +0 -222
  37. package/src/lib/biome-host/errors.ts +0 -220
  38. package/src/lib/biome-host/frontend-biome.ts +0 -256
  39. package/src/lib/biome-host/host-bridge.ts +0 -281
  40. package/src/lib/biome-host/host-sources.ts +0 -54
  41. package/src/lib/biome-host/i18n-hooks.ts +0 -51
  42. package/src/lib/biome-host/i18n-port.test.ts +0 -50
  43. package/src/lib/biome-host/i18n-port.ts +0 -96
  44. package/src/lib/biome-host/index.ts +0 -35
  45. package/src/lib/biome-host/realtime-hooks.ts +0 -75
  46. package/src/lib/biome-host/realtime-port.ts +0 -115
  47. package/src/lib/biome-host/session-contributions.ts +0 -296
  48. package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
  49. package/src/lib/biome-host/use-page-state.ts +0 -248
  50. package/src/lib/capabilities/capability-context.ts +0 -25
  51. package/src/lib/capabilities/capability-provider.tsx +0 -73
  52. package/src/lib/capabilities/index.ts +0 -17
  53. package/src/lib/capabilities/types.ts +0 -69
  54. package/src/lib/capabilities/use-capability.ts +0 -79
  55. package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
  56. package/src/lib/contribution-registry/contribution-registry.ts +0 -204
  57. package/src/lib/contribution-registry/index.ts +0 -9
  58. package/src/lib/contribution-registry/renderable-registry.ts +0 -142
  59. package/src/lib/system-bus/capability-invoke.ts +0 -92
  60. package/src/lib/system-bus/deeplink.ts +0 -210
  61. package/src/lib/system-bus/enums.ts +0 -86
  62. package/src/lib/system-bus/host-ports.ts +0 -93
  63. package/src/lib/system-bus/index.ts +0 -16
  64. package/src/lib/system-bus/intent-registry.ts +0 -109
  65. package/src/lib/system-bus/intents.ts +0 -113
  66. package/src/lib/system-bus/palette.ts +0 -78
  67. package/src/lib/system-bus/system-bus-builder.ts +0 -159
  68. package/src/lib/system-bus/system-bus.ts +0 -37
  69. package/src/lib/system-bus/windows.ts +0 -51
  70. package/src/offline/id.ts +0 -16
  71. package/src/offline/index.ts +0 -54
  72. package/src/offline/mutation.ts +0 -75
  73. package/src/offline/offline-status-badge.tsx +0 -51
  74. package/src/offline/online-controller.ts +0 -105
  75. package/src/offline/outbox.test.ts +0 -172
  76. package/src/offline/outbox.ts +0 -348
  77. package/src/offline/react-hooks.ts +0 -40
  78. package/src/offline/react.tsx +0 -32
  79. package/src/offline/storage.ts +0 -112
  80. package/src/offline/types.ts +0 -77
  81. package/src/org-db/components/DbResultTable.tsx +0 -176
  82. package/src/org-db/index.ts +0 -1
  83. package/src/pdf/index.ts +0 -11
  84. package/src/registry/index.ts +0 -9
  85. package/src/registry/lib/agent-validation-host.ts +0 -36
  86. package/src/registry/lib/biome-slot.tsx +0 -50
  87. package/src/registry/lib/biomes-enabled-context.ts +0 -20
  88. package/src/registry/lib/extension-points.ts +0 -134
  89. package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
  90. package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
  91. package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
  92. package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
  93. package/src/registry/lib/primitives/index.ts +0 -18
  94. package/src/registry/lib/session-context-builder.ts +0 -84
  95. package/src/registry/lib/session-context-provider.tsx +0 -33
  96. package/src/registry/lib/session-context.ts +0 -21
  97. package/src/registry/lib/session-selectors.ts +0 -272
  98. package/src/session/comments/CommentRail.tsx +0 -164
  99. package/src/session/index.ts +0 -3
  100. package/src/session/lib/cn.ts +0 -11
  101. package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
  102. package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
  103. package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
  104. package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
  105. package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
  106. package/src/session-kit/approvals/Obligation.tsx +0 -111
  107. package/src/session-kit/approvals/ScopedBody.tsx +0 -392
  108. package/src/session-kit/approvals/approval-icons.ts +0 -31
  109. package/src/session-kit/approvals/approval-model.ts +0 -204
  110. package/src/session-kit/approvals/index.ts +0 -22
  111. package/src/session-kit/approvals/obligation-display.ts +0 -100
  112. package/src/session-kit/approvals/risk-accent.ts +0 -47
  113. package/src/session-kit/approvals/scope-icons.ts +0 -19
  114. package/src/session-kit/combobox/Combobox.tsx +0 -358
  115. package/src/session-kit/combobox/use-click-outside.ts +0 -21
  116. package/src/session-kit/display/ContextHeader.tsx +0 -148
  117. package/src/session-kit/display/FileDiffCard.tsx +0 -140
  118. package/src/session-kit/display/MD.tsx +0 -162
  119. package/src/session-kit/display/MessageTurn.tsx +0 -157
  120. package/src/session-kit/display/ThinkingPanel.tsx +0 -81
  121. package/src/session-kit/display/TodoChecklist.tsx +0 -120
  122. package/src/session-kit/display/TokenMeter.tsx +0 -89
  123. package/src/session-kit/display/ToolStrip.tsx +0 -278
  124. package/src/session-kit/display/TypingDots.tsx +0 -24
  125. package/src/session-kit/index.ts +0 -44
  126. package/src/session-kit/lib/enums.ts +0 -66
  127. package/src/session-kit/lib/portal-accent.ts +0 -30
  128. package/src/session-kit/lib/status-dot.ts +0 -68
  129. package/src/session-kit/primitives/Avatar.tsx +0 -44
  130. package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
  131. package/src/session-kit/primitives/RiskPill.tsx +0 -95
  132. package/src/session-kit/primitives/ScopeDot.tsx +0 -47
  133. package/src/session-kit/primitives/Segmented.tsx +0 -71
  134. package/src/ui/branding/app-branding-context.ts +0 -20
  135. package/src/ui/branding/app-branding-provider.tsx +0 -123
  136. package/src/ui/branding/sanitize.ts +0 -89
  137. package/src/ui/breakpoints.ts +0 -14
  138. package/src/ui/capture/document-preview.tsx +0 -166
  139. package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
  140. package/src/ui/capture/field-spec-to-zod.ts +0 -174
  141. package/src/ui/capture/field-spec-validate.ts +0 -236
  142. package/src/ui/capture/field-spec.test.ts +0 -49
  143. package/src/ui/capture/field-spec.ts +0 -274
  144. package/src/ui/capture/index.ts +0 -51
  145. package/src/ui/capture/schema-field-input.tsx +0 -620
  146. package/src/ui/capture/schema-form.tsx +0 -149
  147. package/src/ui/capture/submit-values.test.ts +0 -70
  148. package/src/ui/capture/submit-values.ts +0 -63
  149. package/src/ui/capture/use-prefill.ts +0 -186
  150. package/src/ui/capture/use-schema-validation.ts +0 -145
  151. package/src/ui/capture/use-wizard.ts +0 -180
  152. package/src/ui/capture/value-format.ts +0 -32
  153. package/src/ui/capture/wizard.tsx +0 -105
  154. package/src/ui/charts/bar-chart.tsx +0 -139
  155. package/src/ui/charts/chart-frame.tsx +0 -125
  156. package/src/ui/charts/chart-types.ts +0 -52
  157. package/src/ui/charts/chart-utils.ts +0 -190
  158. package/src/ui/charts/charts.test.tsx +0 -204
  159. package/src/ui/charts/donut-chart.tsx +0 -129
  160. package/src/ui/charts/index.ts +0 -27
  161. package/src/ui/charts/line-chart.tsx +0 -202
  162. package/src/ui/charts/sparkline.tsx +0 -109
  163. package/src/ui/chrome/AsyncBoundary.tsx +0 -74
  164. package/src/ui/chrome/EmptyState.tsx +0 -257
  165. package/src/ui/chrome/ErrorCard.tsx +0 -63
  166. package/src/ui/chrome/LoadingState.tsx +0 -61
  167. package/src/ui/chrome/PageHeader.tsx +0 -137
  168. package/src/ui/chrome/RecordCardList.tsx +0 -98
  169. package/src/ui/chrome/StateCard.tsx +0 -150
  170. package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
  171. package/src/ui/chrome/page-shell.spec.tsx +0 -117
  172. package/src/ui/chrome/page-shell.tsx +0 -221
  173. package/src/ui/chrome/scope-badge.tsx +0 -40
  174. package/src/ui/chrome/status-badge.tsx +0 -79
  175. package/src/ui/cn.ts +0 -32
  176. package/src/ui/design-tokens.ts +0 -372
  177. package/src/ui/hooks/use-debounced-value.ts +0 -16
  178. package/src/ui/hooks/use-is-mobile.ts +0 -28
  179. package/src/ui/index.ts +0 -110
  180. package/src/ui/pdf/pdf-preview.tsx +0 -122
  181. package/src/ui/primitives/alert-dialog.tsx +0 -104
  182. package/src/ui/primitives/async-combobox.tsx +0 -182
  183. package/src/ui/primitives/badge-variants.ts +0 -21
  184. package/src/ui/primitives/badge.tsx +0 -25
  185. package/src/ui/primitives/button-variants.ts +0 -31
  186. package/src/ui/primitives/button.tsx +0 -29
  187. package/src/ui/primitives/card.tsx +0 -43
  188. package/src/ui/primitives/checkbox.tsx +0 -26
  189. package/src/ui/primitives/collapsible.tsx +0 -9
  190. package/src/ui/primitives/dialog.tsx +0 -103
  191. package/src/ui/primitives/dropdown-menu.tsx +0 -179
  192. package/src/ui/primitives/form-stepper.tsx +0 -94
  193. package/src/ui/primitives/form-stepper.types.ts +0 -15
  194. package/src/ui/primitives/input.tsx +0 -22
  195. package/src/ui/primitives/label.tsx +0 -17
  196. package/src/ui/primitives/overflow-tabs.tsx +0 -331
  197. package/src/ui/primitives/popover.tsx +0 -33
  198. package/src/ui/primitives/preview-device-frame.tsx +0 -213
  199. package/src/ui/primitives/radio-group.tsx +0 -36
  200. package/src/ui/primitives/resizable.tsx +0 -67
  201. package/src/ui/primitives/scroll-area.tsx +0 -38
  202. package/src/ui/primitives/select.tsx +0 -143
  203. package/src/ui/primitives/separator.tsx +0 -20
  204. package/src/ui/primitives/sheet.tsx +0 -107
  205. package/src/ui/primitives/skeleton.tsx +0 -99
  206. package/src/ui/primitives/switch.tsx +0 -27
  207. package/src/ui/primitives/table.tsx +0 -72
  208. package/src/ui/primitives/tabs.tsx +0 -53
  209. package/src/ui/primitives/tag-multi-select.tsx +0 -261
  210. package/src/ui/primitives/textarea.tsx +0 -21
  211. package/src/ui/primitives/tooltip.tsx +0 -30
  212. package/src/ui/primitives/url-preview-chrome.tsx +0 -103
  213. package/src/vista/GenericCatalogueSurface.tsx +0 -179
  214. package/src/vista/SurfaceFallbackCard.tsx +0 -109
  215. package/src/vista/VistaPanel.tsx +0 -163
  216. package/src/vista/form-payload.spec.ts +0 -139
  217. package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
  218. package/src/vista/html/CustomUiFrame.tsx +0 -206
  219. package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
  220. package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
  221. package/src/vista/html/custom-ui-frame-contract.ts +0 -88
  222. package/src/vista/html/custom-ui-intents.spec.ts +0 -135
  223. package/src/vista/html/custom-ui-intents.ts +0 -155
  224. package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
  225. package/src/vista/html/custom-ui-srcdoc.ts +0 -193
  226. package/src/vista/html/index.ts +0 -13
  227. package/src/vista/index.ts +0 -52
  228. package/src/vista/surface-catalogue.spec.ts +0 -140
  229. package/src/vista/surface-catalogue.ts +0 -90
  230. package/src/vista/surface-icons.ts +0 -57
  231. package/src/vista/surface-registry.ts +0 -56
  232. package/src/vista/surfaces/ChartSurface.tsx +0 -34
  233. package/src/vista/surfaces/FilesSurface.tsx +0 -202
  234. package/src/vista/surfaces/FormSurface.tsx +0 -380
  235. package/src/vista/surfaces/HtmlSurface.tsx +0 -43
  236. package/src/vista/surfaces/TableSurface.tsx +0 -100
  237. package/src/vista/surfaces/UrlSurface.tsx +0 -40
  238. package/src/vista/surfaces/blob-surfaces.tsx +0 -139
  239. package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
  240. package/src/vista/surfaces/elicitation-transport.ts +0 -367
  241. package/src/vista/surfaces/file-surface-utils.ts +0 -69
  242. package/src/vista/surfaces/index.ts +0 -24
  243. package/src/vista/surfaces/register-surfaces.tsx +0 -139
  244. package/src/vista/surfaces/text-surfaces.tsx +0 -132
  245. package/src/vista/use-vista-tabs.ts +0 -196
  246. package/src/vista/vista-code-renderer-context.ts +0 -16
  247. package/src/vista/vista-code-renderer.tsx +0 -35
  248. package/src/vista/vista-file-context.ts +0 -14
  249. package/src/vista/vista-file-source.tsx +0 -141
  250. package/src/vista/vista-form-context.ts +0 -20
  251. package/src/vista/vista-form-host.tsx +0 -48
  252. package/src/vista/vista-model.ts +0 -137
  253. package/src/vista/vista-open-context.tsx +0 -27
  254. package/src/vista/vista-open-state.ts +0 -11
  255. package/src/vista/vista-payloads.ts +0 -635
  256. package/src/vista/vista-tabs-manifest.ts +0 -138
  257. package/src/vista/vista-tools.ts +0 -232
@@ -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
- }
@@ -1,141 +0,0 @@
1
- /**
2
- * Guards the shared closed+open contribution registry core — the ONE
3
- * implementation behind the ChatWidget, biome output-renderer, reply-variant
4
- * and Vista-surface planes. Every property the hand-written copies had must
5
- * survive the extraction.
6
- *
7
- * Runs under the host-web vitest mirror (this package carries no vitest dep).
8
- */
9
- import { describe, expect, it, vi } from 'vitest';
10
-
11
- import {
12
- ContributionFallbackReason,
13
- DuplicateContributionPolicy,
14
- createContributionRegistry,
15
- createRenderableContributionRegistry,
16
- } from './index';
17
-
18
- function makeRegistry(): ReturnType<typeof createContributionRegistry<string>> {
19
- return createContributionRegistry<string>({
20
- name: 'test',
21
- reservedKinds: ['plan', 'diff'],
22
- });
23
- }
24
-
25
- describe('createContributionRegistry', () => {
26
- it('resolves first-party before open, and lists both', () => {
27
- const registry = makeRegistry();
28
- registry.registerFirstParty('plan', 'builtin-plan');
29
- registry.register('acme:gantt', 'acme-gantt');
30
- expect(registry.get('plan')).toBe('builtin-plan');
31
- expect(registry.get('acme:gantt')).toBe('acme-gantt');
32
- expect(registry.has('nope')).toBe(false);
33
- expect(registry.list()).toEqual(['acme:gantt', 'plan']);
34
- expect(registry.listOpen()).toEqual(['acme:gantt']);
35
- expect([...registry.entries().keys()].sort()).toEqual(['acme:gantt', 'plan']);
36
- });
37
-
38
- it('refuses to let the open set claim a reserved first-party kind', () => {
39
- const registry = makeRegistry();
40
- expect(registry.isReserved('diff')).toBe(true);
41
- expect(() => registry.register('diff', 'hijack')).toThrow(/reserved/);
42
- // The namespaced form is fine.
43
- expect(() => registry.register('acme:diff', 'ok')).not.toThrow();
44
- });
45
-
46
- it('is idempotent for the identical entry and throws for a different one', () => {
47
- const registry = makeRegistry();
48
- registry.register('acme:gantt', 'entry');
49
- expect(() => registry.register('acme:gantt', 'entry')).not.toThrow();
50
- expect(() => registry.register('acme:gantt', 'other')).toThrow(
51
- /already registered/,
52
- );
53
- registry.registerFirstParty('plan', 'builtin');
54
- expect(() => registry.registerFirstParty('plan', 'builtin')).not.toThrow();
55
- expect(() => registry.registerFirstParty('plan', 'other')).toThrow(
56
- /already registered/,
57
- );
58
- });
59
-
60
- it('resets only the OPEN set', () => {
61
- const registry = makeRegistry();
62
- registry.registerFirstParty('plan', 'builtin');
63
- registry.register('acme:gantt', 'entry');
64
- registry.__resetOpenForTests();
65
- expect(registry.list()).toEqual(['plan']);
66
- });
67
-
68
- it('reports — never swallows — a duplicate under FirstWins', () => {
69
- const onDuplicate = vi.fn();
70
- const registry = createContributionRegistry<string>({
71
- name: 'overlay',
72
- duplicatePolicy: DuplicateContributionPolicy.FirstWins,
73
- onDuplicate,
74
- });
75
- registry.register('k', 'first');
76
- registry.register('k', 'second');
77
- expect(registry.get('k')).toBe('first');
78
- expect(onDuplicate).toHaveBeenCalledWith('k', 'first', 'second');
79
- });
80
-
81
- it('refuses FirstWins without a reporter', () => {
82
- expect(() =>
83
- createContributionRegistry<string>({
84
- name: 'overlay',
85
- duplicatePolicy: DuplicateContributionPolicy.FirstWins,
86
- }),
87
- ).toThrow(/onDuplicate/);
88
- });
89
- });
90
-
91
- describe('createRenderableContributionRegistry', () => {
92
- const registry = createRenderableContributionRegistry<string>({
93
- name: 'renderable',
94
- reservedKinds: ['plan'],
95
- });
96
- registry.registerFirstParty<{ label: string }>('plan', {
97
- version: 2,
98
- validate: (payload) =>
99
- typeof payload === 'object' &&
100
- payload !== null &&
101
- typeof (payload as { label?: unknown }).label === 'string'
102
- ? (payload as { label: string })
103
- : null,
104
- render: (payload) => `plan:${payload.label}`,
105
- });
106
-
107
- it('renders a matching envelope', () => {
108
- const resolution = registry.resolve({
109
- kind: 'plan',
110
- version: 1,
111
- payload: { label: 'ship it' },
112
- });
113
- expect(resolution).toEqual({
114
- matched: true,
115
- kind: 'plan',
116
- version: 2,
117
- rendered: 'plan:ship it',
118
- });
119
- });
120
-
121
- it('never throws — it returns a typed reason instead', () => {
122
- expect(
123
- registry.resolve({ kind: 'nope', version: 1, payload: {} }),
124
- ).toMatchObject({
125
- matched: false,
126
- reason: ContributionFallbackReason.UnknownKind,
127
- });
128
- expect(
129
- registry.resolve({ kind: 'plan', version: 99, payload: {} }),
130
- ).toMatchObject({
131
- matched: false,
132
- reason: ContributionFallbackReason.UnsupportedVersion,
133
- });
134
- expect(
135
- registry.resolve({ kind: 'plan', version: 1, payload: { label: 7 } }),
136
- ).toMatchObject({
137
- matched: false,
138
- reason: ContributionFallbackReason.InvalidPayload,
139
- });
140
- });
141
- });