@xemahq/ui-kernel 0.2.0 → 0.3.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.
- package/dist/lib/biome-host/errors.d.ts +2 -0
- package/dist/lib/biome-host/errors.d.ts.map +1 -0
- package/dist/lib/biome-host/errors.js +146 -0
- package/dist/lib/biome-host/errors.js.map +1 -0
- package/dist/lib/biome-host/host-bridge.d.ts +6 -0
- package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.js.map +1 -1
- package/dist/lib/biome-host/index.d.ts +4 -0
- package/dist/lib/biome-host/index.d.ts.map +1 -1
- package/dist/lib/biome-host/index.js +4 -0
- package/dist/lib/biome-host/index.js.map +1 -1
- package/dist/lib/biome-host/realtime-hooks.d.ts +5 -0
- package/dist/lib/biome-host/realtime-hooks.d.ts.map +1 -0
- package/dist/lib/biome-host/realtime-hooks.js +28 -0
- package/dist/lib/biome-host/realtime-hooks.js.map +1 -0
- package/dist/lib/biome-host/realtime-port.d.ts +30 -0
- package/dist/lib/biome-host/realtime-port.d.ts.map +1 -0
- package/dist/lib/biome-host/realtime-port.js +3 -0
- package/dist/lib/biome-host/realtime-port.js.map +1 -0
- package/dist/lib/biome-host/response-envelope.d.ts +3 -0
- package/dist/lib/biome-host/response-envelope.d.ts.map +1 -0
- package/dist/lib/biome-host/response-envelope.js +25 -0
- package/dist/lib/biome-host/response-envelope.js.map +1 -0
- package/dist/ui/chrome/ErrorCard.d.ts.map +1 -1
- package/dist/ui/chrome/ErrorCard.js +2 -9
- package/dist/ui/chrome/ErrorCard.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/biome-host/errors.ts +220 -0
- package/src/lib/biome-host/host-bridge.ts +44 -0
- package/src/lib/biome-host/index.ts +4 -0
- package/src/lib/biome-host/realtime-hooks.ts +74 -0
- package/src/lib/biome-host/realtime-port.ts +109 -0
- package/src/lib/biome-host/response-envelope.ts +69 -0
- package/src/ui/chrome/ErrorCard.tsx +8 -13
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/errors.ts"],"names":[],"mappings":"AA0MA,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,EACd,QAAQ,SAA4C,GACnD,MAAM,CAcR"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUserFacingErrorMessage = getUserFacingErrorMessage;
|
|
4
|
+
function collapseWhitespace(value) {
|
|
5
|
+
return value.split(/\s+/).join(' ').trim();
|
|
6
|
+
}
|
|
7
|
+
function getGenericErrorMessage(message, fallback) {
|
|
8
|
+
const normalized = collapseWhitespace(message);
|
|
9
|
+
if (/failed to fetch/i.test(normalized)) {
|
|
10
|
+
return 'Could not reach the service. Check your connection and try again.';
|
|
11
|
+
}
|
|
12
|
+
return normalized || fallback;
|
|
13
|
+
}
|
|
14
|
+
function normalizeValidationMessage(rawMessage) {
|
|
15
|
+
const parts = rawMessage
|
|
16
|
+
.split(',')
|
|
17
|
+
.map((part) => collapseWhitespace(part))
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
if (parts.length === 0) {
|
|
20
|
+
return 'Some required fields are missing or invalid. Please review your input and try again.';
|
|
21
|
+
}
|
|
22
|
+
const preview = parts.slice(0, 2).join('; ');
|
|
23
|
+
const suffix = parts.length > 2 ? '; and more.' : '.';
|
|
24
|
+
return `Some required fields are missing or invalid (${preview}${suffix})`;
|
|
25
|
+
}
|
|
26
|
+
function isApiClientErrorLike(error) {
|
|
27
|
+
if (!error || typeof error !== 'object') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const record = error;
|
|
31
|
+
return (record['name'] === 'ApiClientError' &&
|
|
32
|
+
typeof record['statusCode'] === 'number' &&
|
|
33
|
+
typeof record['message'] === 'string');
|
|
34
|
+
}
|
|
35
|
+
function isOrvalClientErrorLike(error) {
|
|
36
|
+
if (!error || typeof error !== 'object') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const record = error;
|
|
40
|
+
return (record['name'] === 'ClientError' &&
|
|
41
|
+
typeof record['status'] === 'number' &&
|
|
42
|
+
typeof record['message'] === 'string' &&
|
|
43
|
+
Object.hasOwn(record, 'body'));
|
|
44
|
+
}
|
|
45
|
+
function readStringList(value) {
|
|
46
|
+
if (!Array.isArray(value)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (value.some((entry) => typeof entry !== 'string')) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function normalizeWorkflowWalletMissingMessage(envelope) {
|
|
55
|
+
if (envelope.code !== 'WORKFLOW_WALLET_NOT_FOUND') {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const missing = readStringList(envelope.details?.['missing']);
|
|
59
|
+
if (missing === null || missing.length === 0) {
|
|
60
|
+
return 'One or more required wallets are missing for this project.';
|
|
61
|
+
}
|
|
62
|
+
return `Missing wallet${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}. Add ${missing.length > 1 ? 'them' : 'it'} in Project Settings -> Wallets.`;
|
|
63
|
+
}
|
|
64
|
+
function getApiClientErrorMessage(error, fallback) {
|
|
65
|
+
if (error.statusCode === 0) {
|
|
66
|
+
return collapseWhitespace(error.message) || fallback;
|
|
67
|
+
}
|
|
68
|
+
if (error.statusCode === 400) {
|
|
69
|
+
const raw = collapseWhitespace(error.message);
|
|
70
|
+
if (raw.includes(' must ') || raw.includes('regular expression')) {
|
|
71
|
+
return normalizeValidationMessage(raw);
|
|
72
|
+
}
|
|
73
|
+
return raw || 'The request is invalid. Please check your input and try again.';
|
|
74
|
+
}
|
|
75
|
+
if (error.statusCode === 401) {
|
|
76
|
+
return 'Your session expired. Please sign in again.';
|
|
77
|
+
}
|
|
78
|
+
if (error.statusCode === 403) {
|
|
79
|
+
return 'You do not have permission to perform this action.';
|
|
80
|
+
}
|
|
81
|
+
if (error.statusCode === 404) {
|
|
82
|
+
return 'The requested resource was not found.';
|
|
83
|
+
}
|
|
84
|
+
if (error.statusCode >= 500) {
|
|
85
|
+
return 'The service is temporarily unavailable. Please try again in a moment.';
|
|
86
|
+
}
|
|
87
|
+
return collapseWhitespace(error.message) || fallback;
|
|
88
|
+
}
|
|
89
|
+
function getOrvalClientErrorMessage(error, fallback) {
|
|
90
|
+
if (error.status >= 500) {
|
|
91
|
+
return 'The service is temporarily unavailable. Please try again in a moment.';
|
|
92
|
+
}
|
|
93
|
+
if (error.status === 409) {
|
|
94
|
+
const body = error.body;
|
|
95
|
+
if (body && typeof body === 'object') {
|
|
96
|
+
const record = body;
|
|
97
|
+
if (record['error'] === 'session_busy') {
|
|
98
|
+
return 'Your last message is still running. Wait for the agent to finish, then try again.';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const body = error.body;
|
|
103
|
+
if (body && typeof body === 'object') {
|
|
104
|
+
const record = body;
|
|
105
|
+
const payload = (() => {
|
|
106
|
+
const message = record['message'];
|
|
107
|
+
if (message && typeof message === 'object') {
|
|
108
|
+
return message;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
code: typeof record['code'] === 'string' ? record['code'] : undefined,
|
|
112
|
+
message: typeof record['message'] === 'string' ? record['message'] : undefined,
|
|
113
|
+
details: record['details'] && typeof record['details'] === 'object'
|
|
114
|
+
? record['details']
|
|
115
|
+
: undefined,
|
|
116
|
+
};
|
|
117
|
+
})();
|
|
118
|
+
const workflowWalletMessage = normalizeWorkflowWalletMissingMessage(payload);
|
|
119
|
+
if (workflowWalletMessage !== null) {
|
|
120
|
+
return workflowWalletMessage;
|
|
121
|
+
}
|
|
122
|
+
if (typeof payload.message === 'string' && payload.message.trim().length > 0) {
|
|
123
|
+
return collapseWhitespace(payload.message);
|
|
124
|
+
}
|
|
125
|
+
if (typeof record['message'] === 'string' && record['message'].trim().length > 0) {
|
|
126
|
+
return collapseWhitespace(record['message']);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return getGenericErrorMessage(error.message, fallback);
|
|
130
|
+
}
|
|
131
|
+
function getUserFacingErrorMessage(error, fallback = 'Something went wrong. Please try again.') {
|
|
132
|
+
if (typeof error === 'string') {
|
|
133
|
+
return collapseWhitespace(error) || fallback;
|
|
134
|
+
}
|
|
135
|
+
if (isApiClientErrorLike(error)) {
|
|
136
|
+
return getApiClientErrorMessage(error, fallback);
|
|
137
|
+
}
|
|
138
|
+
if (isOrvalClientErrorLike(error)) {
|
|
139
|
+
return getOrvalClientErrorMessage(error, fallback);
|
|
140
|
+
}
|
|
141
|
+
if (error instanceof Error) {
|
|
142
|
+
return getGenericErrorMessage(error.message, fallback);
|
|
143
|
+
}
|
|
144
|
+
return fallback;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/biome-host/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import type { QueryClient } from '@tanstack/react-query';
|
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import type { CapabilityPort } from '../capabilities';
|
|
4
4
|
import type { SystemBus } from '../system-bus';
|
|
5
|
+
import type { RealtimeSource } from './realtime-port';
|
|
5
6
|
export interface HostBridgeNavigation {
|
|
6
7
|
push(path: string): void;
|
|
7
8
|
replace(path: string): void;
|
|
@@ -29,6 +30,9 @@ export interface HostBridgeToast {
|
|
|
29
30
|
export interface HostBridgeRequestContext {
|
|
30
31
|
readonly correlationId: string;
|
|
31
32
|
}
|
|
33
|
+
export interface HostBridgeErrors {
|
|
34
|
+
getUserFacingErrorMessage(error: unknown, fallback?: string): string;
|
|
35
|
+
}
|
|
32
36
|
export interface PageBackTarget {
|
|
33
37
|
readonly to: string;
|
|
34
38
|
readonly label?: string;
|
|
@@ -54,6 +58,8 @@ export interface HostBridge {
|
|
|
54
58
|
readonly pageMeta: HostBridgePageMeta;
|
|
55
59
|
readonly system: SystemBus;
|
|
56
60
|
readonly capabilities: CapabilityPort;
|
|
61
|
+
readonly realtime?: RealtimeSource;
|
|
62
|
+
readonly errors?: HostBridgeErrors;
|
|
57
63
|
}
|
|
58
64
|
export declare const HostBridgeContext: import("react").Context<HostBridge | null>;
|
|
59
65
|
export declare function useHostBridge(): HostBridge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAatD,MAAM,WAAW,oBAAoB;IAEnC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAK5B,WAAW,IAAI,YAAY,CAAC;IAe5B,cAAc,CACZ,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAC9E,CAAC,CAAC;IAaP,eAAe,IAAI,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAE7B,aAAa,IAAI,MAAM,GAAG,IAAI,CAAC;IAE/B,QAAQ,IAAI,MAAM,GAAG,IAAI,CAAC;IAE1B,YAAY,IAAI,MAAM,GAAG,IAAI,CAAC;IAE9B,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC;IAc3B,gBAAgB,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAQ/D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAO9B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAaD,MAAM,WAAW,gBAAgB;IAM/B,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACtE;AAQD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAUD,MAAM,WAAW,QAAQ;IAEvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAEjC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAEnC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;CACjC;AAaD,MAAM,WAAW,kBAAkB;IAMjC,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,wBAAwB,CAAC;IAOlD,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAStC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAS3B,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC;IAYtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAUnC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAED,eAAO,MAAM,iBAAiB,4CAAyC,CAAC;AAQxE,wBAAgB,aAAa,IAAI,UAAU,CAS1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-bridge.js","sourceRoot":"","sources":["../../../src/lib/biome-host/host-bridge.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"host-bridge.js","sourceRoot":"","sources":["../../../src/lib/biome-host/host-bridge.ts"],"names":[],"mappings":";;;AAiQA,sCASC;AAxQD,iCAAkE;AAuPrD,QAAA,iBAAiB,GAAG,IAAA,qBAAa,EAAoB,IAAI,CAAC,CAAC;AACxE,yBAAiB,CAAC,WAAW,GAAG,mBAAmB,CAAC;AAOpD,SAAgB,aAAa;IAC3B,MAAM,MAAM,GAAG,IAAA,kBAAU,EAAC,yBAAiB,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,gFAAgF;YAC9E,0EAA0E,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -12,4 +12,8 @@ export * from './host-sources';
|
|
|
12
12
|
export * from './nav';
|
|
13
13
|
export * from './biome-mode';
|
|
14
14
|
export * from './agent-validation';
|
|
15
|
+
export * from './realtime-port';
|
|
16
|
+
export * from './realtime-hooks';
|
|
17
|
+
export * from './errors';
|
|
18
|
+
export * from './response-envelope';
|
|
15
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/index.ts"],"names":[],"mappings":"AAcA,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/index.ts"],"names":[],"mappings":"AAcA,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC"}
|
|
@@ -28,4 +28,8 @@ __exportStar(require("./host-sources"), exports);
|
|
|
28
28
|
__exportStar(require("./nav"), exports);
|
|
29
29
|
__exportStar(require("./biome-mode"), exports);
|
|
30
30
|
__exportStar(require("./agent-validation"), exports);
|
|
31
|
+
__exportStar(require("./realtime-port"), exports);
|
|
32
|
+
__exportStar(require("./realtime-hooks"), exports);
|
|
33
|
+
__exportStar(require("./errors"), exports);
|
|
34
|
+
__exportStar(require("./response-envelope"), exports);
|
|
31
35
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/biome-host/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAcA,mDAAiC;AACjC,gDAA8B;AAC9B,qDAAmC;AACnC,gDAA8B;AAC9B,uDAAqC;AACrC,mDAAiC;AACjC,8DAA4C;AAC5C,mDAAiC;AACjC,0DAAwC;AACxC,qDAAmC;AACnC,iDAA+B;AAC/B,wCAAsB;AACtB,+CAA6B;AAC7B,qDAAmC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/biome-host/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAcA,mDAAiC;AACjC,gDAA8B;AAC9B,qDAAmC;AACnC,gDAA8B;AAC9B,uDAAqC;AACrC,mDAAiC;AACjC,8DAA4C;AAC5C,mDAAiC;AACjC,0DAAwC;AACxC,qDAAmC;AACnC,iDAA+B;AAC/B,wCAAsB;AACtB,+CAA6B;AAC7B,qDAAmC;AACnC,kDAAgC;AAChC,mDAAiC;AACjC,2CAAyB;AACzB,sDAAoC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RealtimeCloudEvent, RealtimeConnectionState, RealtimeScope } from './realtime-port';
|
|
2
|
+
export declare function useCloudEvent(eventType: string, handler: (event: RealtimeCloudEvent) => void): void;
|
|
3
|
+
export declare function useRealtimeStatus(): RealtimeConnectionState;
|
|
4
|
+
export declare function useEventScope(scope: RealtimeScope): void;
|
|
5
|
+
//# sourceMappingURL=realtime-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-hooks.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/realtime-hooks.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EAEd,MAAM,iBAAiB,CAAC;AAiCzB,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAC3C,IAAI,CAGN;AAGD,wBAAgB,iBAAiB,IAAI,uBAAuB,CAG3D;AAOD,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAGxD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useCloudEvent = useCloudEvent;
|
|
4
|
+
exports.useRealtimeStatus = useRealtimeStatus;
|
|
5
|
+
exports.useEventScope = useEventScope;
|
|
6
|
+
const host_bridge_1 = require("./host-bridge");
|
|
7
|
+
function requireRealtime(realtime) {
|
|
8
|
+
if (!realtime) {
|
|
9
|
+
throw new Error('useCloudEvent/useRealtimeStatus/useEventScope require a realtime ' +
|
|
10
|
+
'transport, but the host has not wired `bridge.realtime`. The host ' +
|
|
11
|
+
'must provide a RealtimeSource (e.g. backed by @xemahq/realtime-client) ' +
|
|
12
|
+
'on the HostBridge to use kernel realtime hooks.');
|
|
13
|
+
}
|
|
14
|
+
return realtime;
|
|
15
|
+
}
|
|
16
|
+
function useCloudEvent(eventType, handler) {
|
|
17
|
+
const bridge = (0, host_bridge_1.useHostBridge)();
|
|
18
|
+
requireRealtime(bridge.realtime).useCloudEvent(eventType, handler);
|
|
19
|
+
}
|
|
20
|
+
function useRealtimeStatus() {
|
|
21
|
+
const bridge = (0, host_bridge_1.useHostBridge)();
|
|
22
|
+
return requireRealtime(bridge.realtime).useRealtimeStatus();
|
|
23
|
+
}
|
|
24
|
+
function useEventScope(scope) {
|
|
25
|
+
const bridge = (0, host_bridge_1.useHostBridge)();
|
|
26
|
+
requireRealtime(bridge.realtime).useEventScope(scope);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=realtime-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-hooks.js","sourceRoot":"","sources":["../../../src/lib/biome-host/realtime-hooks.ts"],"names":[],"mappings":";;AAmDA,sCAMC;AAGD,8CAGC;AAOD,sCAGC;AA7DD,+CAA8C;AAc9C,SAAS,eAAe,CAAC,QAAoC;IAC3D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,mEAAmE;YACjE,oEAAoE;YACpE,yEAAyE;YACzE,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAeD,SAAgB,aAAa,CAC3B,SAAiB,EACjB,OAA4C;IAE5C,MAAM,MAAM,GAAG,IAAA,2BAAa,GAAE,CAAC;IAC/B,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AACrE,CAAC;AAGD,SAAgB,iBAAiB;IAC/B,MAAM,MAAM,GAAG,IAAA,2BAAa,GAAE,CAAC;IAC/B,OAAO,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,iBAAiB,EAAE,CAAC;AAC9D,CAAC;AAOD,SAAgB,aAAa,CAAC,KAAoB;IAChD,MAAM,MAAM,GAAG,IAAA,2BAAa,GAAE,CAAC;IAC/B,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface RealtimeCloudEvent {
|
|
2
|
+
id?: string;
|
|
3
|
+
type: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
subject?: string;
|
|
6
|
+
data?: unknown;
|
|
7
|
+
ehorgid: string;
|
|
8
|
+
ehprojectid?: string;
|
|
9
|
+
ehuserid?: string;
|
|
10
|
+
ehglobalseq?: string;
|
|
11
|
+
ehorgseq?: string;
|
|
12
|
+
ehprojectseq?: string;
|
|
13
|
+
ehsessionseq?: string;
|
|
14
|
+
__hint?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
|
|
17
|
+
export interface RealtimeConnectionState {
|
|
18
|
+
readonly status: RealtimeStatus;
|
|
19
|
+
readonly connectionId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RealtimeScope {
|
|
22
|
+
readonly kind: 'project' | 'session';
|
|
23
|
+
readonly id: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RealtimeSource {
|
|
26
|
+
useCloudEvent(eventType: string, handler: (event: RealtimeCloudEvent) => void): void;
|
|
27
|
+
useRealtimeStatus(): RealtimeConnectionState;
|
|
28
|
+
useEventScope(scope: RealtimeScope): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=realtime-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-port.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/realtime-port.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,kBAAkB;IAEjC,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,OAAO,EAAE,MAAM,CAAC;IAEhB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAGD,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,YAAY,GACZ,WAAW,GACX,cAAc,GACd,QAAQ,CAAC;AAGb,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAOD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAWD,MAAM,WAAW,cAAc;IAO7B,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI,CAAC;IAErF,iBAAiB,IAAI,uBAAuB,CAAC;IAM7C,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-port.js","sourceRoot":"","sources":["../../../src/lib/biome-host/realtime-port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-envelope.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/response-envelope.ts"],"names":[],"mappings":"AA+BA,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,EAAE,CAYjD;AAeD,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAU/C"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.unwrapList = unwrapList;
|
|
4
|
+
exports.unwrapData = unwrapData;
|
|
5
|
+
function unwrapList(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (value !== null &&
|
|
10
|
+
typeof value === 'object' &&
|
|
11
|
+
Array.isArray(value.data)) {
|
|
12
|
+
return value.data;
|
|
13
|
+
}
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
function unwrapData(value) {
|
|
17
|
+
if (value !== null &&
|
|
18
|
+
typeof value === 'object' &&
|
|
19
|
+
'data' in value &&
|
|
20
|
+
!('pagination' in value)) {
|
|
21
|
+
return value.data;
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=response-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-envelope.js","sourceRoot":"","sources":["../../../src/lib/biome-host/response-envelope.ts"],"names":[],"mappings":";;AA+BA,gCAYC;AAeD,gCAUC;AArCD,SAAgB,UAAU,CAAI,KAAc;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAY,CAAC;IACtB,CAAC;IACD,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,OAAO,CAAE,KAA4B,CAAC,IAAI,CAAC,EACjD,CAAC;QACD,OAAQ,KAAuB,CAAC,IAAI,CAAC;IACvC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAeD,SAAgB,UAAU,CAAI,KAAc;IAC1C,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,MAAM,IAAK,KAAiC;QAC5C,CAAC,CAAC,YAAY,IAAK,KAAiC,CAAC,EACrD,CAAC;QACD,OAAQ,KAAqB,CAAC,IAAI,CAAC;IACrC,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorCard.d.ts","sourceRoot":"","sources":["../../../src/ui/chrome/ErrorCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ErrorCard.d.ts","sourceRoot":"","sources":["../../../src/ui/chrome/ErrorCard.tsx"],"names":[],"mappings":"AAaA,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAKjF,UAAU,cAAc;IACtB,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAMrB,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,KAAK,EACL,KAA8B,EAC9B,OAAO,EACP,WAAgC,EAChC,eAA0D,GAC3D,EAAE,cAAc,+BAyBhB"}
|
|
@@ -3,17 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.default = ErrorCard;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const lucide_react_1 = require("lucide-react");
|
|
6
|
+
const errors_1 = require("../../lib/biome-host/errors");
|
|
6
7
|
const button_1 = require("../primitives/button");
|
|
7
8
|
const card_1 = require("../primitives/card");
|
|
8
|
-
const defaultFormatError = (error, fallback) =>
|
|
9
|
-
if (typeof error === 'string') {
|
|
10
|
-
return error || fallback;
|
|
11
|
-
}
|
|
12
|
-
if (error instanceof Error && error.message) {
|
|
13
|
-
return error.message;
|
|
14
|
-
}
|
|
15
|
-
return fallback;
|
|
16
|
-
};
|
|
9
|
+
const defaultFormatError = (error, fallback) => (0, errors_1.getUserFacingErrorMessage)(error, fallback);
|
|
17
10
|
function ErrorCard({ error, title = 'Something went wrong', onRetry, formatError = defaultFormatError, fallbackMessage = 'Unable to load this content right now.', }) {
|
|
18
11
|
const message = formatError(error, fallbackMessage);
|
|
19
12
|
return ((0, jsx_runtime_1.jsx)(card_1.Card, { className: "border-destructive/25 bg-destructive/[0.03] rounded-xl", children: (0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "flex flex-col items-center py-14 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-14 w-14 rounded-2xl bg-destructive/10 flex items-center justify-center mb-4", children: (0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { className: "h-6 w-6 text-destructive" }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-subtitle font-semibold text-ink", children: title }), (0, jsx_runtime_1.jsx)("p", { className: "text-body-1 text-ink-3 mt-2 max-w-md leading-relaxed", children: message }), onRetry && ((0, jsx_runtime_1.jsxs)(button_1.Button, { size: "sm", variant: "outline", onClick: onRetry, className: "mt-5 gap-2 h-10 border-destructive/20 text-destructive hover:bg-destructive/5", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { className: "h-4 w-4" }), "Retry"] }))] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorCard.js","sourceRoot":"","sources":["../../../src/ui/chrome/ErrorCard.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ErrorCard.js","sourceRoot":"","sources":["../../../src/ui/chrome/ErrorCard.tsx"],"names":[],"mappings":";;AA+BA,4BA+BC;;AA9DD,+CAAsD;AAEtD,wDAAwE;AACxE,iDAA8C;AAC9C,6CAAuD;AAWvD,MAAM,kBAAkB,GAA0B,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CACpE,IAAA,kCAAyB,EAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAe7C,SAAwB,SAAS,CAAC,EAChC,KAAK,EACL,KAAK,GAAG,sBAAsB,EAC9B,OAAO,EACP,WAAW,GAAG,kBAAkB,EAChC,eAAe,GAAG,wCAAwC,GAC3C;IACf,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAEpD,OAAO,CACL,uBAAC,WAAI,IAAC,SAAS,EAAC,wDAAwD,YACtE,wBAAC,kBAAW,IAAC,SAAS,EAAC,8CAA8C,aACnE,gCAAK,SAAS,EAAC,+EAA+E,YAC5F,uBAAC,0BAAW,IAAC,SAAS,EAAC,0BAA0B,GAAG,GAChD,EACN,8BAAG,SAAS,EAAC,sCAAsC,YAAE,KAAK,GAAK,EAC/D,8BAAG,SAAS,EAAC,sDAAsD,YAAE,OAAO,GAAK,EAChF,OAAO,IAAI,CACV,wBAAC,eAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,SAAS,EACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,+EAA+E,aAEzF,uBAAC,wBAAS,IAAC,SAAS,EAAC,SAAS,GAAG,aAE1B,CACV,IACW,GACT,CACR,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/ui-kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Host-framework-agnostic UI kernel for the Xema OS. Defines the SystemBus orchestration contract (capability.invoke, cross-biome intents, command palette, xema:// deeplinks, window manager) AND the biome-host contract surface (FrontendBiome/FrontendBiomeFactory, HostBridge, the singleton biomeRegistry, session contributions) that every frontend biome composes against. No Vite, Next.js, or React-Router — React itself IS allowed as the shared component model (the contracts traffic in ReactNode/ComponentType and a React context). Concrete host adapters (router/auth/toast wiring) live in separate packages that consume this kernel. The SystemBus is pure orchestration: it never authorizes, the backend capability-router enforces all policy.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xema",
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical user-facing error decoder — ONE implementation biomes, the host
|
|
3
|
+
* shell, and the kernel's {@link ErrorCard} default all share.
|
|
4
|
+
*
|
|
5
|
+
* Biomes were each re-implementing "pull a human message off an error"
|
|
6
|
+
* (envelope shapes, status-code copy, "failed to fetch"). This is the single
|
|
7
|
+
* host-agnostic decoder. It is a PURE function: no fetch, no i18n, no config,
|
|
8
|
+
* no `instanceof` against host classes. It recognises the two error shapes the
|
|
9
|
+
* Xema frontend produces by their structural signature:
|
|
10
|
+
*
|
|
11
|
+
* - the host's `ApiClientError` (`name === 'ApiClientError'`, `statusCode`,
|
|
12
|
+
* `message`, optional `errorType`), thrown by the hand-rolled HTTP client;
|
|
13
|
+
* - Orval's generated `ClientError` (`name === 'ClientError'`, `status`,
|
|
14
|
+
* `message`, `body`), thrown by the generated clients. Its `body` carries
|
|
15
|
+
* the backend `{ message, code, details }` envelope (and the workflow
|
|
16
|
+
* wallet-missing / `session_busy` special cases).
|
|
17
|
+
*
|
|
18
|
+
* Hosts that previously kept their own `getUserFacingErrorMessage` should
|
|
19
|
+
* re-export THIS one (or delete theirs and import it) so there is a single
|
|
20
|
+
* decoder. {@link ErrorCard} defaults to it, so biomes get envelope-aware copy
|
|
21
|
+
* without wiring anything.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
function collapseWhitespace(value: string): string {
|
|
25
|
+
return value.split(/\s+/).join(' ').trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getGenericErrorMessage(message: string, fallback: string): string {
|
|
29
|
+
const normalized = collapseWhitespace(message);
|
|
30
|
+
if (/failed to fetch/i.test(normalized)) {
|
|
31
|
+
return 'Could not reach the service. Check your connection and try again.';
|
|
32
|
+
}
|
|
33
|
+
return normalized || fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeValidationMessage(rawMessage: string): string {
|
|
37
|
+
const parts = rawMessage
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((part) => collapseWhitespace(part))
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
|
|
42
|
+
if (parts.length === 0) {
|
|
43
|
+
return 'Some required fields are missing or invalid. Please review your input and try again.';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const preview = parts.slice(0, 2).join('; ');
|
|
47
|
+
const suffix = parts.length > 2 ? '; and more.' : '.';
|
|
48
|
+
return `Some required fields are missing or invalid (${preview}${suffix})`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Structural signature of the host's `ApiClientError`. */
|
|
52
|
+
interface ApiClientErrorLike {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly statusCode: number;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
readonly errorType?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isApiClientErrorLike(error: unknown): error is ApiClientErrorLike {
|
|
60
|
+
if (!error || typeof error !== 'object') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const record = error as Record<string, unknown>;
|
|
64
|
+
return (
|
|
65
|
+
record['name'] === 'ApiClientError' &&
|
|
66
|
+
typeof record['statusCode'] === 'number' &&
|
|
67
|
+
typeof record['message'] === 'string'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Structural signature of an Orval-generated `ClientError`. */
|
|
72
|
+
interface OrvalClientErrorLike {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly status: number;
|
|
75
|
+
readonly message: string;
|
|
76
|
+
readonly body: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isOrvalClientErrorLike(error: unknown): error is OrvalClientErrorLike {
|
|
80
|
+
if (!error || typeof error !== 'object') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const record = error as Record<string, unknown>;
|
|
84
|
+
return (
|
|
85
|
+
record['name'] === 'ClientError' &&
|
|
86
|
+
typeof record['status'] === 'number' &&
|
|
87
|
+
typeof record['message'] === 'string' &&
|
|
88
|
+
Object.hasOwn(record, 'body')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface WorkflowErrorEnvelope {
|
|
93
|
+
readonly code?: string;
|
|
94
|
+
readonly message?: string;
|
|
95
|
+
readonly details?: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readStringList(value: unknown): string[] | null {
|
|
99
|
+
if (!Array.isArray(value)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (value.some((entry) => typeof entry !== 'string')) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeWorkflowWalletMissingMessage(envelope: WorkflowErrorEnvelope): string | null {
|
|
109
|
+
if (envelope.code !== 'WORKFLOW_WALLET_NOT_FOUND') {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const missing = readStringList(envelope.details?.['missing']);
|
|
113
|
+
if (missing === null || missing.length === 0) {
|
|
114
|
+
return 'One or more required wallets are missing for this project.';
|
|
115
|
+
}
|
|
116
|
+
return `Missing wallet${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}. Add ${
|
|
117
|
+
missing.length > 1 ? 'them' : 'it'
|
|
118
|
+
} in Project Settings -> Wallets.`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getApiClientErrorMessage(error: ApiClientErrorLike, fallback: string): string {
|
|
122
|
+
if (error.statusCode === 0) {
|
|
123
|
+
return collapseWhitespace(error.message) || fallback;
|
|
124
|
+
}
|
|
125
|
+
if (error.statusCode === 400) {
|
|
126
|
+
const raw = collapseWhitespace(error.message);
|
|
127
|
+
if (raw.includes(' must ') || raw.includes('regular expression')) {
|
|
128
|
+
return normalizeValidationMessage(raw);
|
|
129
|
+
}
|
|
130
|
+
return raw || 'The request is invalid. Please check your input and try again.';
|
|
131
|
+
}
|
|
132
|
+
if (error.statusCode === 401) {
|
|
133
|
+
return 'Your session expired. Please sign in again.';
|
|
134
|
+
}
|
|
135
|
+
if (error.statusCode === 403) {
|
|
136
|
+
return 'You do not have permission to perform this action.';
|
|
137
|
+
}
|
|
138
|
+
if (error.statusCode === 404) {
|
|
139
|
+
return 'The requested resource was not found.';
|
|
140
|
+
}
|
|
141
|
+
if (error.statusCode >= 500) {
|
|
142
|
+
return 'The service is temporarily unavailable. Please try again in a moment.';
|
|
143
|
+
}
|
|
144
|
+
return collapseWhitespace(error.message) || fallback;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getOrvalClientErrorMessage(error: OrvalClientErrorLike, fallback: string): string {
|
|
148
|
+
if (error.status >= 500) {
|
|
149
|
+
return 'The service is temporarily unavailable. Please try again in a moment.';
|
|
150
|
+
}
|
|
151
|
+
// session-api refuses mutations while a prompt turn is in flight; the 409
|
|
152
|
+
// body is `{ error: 'session_busy', retryAfterTurnDone: true }`. Surface a
|
|
153
|
+
// clear retry hint instead of the generic "Conflict" copy.
|
|
154
|
+
if (error.status === 409) {
|
|
155
|
+
const body = error.body;
|
|
156
|
+
if (body && typeof body === 'object') {
|
|
157
|
+
const record = body as Record<string, unknown>;
|
|
158
|
+
if (record['error'] === 'session_busy') {
|
|
159
|
+
return 'Your last message is still running. Wait for the agent to finish, then try again.';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const body = error.body;
|
|
164
|
+
if (body && typeof body === 'object') {
|
|
165
|
+
const record = body as Record<string, unknown>;
|
|
166
|
+
const payload: WorkflowErrorEnvelope = (() => {
|
|
167
|
+
const message = record['message'];
|
|
168
|
+
if (message && typeof message === 'object') {
|
|
169
|
+
return message as WorkflowErrorEnvelope;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
code: typeof record['code'] === 'string' ? record['code'] : undefined,
|
|
173
|
+
message: typeof record['message'] === 'string' ? record['message'] : undefined,
|
|
174
|
+
details:
|
|
175
|
+
record['details'] && typeof record['details'] === 'object'
|
|
176
|
+
? (record['details'] as Record<string, unknown>)
|
|
177
|
+
: undefined,
|
|
178
|
+
};
|
|
179
|
+
})();
|
|
180
|
+
|
|
181
|
+
const workflowWalletMessage = normalizeWorkflowWalletMissingMessage(payload);
|
|
182
|
+
if (workflowWalletMessage !== null) {
|
|
183
|
+
return workflowWalletMessage;
|
|
184
|
+
}
|
|
185
|
+
if (typeof payload.message === 'string' && payload.message.trim().length > 0) {
|
|
186
|
+
return collapseWhitespace(payload.message);
|
|
187
|
+
}
|
|
188
|
+
if (typeof record['message'] === 'string' && record['message'].trim().length > 0) {
|
|
189
|
+
return collapseWhitespace(record['message']);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return getGenericErrorMessage(error.message, fallback);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Decode any thrown value into a human, user-facing message. The single decoder
|
|
197
|
+
* for biomes, the host shell, and {@link ErrorCard}'s default.
|
|
198
|
+
*
|
|
199
|
+
* @param error Any thrown value (string, `Error`, `ApiClientError`, Orval
|
|
200
|
+
* `ClientError`, or unknown).
|
|
201
|
+
* @param fallback Copy returned when nothing better can be extracted.
|
|
202
|
+
*/
|
|
203
|
+
export function getUserFacingErrorMessage(
|
|
204
|
+
error: unknown,
|
|
205
|
+
fallback = 'Something went wrong. Please try again.',
|
|
206
|
+
): string {
|
|
207
|
+
if (typeof error === 'string') {
|
|
208
|
+
return collapseWhitespace(error) || fallback;
|
|
209
|
+
}
|
|
210
|
+
if (isApiClientErrorLike(error)) {
|
|
211
|
+
return getApiClientErrorMessage(error, fallback);
|
|
212
|
+
}
|
|
213
|
+
if (isOrvalClientErrorLike(error)) {
|
|
214
|
+
return getOrvalClientErrorMessage(error, fallback);
|
|
215
|
+
}
|
|
216
|
+
if (error instanceof Error) {
|
|
217
|
+
return getGenericErrorMessage(error.message, fallback);
|
|
218
|
+
}
|
|
219
|
+
return fallback;
|
|
220
|
+
}
|
|
@@ -5,6 +5,8 @@ import { createContext, useContext, type ReactNode } from 'react';
|
|
|
5
5
|
import type { CapabilityPort } from '../capabilities';
|
|
6
6
|
import type { SystemBus } from '../system-bus';
|
|
7
7
|
|
|
8
|
+
import type { RealtimeSource } from './realtime-port';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* `HostBridge` is the host-agnostic abstraction every frontend biome
|
|
10
12
|
* receives at registration time. It decouples biome code from any
|
|
@@ -113,6 +115,26 @@ export interface HostBridgeRequestContext {
|
|
|
113
115
|
readonly correlationId: string;
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Error-decoding surface — the canonical "turn a thrown value into a
|
|
120
|
+
* user-facing message" decoder, exposed on the bridge so biomes use ONE
|
|
121
|
+
* implementation instead of each re-deriving envelope/status-code copy.
|
|
122
|
+
*
|
|
123
|
+
* The decoder is a PURE function (no host context), so the kernel also ships it
|
|
124
|
+
* directly as {@link getUserFacingErrorMessage} and {@link ErrorCard} defaults
|
|
125
|
+
* to it. It is ALSO hung off the bridge so a host can override it with a
|
|
126
|
+
* shell-specific decoder (extra error codes, i18n) without every biome wiring a
|
|
127
|
+
* prop — the bridge value is the single override point.
|
|
128
|
+
*/
|
|
129
|
+
export interface HostBridgeErrors {
|
|
130
|
+
/**
|
|
131
|
+
* Decode any thrown value into a user-facing message. `fallback` is returned
|
|
132
|
+
* when nothing better can be extracted. Defaults (in the kernel default
|
|
133
|
+
* bridge wiring) to {@link getUserFacingErrorMessage}.
|
|
134
|
+
*/
|
|
135
|
+
getUserFacingErrorMessage(error: unknown, fallback?: string): string;
|
|
136
|
+
}
|
|
137
|
+
|
|
116
138
|
/**
|
|
117
139
|
* Where the topbar's leading back-button takes you. Pages that have a
|
|
118
140
|
* meaningful "parent context" register this; pages without one omit it
|
|
@@ -201,6 +223,28 @@ export interface HostBridge {
|
|
|
201
223
|
* `<CapabilityProvider>` + `useCapability()` (see `../capabilities`).
|
|
202
224
|
*/
|
|
203
225
|
readonly capabilities: CapabilityPort;
|
|
226
|
+
/**
|
|
227
|
+
* Realtime (CloudEvents over SSE) port. The HOST injects a concrete
|
|
228
|
+
* {@link RealtimeSource} backed by its realtime transport
|
|
229
|
+
* (`@xemahq/realtime-client`); biomes reach it through the kernel
|
|
230
|
+
* `useCloudEvent` / `useRealtimeStatus` / `useEventScope` hooks so biome code
|
|
231
|
+
* never imports the SSE client directly — same decoupling as `navigation`.
|
|
232
|
+
*
|
|
233
|
+
* OPTIONAL: a host without a realtime transport simply omits it; the kernel
|
|
234
|
+
* `useCloudEvent`/`useRealtimeStatus`/`useEventScope` hooks fail fast with an
|
|
235
|
+
* actionable error if a biome uses them before the host wires `bridge.realtime`.
|
|
236
|
+
*/
|
|
237
|
+
readonly realtime?: RealtimeSource;
|
|
238
|
+
/**
|
|
239
|
+
* Error-decoding surface — the canonical user-facing error decoder. Defaults
|
|
240
|
+
* (in the kernel default bridge wiring) to {@link getUserFacingErrorMessage};
|
|
241
|
+
* a host MAY override it. Biomes + {@link ErrorCard} read ONE decoder instead
|
|
242
|
+
* of each re-deriving message extraction.
|
|
243
|
+
*
|
|
244
|
+
* OPTIONAL: when omitted, consumers fall back to the kernel default
|
|
245
|
+
* {@link getUserFacingErrorMessage} (which ErrorCard already uses directly).
|
|
246
|
+
*/
|
|
247
|
+
readonly errors?: HostBridgeErrors;
|
|
204
248
|
}
|
|
205
249
|
|
|
206
250
|
export const HostBridgeContext = createContext<HostBridge | null>(null);
|
|
@@ -26,3 +26,7 @@ export * from './host-sources';
|
|
|
26
26
|
export * from './nav';
|
|
27
27
|
export * from './biome-mode';
|
|
28
28
|
export * from './agent-validation';
|
|
29
|
+
export * from './realtime-port';
|
|
30
|
+
export * from './realtime-hooks';
|
|
31
|
+
export * from './errors';
|
|
32
|
+
export * from './response-envelope';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel-side realtime hooks. Biomes call these INSTEAD of importing the host's
|
|
3
|
+
* realtime client (`@xemahq/realtime-client`) directly, so biome code stays
|
|
4
|
+
* decoupled from the SSE transport — the same decoupling `useHostBridge`
|
|
5
|
+
* already provides for navigation / auth / capabilities.
|
|
6
|
+
*
|
|
7
|
+
* Each hook reads the host-injected {@link RealtimeSource} off
|
|
8
|
+
* `bridge.realtime` and delegates straight through. The host owns the transport
|
|
9
|
+
* (connection, token refresh, org header, scope refcounting); the kernel owns
|
|
10
|
+
* only the contract + this thin delegation.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useHostBridge } from './host-bridge';
|
|
14
|
+
import type {
|
|
15
|
+
RealtimeCloudEvent,
|
|
16
|
+
RealtimeConnectionState,
|
|
17
|
+
RealtimeScope,
|
|
18
|
+
RealtimeSource,
|
|
19
|
+
} from './realtime-port';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the host-injected realtime transport, failing fast with an actionable
|
|
23
|
+
* message if the host has not wired `bridge.realtime`. `bridge.realtime` is an
|
|
24
|
+
* optional port (a host without a realtime transport omits it), so the kernel
|
|
25
|
+
* hooks must not silently no-op — a biome that calls them expects live events.
|
|
26
|
+
*/
|
|
27
|
+
function requireRealtime(realtime: RealtimeSource | undefined): RealtimeSource {
|
|
28
|
+
if (!realtime) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'useCloudEvent/useRealtimeStatus/useEventScope require a realtime ' +
|
|
31
|
+
'transport, but the host has not wired `bridge.realtime`. The host ' +
|
|
32
|
+
'must provide a RealtimeSource (e.g. backed by @xemahq/realtime-client) ' +
|
|
33
|
+
'on the HostBridge to use kernel realtime hooks.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return realtime;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to delivered CloudEvents of a given `type` for the lifetime of the
|
|
41
|
+
* calling component. Pass a stable `handler` (e.g. via `useCallback`) — the
|
|
42
|
+
* host re-subscribes when the handler identity changes. Narrow `event.data`
|
|
43
|
+
* inside the handler; the kernel does not parse payloads.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* useCloudEvent('session.lifecycle.changed', (event) => {
|
|
47
|
+
* const data = event.data as { sessionId?: string } | undefined;
|
|
48
|
+
* if (data?.sessionId !== sessionId) return;
|
|
49
|
+
* refetch();
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
export function useCloudEvent(
|
|
53
|
+
eventType: string,
|
|
54
|
+
handler: (event: RealtimeCloudEvent) => void,
|
|
55
|
+
): void {
|
|
56
|
+
const bridge = useHostBridge();
|
|
57
|
+
requireRealtime(bridge.realtime).useCloudEvent(eventType, handler);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Coarse realtime connection state for a status indicator. */
|
|
61
|
+
export function useRealtimeStatus(): RealtimeConnectionState {
|
|
62
|
+
const bridge = useHostBridge();
|
|
63
|
+
return requireRealtime(bridge.realtime).useRealtimeStatus();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Subscribe the connection to a project/session {@link RealtimeScope} while the
|
|
68
|
+
* calling component is mounted. Scopes are reference-counted by the host, so
|
|
69
|
+
* multiple components asking for the same scope share one server subscription.
|
|
70
|
+
*/
|
|
71
|
+
export function useEventScope(scope: RealtimeScope): void {
|
|
72
|
+
const bridge = useHostBridge();
|
|
73
|
+
requireRealtime(bridge.realtime).useEventScope(scope);
|
|
74
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-agnostic realtime (CloudEvents over SSE) port.
|
|
3
|
+
*
|
|
4
|
+
* Frontend biomes need to subscribe to server-pushed CloudEvents (session
|
|
5
|
+
* lifecycle, tool calls, notifications, …). The transport — the unified SSE
|
|
6
|
+
* connection, the Keycloak-minted bearer, the active-org header, cross-tab
|
|
7
|
+
* leader election, server-side scope subscriptions — is HOST-owned (today
|
|
8
|
+
* `@xemahq/realtime-client` wrapped by the host's `RealtimeProvider`). Biomes
|
|
9
|
+
* must NOT import that client or `EventSource` directly, exactly as they must
|
|
10
|
+
* not import `next/navigation` or `sonner`.
|
|
11
|
+
*
|
|
12
|
+
* Instead the host injects a concrete {@link RealtimeSource} when it builds the
|
|
13
|
+
* {@link HostBridge}; it is hung off `bridge.realtime`. Biomes reach it through
|
|
14
|
+
* the {@link useCloudEvent} / {@link useRealtimeStatus} / {@link useEventScope}
|
|
15
|
+
* kernel hooks — the same decoupling pattern as `bridge.navigation` /
|
|
16
|
+
* `bridge.capabilities`.
|
|
17
|
+
*
|
|
18
|
+
* Framework-agnostic: pure interface contracts, no React, no SSE, no fetch.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The subset of the CloudEvents-shape envelope a biome handler receives.
|
|
23
|
+
*
|
|
24
|
+
* Locally typed in the kernel (deliberately NOT imported from
|
|
25
|
+
* `@xemahq/realtime-client` / `@xemahq/events`) so `@xemahq/ui-kernel` stays
|
|
26
|
+
* transport-free and host-agnostic. The host's realtime client emits an object
|
|
27
|
+
* that is structurally assignable to this — value-identical to the client's
|
|
28
|
+
* `DeliveredEvent`, kept here as the kernel's stable contract.
|
|
29
|
+
*/
|
|
30
|
+
export interface RealtimeCloudEvent {
|
|
31
|
+
/** CloudEvents `id` — unique per delivered event, when the server stamps it. */
|
|
32
|
+
id?: string;
|
|
33
|
+
/** CloudEvents `type`, e.g. `session.lifecycle.changed`. The subscription key. */
|
|
34
|
+
type: string;
|
|
35
|
+
/** CloudEvents `source`, e.g. `/services/activity-feed-api` or `/biomes/<id>`. */
|
|
36
|
+
source?: string;
|
|
37
|
+
/** CloudEvents `subject`, e.g. `session/<session-id>`. */
|
|
38
|
+
subject?: string;
|
|
39
|
+
/** CloudEvents `data` payload. Biomes narrow this themselves. */
|
|
40
|
+
data?: unknown;
|
|
41
|
+
/** Org-id header echoed onto every delivered envelope. */
|
|
42
|
+
ehorgid: string;
|
|
43
|
+
/** Project-id header, when the event is project-scoped. */
|
|
44
|
+
ehprojectid?: string;
|
|
45
|
+
/** User-id header, when the event is user-scoped. */
|
|
46
|
+
ehuserid?: string;
|
|
47
|
+
/** Global delivery cursor. */
|
|
48
|
+
ehglobalseq?: string;
|
|
49
|
+
/** Per-org delivery cursor. */
|
|
50
|
+
ehorgseq?: string;
|
|
51
|
+
/** Per-project delivery cursor. */
|
|
52
|
+
ehprojectseq?: string;
|
|
53
|
+
/** Per-session delivery cursor. */
|
|
54
|
+
ehsessionseq?: string;
|
|
55
|
+
/** Frontend-only invalidation hint carried alongside the envelope. */
|
|
56
|
+
__hint?: unknown;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Coarse realtime connection status for status indicators. */
|
|
60
|
+
export type RealtimeStatus =
|
|
61
|
+
| 'idle'
|
|
62
|
+
| 'connecting'
|
|
63
|
+
| 'connected'
|
|
64
|
+
| 'reconnecting'
|
|
65
|
+
| 'closed';
|
|
66
|
+
|
|
67
|
+
/** Coarse connection state surfaced to status UIs. */
|
|
68
|
+
export interface RealtimeConnectionState {
|
|
69
|
+
readonly status: RealtimeStatus;
|
|
70
|
+
/** Latest connectionId returned by the upstream `CONNECTED` frame. */
|
|
71
|
+
readonly connectionId?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A server-side scope a connection can subscribe to so it only receives the
|
|
76
|
+
* events for one project or session, rather than the full org firehose. The
|
|
77
|
+
* host's transport reference-counts identical scopes across mounts.
|
|
78
|
+
*/
|
|
79
|
+
export interface RealtimeScope {
|
|
80
|
+
readonly kind: 'project' | 'session';
|
|
81
|
+
readonly id: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Host-implemented realtime source. The host wires each method against its
|
|
86
|
+
* realtime transport (`@xemahq/realtime-client`'s `RealtimeContext`) and passes
|
|
87
|
+
* it in when building the {@link HostBridge}.
|
|
88
|
+
*
|
|
89
|
+
* Every method is a React hook (it reads transport context / state), so it
|
|
90
|
+
* MUST be called from a biome component's render — the kernel `useCloudEvent` /
|
|
91
|
+
* `useRealtimeStatus` / `useEventScope` hooks delegate straight through.
|
|
92
|
+
*/
|
|
93
|
+
export interface RealtimeSource {
|
|
94
|
+
/**
|
|
95
|
+
* Subscribe `handler` to every delivered CloudEvent whose `type` matches
|
|
96
|
+
* `eventType`, for the lifetime of the calling component. Per-`data` filtering
|
|
97
|
+
* (e.g. by `sessionId`) is the handler's responsibility. Mirrors the host
|
|
98
|
+
* client's `useCloudEvent`.
|
|
99
|
+
*/
|
|
100
|
+
useCloudEvent(eventType: string, handler: (event: RealtimeCloudEvent) => void): void;
|
|
101
|
+
/** Coarse connection state for a status indicator. */
|
|
102
|
+
useRealtimeStatus(): RealtimeConnectionState;
|
|
103
|
+
/**
|
|
104
|
+
* Subscribe the connection's unified SSE to a project/session scope while the
|
|
105
|
+
* calling component is mounted; auto-unsubscribes on unmount. Server-side
|
|
106
|
+
* subscriptions are reference-counted by the host transport.
|
|
107
|
+
*/
|
|
108
|
+
useEventScope(scope: RealtimeScope): void;
|
|
109
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend mirror of the backend `ResponseEnvelopeInterceptor`.
|
|
3
|
+
*
|
|
4
|
+
* Every 2xx body the platform returns is wrapped as `{ data: T }`, or
|
|
5
|
+
* `{ data: T[], pagination }` for a paginated list (single source of truth:
|
|
6
|
+
* `@xemahq/platform-common`'s `ResponseEnvelopeInterceptor`). Orval's
|
|
7
|
+
* `custom-fetch` mutators usually peel that envelope at the HTTP boundary, but
|
|
8
|
+
* spec drift (a controller typed `T[]` that emits `{ data }`), public clients
|
|
9
|
+
* that DON'T peel, and transient partial responses leave a `.data`-wrapped or
|
|
10
|
+
* mis-shaped value where biome code expects a bare value/array — and a raw
|
|
11
|
+
* `.map` on it white-screens the route.
|
|
12
|
+
*
|
|
13
|
+
* These are the ONE shared, pure unwrap helpers — no host context, no fetch.
|
|
14
|
+
* Biomes (and the host shell's own `unwrap-list.ts`) should adopt these instead
|
|
15
|
+
* of re-implementing the guard. This is the shared helper only; adopters are
|
|
16
|
+
* migrated incrementally, not in a blanket rewrite.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resilient list unwrap for DISPLAY surfaces — normalises a React-Query `.data`
|
|
21
|
+
* to an array so the page renders its empty/error state instead of crashing:
|
|
22
|
+
*
|
|
23
|
+
* - already an array → returned as-is
|
|
24
|
+
* - `{ data: T[] }` envelope → `value.data`
|
|
25
|
+
* - anything else → `[]`
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NON-throwing: the query's own `isError`/`isLoading` flags drive
|
|
28
|
+
* the visible error/empty UI; this only guards the render path. For
|
|
29
|
+
* mutation/single-resource flows where an unexpected shape is a real bug, use
|
|
30
|
+
* {@link unwrapData} (fail-fast) instead.
|
|
31
|
+
*/
|
|
32
|
+
export function unwrapList<T>(value: unknown): T[] {
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return value as T[];
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
value !== null &&
|
|
38
|
+
typeof value === 'object' &&
|
|
39
|
+
Array.isArray((value as { data?: unknown }).data)
|
|
40
|
+
) {
|
|
41
|
+
return (value as { data: T[] }).data;
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fail-fast single-resource unwrap. Returns `value.data` when the value is a
|
|
48
|
+
* `{ data }` envelope; returns the value unchanged when it is already the
|
|
49
|
+
* unwrapped resource (an Orval client that peeled the envelope at the boundary).
|
|
50
|
+
*
|
|
51
|
+
* Use this on mutation / single-resource flows where the response SHOULD be a
|
|
52
|
+
* concrete object — unlike {@link unwrapList}, it does not silently coerce, so
|
|
53
|
+
* a genuinely-unexpected shape surfaces as the value rather than being masked.
|
|
54
|
+
*
|
|
55
|
+
* A paginated envelope (`{ data, pagination }`) is returned INTACT — callers
|
|
56
|
+
* that need both fields read them off the returned object; peeling only `data`
|
|
57
|
+
* would drop the pagination cursor.
|
|
58
|
+
*/
|
|
59
|
+
export function unwrapData<T>(value: unknown): T {
|
|
60
|
+
if (
|
|
61
|
+
value !== null &&
|
|
62
|
+
typeof value === 'object' &&
|
|
63
|
+
'data' in (value as Record<string, unknown>) &&
|
|
64
|
+
!('pagination' in (value as Record<string, unknown>))
|
|
65
|
+
) {
|
|
66
|
+
return (value as { data: T }).data;
|
|
67
|
+
}
|
|
68
|
+
return value as T;
|
|
69
|
+
}
|
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
import { AlertCircle, RefreshCw } from 'lucide-react';
|
|
2
2
|
|
|
3
|
+
import { getUserFacingErrorMessage } from '../../lib/biome-host/errors';
|
|
3
4
|
import { Button } from '../primitives/button';
|
|
4
5
|
import { Card, CardContent } from '../primitives/card';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Host-agnostic error formatter. The
|
|
8
|
-
*
|
|
9
|
-
* `formatError` to
|
|
10
|
-
*
|
|
8
|
+
* Host-agnostic error formatter. The default is the canonical, envelope-aware
|
|
9
|
+
* {@link getUserFacingErrorMessage} decoder — biomes get rich error copy with
|
|
10
|
+
* zero wiring. Pass `formatError` only to override with a host-specific decoder
|
|
11
|
+
* (extra codes, i18n); the canonical default already handles Orval envelopes,
|
|
12
|
+
* `ApiClientError`, workflow error codes, and "failed to fetch".
|
|
11
13
|
*/
|
|
12
14
|
export type ErrorMessageFormatter = (error: unknown, fallback: string) => string;
|
|
13
15
|
|
|
14
|
-
const defaultFormatError: ErrorMessageFormatter = (error, fallback) =>
|
|
15
|
-
|
|
16
|
-
return error || fallback;
|
|
17
|
-
}
|
|
18
|
-
if (error instanceof Error && error.message) {
|
|
19
|
-
return error.message;
|
|
20
|
-
}
|
|
21
|
-
return fallback;
|
|
22
|
-
};
|
|
16
|
+
const defaultFormatError: ErrorMessageFormatter = (error, fallback) =>
|
|
17
|
+
getUserFacingErrorMessage(error, fallback);
|
|
23
18
|
|
|
24
19
|
interface ErrorCardProps {
|
|
25
20
|
error: Error | string | unknown;
|