@open-mercato/ui 0.7.1-develop.7150.1.c1941e0c22 → 0.7.1-develop.7152.1.a69e92f9c9
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/.turbo/turbo-build.log +1 -1
- package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js +332 -0
- package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js.map +7 -0
- package/dist/backend/dev/DevRuntimeReporter.js +36 -0
- package/dist/backend/dev/DevRuntimeReporter.js.map +7 -0
- package/package.json +3 -3
- package/src/backend/__tests__/DevRuntimeDiagnosticsBanner.test.tsx +495 -0
- package/src/backend/__tests__/DevRuntimeReporter.test.tsx +145 -0
- package/src/backend/dev/DevRuntimeDiagnosticsBanner.tsx +412 -0
- package/src/backend/dev/DevRuntimeReporter.tsx +46 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronDown, ChevronUp, Database, RefreshCw, RotateCcw, ScrollText, Wrench, X } from "lucide-react";
|
|
5
|
+
import { useOptionalT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
+
import {
|
|
7
|
+
isDevRuntimeBannerEnabled,
|
|
8
|
+
readDevRuntimeLogsUrl,
|
|
9
|
+
readDevRuntimeToken
|
|
10
|
+
} from "@open-mercato/shared/lib/dev-runtime/report";
|
|
11
|
+
import {
|
|
12
|
+
DEV_RUNTIME_ACTIONS_PATH,
|
|
13
|
+
DEV_RUNTIME_LOGS_PATH,
|
|
14
|
+
DEV_RUNTIME_STATUS_PATH,
|
|
15
|
+
DEV_RUNTIME_TOKEN_HEADER
|
|
16
|
+
} from "@open-mercato/shared/lib/dev-runtime/types";
|
|
17
|
+
import { Button } from "../../primitives/button.js";
|
|
18
|
+
import { IconButton } from "../../primitives/icon-button.js";
|
|
19
|
+
import { useConfirmDialog } from "../confirm-dialog/index.js";
|
|
20
|
+
import { apiCall } from "../utils/apiCall.js";
|
|
21
|
+
const ACTION_ICONS = {
|
|
22
|
+
generate: Wrench,
|
|
23
|
+
migrate: Database,
|
|
24
|
+
restart: RotateCcw
|
|
25
|
+
};
|
|
26
|
+
function resolveOfferedActions(issue, { canConfirm }) {
|
|
27
|
+
const actions = [];
|
|
28
|
+
if (issue?.recovery === "generate") actions.push("generate");
|
|
29
|
+
if (issue?.recovery === "migrate" && canConfirm) actions.push("migrate");
|
|
30
|
+
actions.push("restart");
|
|
31
|
+
return actions;
|
|
32
|
+
}
|
|
33
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
34
|
+
const VISIBLE_HEALTH = ["starting", "degraded", "recovering", "unavailable"];
|
|
35
|
+
const HEALTH_TONE = {
|
|
36
|
+
starting: "info",
|
|
37
|
+
ready: "info",
|
|
38
|
+
degraded: "warning",
|
|
39
|
+
recovering: "info",
|
|
40
|
+
unavailable: "error"
|
|
41
|
+
};
|
|
42
|
+
const TONE_CLASSES = {
|
|
43
|
+
info: "border-status-info-border bg-status-info-bg text-status-info-text",
|
|
44
|
+
warning: "border-status-warning-border bg-status-warning-bg text-status-warning-text",
|
|
45
|
+
error: "border-status-error-border bg-status-error-bg text-status-error-text"
|
|
46
|
+
};
|
|
47
|
+
const TONE_ACTION_CLASSES = {
|
|
48
|
+
info: "border-status-info-border bg-status-info-bg text-status-info-text hover:bg-status-info-border hover:text-status-info-text",
|
|
49
|
+
warning: "border-status-warning-border bg-status-warning-bg text-status-warning-text hover:bg-status-warning-border hover:text-status-warning-text",
|
|
50
|
+
error: "border-status-error-border bg-status-error-bg text-status-error-text hover:bg-status-error-border hover:text-status-error-text"
|
|
51
|
+
};
|
|
52
|
+
function dismissalKey(status, issue) {
|
|
53
|
+
return `${status.generation}:${issue?.fingerprint ?? status.health}`;
|
|
54
|
+
}
|
|
55
|
+
function devRuntimeRequestHeaders(token) {
|
|
56
|
+
return {
|
|
57
|
+
[DEV_RUNTIME_TOKEN_HEADER]: token,
|
|
58
|
+
"x-om-unauthorized-redirect": "0",
|
|
59
|
+
"x-om-forbidden-redirect": "0"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function fetchRuntimeStatus(token, signal) {
|
|
63
|
+
const response = await apiCall(DEV_RUNTIME_STATUS_PATH, {
|
|
64
|
+
headers: devRuntimeRequestHeaders(token),
|
|
65
|
+
cache: "no-store",
|
|
66
|
+
signal
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) return null;
|
|
69
|
+
return response.result;
|
|
70
|
+
}
|
|
71
|
+
function useRuntimeStatus(token) {
|
|
72
|
+
const [status, setStatus] = React.useState(null);
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
if (!token) return void 0;
|
|
75
|
+
let cancelled = false;
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const poll = async () => {
|
|
78
|
+
try {
|
|
79
|
+
const next = await fetchRuntimeStatus(token, controller.signal);
|
|
80
|
+
if (!cancelled) setStatus(next);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
void poll();
|
|
85
|
+
const timer = setInterval(() => {
|
|
86
|
+
void poll();
|
|
87
|
+
}, POLL_INTERVAL_MS);
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
controller.abort();
|
|
91
|
+
clearInterval(timer);
|
|
92
|
+
};
|
|
93
|
+
}, [token]);
|
|
94
|
+
return status;
|
|
95
|
+
}
|
|
96
|
+
function reloadPage() {
|
|
97
|
+
if (typeof window === "undefined") return;
|
|
98
|
+
try {
|
|
99
|
+
window.location.reload();
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function DevRuntimeDiagnosticsBanner() {
|
|
104
|
+
const translate = useOptionalT();
|
|
105
|
+
const t = React.useCallback(
|
|
106
|
+
(key, fallback) => translate ? translate(key, fallback) : fallback,
|
|
107
|
+
[translate]
|
|
108
|
+
);
|
|
109
|
+
const [token, setToken] = React.useState(null);
|
|
110
|
+
const [logsUrl, setLogsUrl] = React.useState(null);
|
|
111
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
112
|
+
const [dismissed, setDismissed] = React.useState(null);
|
|
113
|
+
const [pendingAction, setPendingAction] = React.useState(null);
|
|
114
|
+
const [logs, setLogs] = React.useState(null);
|
|
115
|
+
const [logsOpen, setLogsOpen] = React.useState(false);
|
|
116
|
+
const [actionError, setActionError] = React.useState(null);
|
|
117
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
118
|
+
const canConfirm = translate !== void 0;
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (!isDevRuntimeBannerEnabled()) return;
|
|
121
|
+
setToken(readDevRuntimeToken());
|
|
122
|
+
setLogsUrl(readDevRuntimeLogsUrl());
|
|
123
|
+
}, []);
|
|
124
|
+
const status = useRuntimeStatus(token);
|
|
125
|
+
const issue = status?.issueSummary ?? null;
|
|
126
|
+
const currentKey = status ? dismissalKey(status, issue) : null;
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
if (currentKey && dismissed && dismissed !== currentKey) setDismissed(null);
|
|
129
|
+
}, [currentKey, dismissed]);
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
if (status?.health === "ready") setExpanded(false);
|
|
132
|
+
}, [status?.health]);
|
|
133
|
+
const toggleLogs = React.useCallback(async () => {
|
|
134
|
+
if (logsOpen) {
|
|
135
|
+
setLogsOpen(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
setLogsOpen(true);
|
|
139
|
+
if (!token) return;
|
|
140
|
+
try {
|
|
141
|
+
const response = await apiCall(`${DEV_RUNTIME_LOGS_PATH}?cursor=0`, {
|
|
142
|
+
headers: devRuntimeRequestHeaders(token),
|
|
143
|
+
cache: "no-store"
|
|
144
|
+
});
|
|
145
|
+
setLogs(response.ok ? response.result : null);
|
|
146
|
+
} catch {
|
|
147
|
+
setLogs(null);
|
|
148
|
+
}
|
|
149
|
+
}, [logsOpen, token]);
|
|
150
|
+
const runRecoveryAction = React.useCallback(async (action) => {
|
|
151
|
+
if (!token || pendingAction) return;
|
|
152
|
+
if (action === "migrate") {
|
|
153
|
+
const confirmed = await confirm({
|
|
154
|
+
title: t("ui.devRuntime.confirm.migrate.title", "Apply database migrations?"),
|
|
155
|
+
text: t(
|
|
156
|
+
"ui.devRuntime.confirm.migrate.text",
|
|
157
|
+
"This applies pending migrations to your development database. It is not automatically reversible \u2014 rolling back is a separate manual task."
|
|
158
|
+
),
|
|
159
|
+
confirmText: t("ui.devRuntime.actions.migrate", "Run migrations"),
|
|
160
|
+
variant: "destructive"
|
|
161
|
+
});
|
|
162
|
+
if (!confirmed) return;
|
|
163
|
+
}
|
|
164
|
+
setPendingAction(action);
|
|
165
|
+
setActionError(null);
|
|
166
|
+
try {
|
|
167
|
+
const response = await apiCall(`${DEV_RUNTIME_ACTIONS_PATH}/${action}`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: devRuntimeRequestHeaders(token),
|
|
170
|
+
cache: "no-store"
|
|
171
|
+
});
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
setActionError(response.result?.error?.message ?? t("ui.devRuntime.actions.failed", "The recovery action could not be started."));
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
setActionError(t("ui.devRuntime.actions.failed", "The recovery action could not be started."));
|
|
177
|
+
} finally {
|
|
178
|
+
setPendingAction(null);
|
|
179
|
+
}
|
|
180
|
+
}, [token, pendingAction, confirm, t]);
|
|
181
|
+
if (!status || !VISIBLE_HEALTH.includes(status.health)) return null;
|
|
182
|
+
if (currentKey && dismissed === currentKey) return null;
|
|
183
|
+
const tone = HEALTH_TONE[status.health];
|
|
184
|
+
const isBusy = status.recovery?.busy === true;
|
|
185
|
+
const headline = t(`ui.devRuntime.health.${status.health}`, DEFAULT_HEALTH_COPY[status.health]);
|
|
186
|
+
const title = issue?.title ?? t("ui.devRuntime.noIncident", "No incident details available");
|
|
187
|
+
return /* @__PURE__ */ jsxs(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
"data-testid": "dev-runtime-diagnostics-banner",
|
|
191
|
+
"data-health": status.health,
|
|
192
|
+
role: status.health === "unavailable" ? "alert" : "status",
|
|
193
|
+
"aria-live": status.health === "unavailable" ? "assertive" : "polite",
|
|
194
|
+
className: `fixed inset-x-3 bottom-20 z-banner flex flex-col gap-2 rounded-lg border px-4 py-3 text-sm shadow-lg sm:inset-x-auto sm:right-4 sm:max-w-4xl ${TONE_CLASSES[tone]}`,
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
197
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
198
|
+
/* @__PURE__ */ jsxs("p", { className: "font-medium", children: [
|
|
199
|
+
headline,
|
|
200
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " \xB7 " }),
|
|
201
|
+
title
|
|
202
|
+
] }),
|
|
203
|
+
issue?.detail ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 break-words", children: issue.detail }) : null
|
|
204
|
+
] }),
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
IconButton,
|
|
207
|
+
{
|
|
208
|
+
type: "button",
|
|
209
|
+
variant: "ghost",
|
|
210
|
+
size: "sm",
|
|
211
|
+
"aria-label": t("ui.devRuntime.actions.dismiss", "Dismiss"),
|
|
212
|
+
onClick: () => setDismissed(currentKey),
|
|
213
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] }),
|
|
217
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
218
|
+
issue ? /* @__PURE__ */ jsxs(
|
|
219
|
+
Button,
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
variant: "outline",
|
|
223
|
+
size: "sm",
|
|
224
|
+
"aria-expanded": expanded,
|
|
225
|
+
onClick: () => setExpanded((value) => !value),
|
|
226
|
+
className: `whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`,
|
|
227
|
+
children: [
|
|
228
|
+
expanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "mr-1 size-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "mr-1 size-4", "aria-hidden": "true" }),
|
|
229
|
+
expanded ? t("ui.devRuntime.actions.hideDetails", "Hide details") : t("ui.devRuntime.actions.showDetails", "Show details")
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
) : null,
|
|
233
|
+
!isBusy ? /* @__PURE__ */ jsxs(
|
|
234
|
+
Button,
|
|
235
|
+
{
|
|
236
|
+
type: "button",
|
|
237
|
+
variant: "outline",
|
|
238
|
+
size: "sm",
|
|
239
|
+
onClick: reloadPage,
|
|
240
|
+
className: `whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`,
|
|
241
|
+
children: [
|
|
242
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 size-4", "aria-hidden": "true" }),
|
|
243
|
+
t("ui.devRuntime.actions.retry", "Retry")
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
) : null,
|
|
247
|
+
!isBusy && token ? resolveOfferedActions(issue, { canConfirm }).map((action) => {
|
|
248
|
+
const ActionIcon = ACTION_ICONS[action];
|
|
249
|
+
return /* @__PURE__ */ jsxs(
|
|
250
|
+
Button,
|
|
251
|
+
{
|
|
252
|
+
type: "button",
|
|
253
|
+
variant: "outline",
|
|
254
|
+
size: "sm",
|
|
255
|
+
disabled: pendingAction !== null,
|
|
256
|
+
onClick: () => {
|
|
257
|
+
void runRecoveryAction(action);
|
|
258
|
+
},
|
|
259
|
+
className: `whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`,
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ jsx(ActionIcon, { className: "mr-1 size-4", "aria-hidden": "true" }),
|
|
262
|
+
t(`ui.devRuntime.actions.${action}`, DEFAULT_ACTION_COPY[action])
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
action
|
|
266
|
+
);
|
|
267
|
+
}) : null,
|
|
268
|
+
token ? /* @__PURE__ */ jsxs(
|
|
269
|
+
Button,
|
|
270
|
+
{
|
|
271
|
+
type: "button",
|
|
272
|
+
variant: "outline",
|
|
273
|
+
size: "sm",
|
|
274
|
+
"aria-expanded": logsOpen,
|
|
275
|
+
onClick: () => {
|
|
276
|
+
void toggleLogs();
|
|
277
|
+
},
|
|
278
|
+
className: `whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`,
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ jsx(ScrollText, { className: "mr-1 size-4", "aria-hidden": "true" }),
|
|
281
|
+
logsOpen ? t("ui.devRuntime.actions.hideLogs", "Hide logs") : t("ui.devRuntime.actions.viewLogs", "View logs")
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
) : null
|
|
285
|
+
] }),
|
|
286
|
+
actionError ? /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: actionError }) : null,
|
|
287
|
+
expanded && issue ? /* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-1 text-xs sm:grid-cols-2", children: [
|
|
288
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.code", "Error code"), value: issue.code }),
|
|
289
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.source", "Source"), value: issue.source }),
|
|
290
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.occurrences", "Occurrences"), value: String(issue.occurrences) }),
|
|
291
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.generation", "Runtime generation"), value: String(issue.generation) }),
|
|
292
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.firstSeen", "First seen"), value: issue.firstSeenAt }),
|
|
293
|
+
/* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.lastSeen", "Last seen"), value: issue.lastSeenAt }),
|
|
294
|
+
issue.path ? /* @__PURE__ */ jsx(DetailRow, { label: t("ui.devRuntime.details.path", "Path"), value: issue.path }) : null
|
|
295
|
+
] }) : null,
|
|
296
|
+
logsOpen ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-current/20 bg-black/20", children: [
|
|
297
|
+
logs && logs.lines.length > 0 ? /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap break-words p-2 font-mono text-xs leading-relaxed", children: logs.lines.map((line) => `${line.at.slice(11, 19)} ${line.text}`).join("\n") }) : /* @__PURE__ */ jsx("p", { className: "p-2 text-xs", children: t("ui.devRuntime.logs.empty", "No diagnostic lines yet.") }),
|
|
298
|
+
logsUrl ? /* @__PURE__ */ jsxs("p", { className: "border-t border-current/20 px-2 py-1 text-xs opacity-80", children: [
|
|
299
|
+
t("ui.devRuntime.logs.splashHint", "Full startup stream:"),
|
|
300
|
+
" ",
|
|
301
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: logsUrl, target: "_blank", rel: "noreferrer", children: logsUrl })
|
|
302
|
+
] }) : null
|
|
303
|
+
] }) : null,
|
|
304
|
+
canConfirm ? ConfirmDialogElement : null
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
const DEFAULT_ACTION_COPY = {
|
|
310
|
+
generate: "Run generators",
|
|
311
|
+
migrate: "Run migrations",
|
|
312
|
+
restart: "Restart runtime"
|
|
313
|
+
};
|
|
314
|
+
const DEFAULT_HEALTH_COPY = {
|
|
315
|
+
starting: "Runtime starting",
|
|
316
|
+
ready: "Runtime ready",
|
|
317
|
+
degraded: "Runtime degraded",
|
|
318
|
+
recovering: "Runtime recovering",
|
|
319
|
+
unavailable: "Runtime unavailable"
|
|
320
|
+
};
|
|
321
|
+
function DetailRow({ label, value }) {
|
|
322
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
323
|
+
/* @__PURE__ */ jsx("dt", { className: "shrink-0 opacity-80", children: label }),
|
|
324
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words font-mono", children: value })
|
|
325
|
+
] });
|
|
326
|
+
}
|
|
327
|
+
var DevRuntimeDiagnosticsBanner_default = DevRuntimeDiagnosticsBanner;
|
|
328
|
+
export {
|
|
329
|
+
DevRuntimeDiagnosticsBanner,
|
|
330
|
+
DevRuntimeDiagnosticsBanner_default as default
|
|
331
|
+
};
|
|
332
|
+
//# sourceMappingURL=DevRuntimeDiagnosticsBanner.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/backend/dev/DevRuntimeDiagnosticsBanner.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, ChevronUp, Database, RefreshCw, RotateCcw, ScrollText, Wrench, X } from 'lucide-react'\nimport { useOptionalT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n isDevRuntimeBannerEnabled,\n readDevRuntimeLogsUrl,\n readDevRuntimeToken,\n} from '@open-mercato/shared/lib/dev-runtime/report'\nimport {\n DEV_RUNTIME_ACTIONS_PATH,\n DEV_RUNTIME_LOGS_PATH,\n DEV_RUNTIME_STATUS_PATH,\n DEV_RUNTIME_TOKEN_HEADER,\n type RuntimeHealth,\n type RuntimeIssue,\n type DevRuntimeLogSnapshot,\n type RuntimeRecoveryAction,\n type RuntimeStatus,\n} from '@open-mercato/shared/lib/dev-runtime/types'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { useConfirmDialog } from '../confirm-dialog'\nimport { apiCall } from '../utils/apiCall'\n\nconst ACTION_ICONS: Record<RuntimeRecoveryAction, typeof RefreshCw> = {\n generate: Wrench,\n migrate: Database,\n restart: RotateCcw,\n}\n\n// `restart` is always safe to offer; `generate` and `migrate` appear only when\n// the classifier justified them for this incident. `migrate` additionally\n// requires a confirmation surface \u2014 the shared dialog needs the i18n provider,\n// so a provider-less tree gets no irreversible action rather than an\n// unconfirmed one.\nfunction resolveOfferedActions(\n issue: RuntimeIssue | null,\n { canConfirm }: { canConfirm: boolean },\n): RuntimeRecoveryAction[] {\n const actions: RuntimeRecoveryAction[] = []\n if (issue?.recovery === 'generate') actions.push('generate')\n if (issue?.recovery === 'migrate' && canConfirm) actions.push('migrate')\n actions.push('restart')\n return actions\n}\n\nconst POLL_INTERVAL_MS = 2000\n\nconst VISIBLE_HEALTH: RuntimeHealth[] = ['starting', 'degraded', 'recovering', 'unavailable']\n\ntype BannerTone = 'info' | 'warning' | 'error'\n\nconst HEALTH_TONE: Record<RuntimeHealth, BannerTone> = {\n starting: 'info',\n ready: 'info',\n degraded: 'warning',\n recovering: 'info',\n unavailable: 'error',\n}\n\nconst TONE_CLASSES: Record<BannerTone, string> = {\n info: 'border-status-info-border bg-status-info-bg text-status-info-text',\n warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text',\n error: 'border-status-error-border bg-status-error-bg text-status-error-text',\n}\n\nconst TONE_ACTION_CLASSES: Record<BannerTone, string> = {\n info: 'border-status-info-border bg-status-info-bg text-status-info-text hover:bg-status-info-border hover:text-status-info-text',\n warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text hover:bg-status-warning-border hover:text-status-warning-text',\n error: 'border-status-error-border bg-status-error-bg text-status-error-text hover:bg-status-error-border hover:text-status-error-text',\n}\n\nfunction dismissalKey(status: RuntimeStatus, issue: RuntimeIssue | null): string {\n return `${status.generation}:${issue?.fingerprint ?? status.health}`\n}\n\n// The dev bridge answers 403 whenever the per-run token is stale \u2014 routine after\n// a `yarn dev` restart leaves an already-open tab holding the previous run's\n// token \u2014 and 404 once diagnostics are off. Neither is a staff-auth event, so\n// both of `apiFetch`'s redirect hooks are switched off. Without this it throws\n// `ForbiddenError` instead of returning the response, the poll below swallows\n// the throw, and the banner freezes on the dead runtime's incident rather than\n// clearing itself.\nfunction devRuntimeRequestHeaders(token: string): Record<string, string> {\n return {\n [DEV_RUNTIME_TOKEN_HEADER]: token,\n 'x-om-unauthorized-redirect': '0',\n 'x-om-forbidden-redirect': '0',\n }\n}\n\nasync function fetchRuntimeStatus(token: string, signal: AbortSignal): Promise<RuntimeStatus | null> {\n const response = await apiCall<RuntimeStatus>(DEV_RUNTIME_STATUS_PATH, {\n headers: devRuntimeRequestHeaders(token),\n cache: 'no-store',\n signal,\n })\n if (!response.ok) return null\n return response.result\n}\n\nfunction useRuntimeStatus(token: string | null): RuntimeStatus | null {\n const [status, setStatus] = React.useState<RuntimeStatus | null>(null)\n\n React.useEffect(() => {\n if (!token) return undefined\n let cancelled = false\n const controller = new AbortController()\n\n const poll = async () => {\n try {\n const next = await fetchRuntimeStatus(token, controller.signal)\n if (!cancelled) setStatus(next)\n } catch {\n // A momentarily unreachable bridge must never break the page: keep the\n // last known status and try again on the next tick.\n }\n }\n\n void poll()\n const timer = setInterval(() => { void poll() }, POLL_INTERVAL_MS)\n return () => {\n cancelled = true\n controller.abort()\n clearInterval(timer)\n }\n }, [token])\n\n return status\n}\n\nfunction reloadPage(): void {\n if (typeof window === 'undefined') return\n try {\n window.location.reload()\n } catch {\n // Reload is a convenience affordance; ignore hosts that block it.\n }\n}\n\n/**\n * Dev-only, in-app counterpart to the standalone startup splash. It reports the\n * supervisor's runtime state on an already-open page so a post-ready failure is\n * visible without switching to the terminal. It never renders in production and\n * never renders while the runtime is healthy.\n */\nexport function DevRuntimeDiagnosticsBanner() {\n // The banner must render even when a broken tree left the app without its\n // i18n provider, so the translator is optional with inline English fallbacks.\n const translate = useOptionalT()\n const t = React.useCallback(\n (key: string, fallback: string) => (translate ? translate(key, fallback) : fallback),\n [translate],\n )\n const [token, setToken] = React.useState<string | null>(null)\n const [logsUrl, setLogsUrl] = React.useState<string | null>(null)\n const [expanded, setExpanded] = React.useState(false)\n const [dismissed, setDismissed] = React.useState<string | null>(null)\n const [pendingAction, setPendingAction] = React.useState<RuntimeRecoveryAction | null>(null)\n const [logs, setLogs] = React.useState<DevRuntimeLogSnapshot | null>(null)\n const [logsOpen, setLogsOpen] = React.useState(false)\n const [actionError, setActionError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n // ConfirmDialog itself calls `useT`, so it can only be mounted where the\n // provider exists.\n const canConfirm = translate !== undefined\n\n React.useEffect(() => {\n if (!isDevRuntimeBannerEnabled()) return\n setToken(readDevRuntimeToken())\n setLogsUrl(readDevRuntimeLogsUrl())\n }, [])\n\n const status = useRuntimeStatus(token)\n const issue = status?.issueSummary ?? null\n const currentKey = status ? dismissalKey(status, issue) : null\n\n // Dismissal is view-local and scoped to one generation:fingerprint, so a new\n // incident \u2014 or the same one in a new generation \u2014 reappears.\n React.useEffect(() => {\n if (currentKey && dismissed && dismissed !== currentKey) setDismissed(null)\n }, [currentKey, dismissed])\n\n React.useEffect(() => {\n if (status?.health === 'ready') setExpanded(false)\n }, [status?.health])\n\n // Logs are fetched on demand from the app itself, so opening them never\n // navigates away from the page being debugged.\n const toggleLogs = React.useCallback(async () => {\n if (logsOpen) {\n setLogsOpen(false)\n return\n }\n setLogsOpen(true)\n if (!token) return\n try {\n const response = await apiCall<DevRuntimeLogSnapshot>(`${DEV_RUNTIME_LOGS_PATH}?cursor=0`, {\n headers: devRuntimeRequestHeaders(token),\n cache: 'no-store',\n })\n setLogs(response.ok ? response.result : null)\n } catch {\n setLogs(null)\n }\n }, [logsOpen, token])\n\n const runRecoveryAction = React.useCallback(async (action: RuntimeRecoveryAction) => {\n if (!token || pendingAction) return\n // `migrate` writes to the database and cannot be undone automatically, so it\n // always goes through the shared confirmation dialog.\n if (action === 'migrate') {\n const confirmed = await confirm({\n title: t('ui.devRuntime.confirm.migrate.title', 'Apply database migrations?'),\n text: t(\n 'ui.devRuntime.confirm.migrate.text',\n 'This applies pending migrations to your development database. It is not automatically reversible \u2014 rolling back is a separate manual task.',\n ),\n confirmText: t('ui.devRuntime.actions.migrate', 'Run migrations'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n setPendingAction(action)\n setActionError(null)\n try {\n const response = await apiCall<{ error?: { message?: string } }>(`${DEV_RUNTIME_ACTIONS_PATH}/${action}`, {\n method: 'POST',\n headers: devRuntimeRequestHeaders(token),\n cache: 'no-store',\n })\n if (!response.ok) {\n setActionError(response.result?.error?.message ?? t('ui.devRuntime.actions.failed', 'The recovery action could not be started.'))\n }\n } catch {\n setActionError(t('ui.devRuntime.actions.failed', 'The recovery action could not be started.'))\n } finally {\n setPendingAction(null)\n }\n }, [token, pendingAction, confirm, t])\n\n if (!status || !VISIBLE_HEALTH.includes(status.health)) return null\n if (currentKey && dismissed === currentKey) return null\n\n const tone = HEALTH_TONE[status.health]\n const isBusy = status.recovery?.busy === true\n const headline = t(`ui.devRuntime.health.${status.health}`, DEFAULT_HEALTH_COPY[status.health])\n const title = issue?.title ?? t('ui.devRuntime.noIncident', 'No incident details available')\n\n return (\n <div\n data-testid=\"dev-runtime-diagnostics-banner\"\n data-health={status.health}\n role={status.health === 'unavailable' ? 'alert' : 'status'}\n aria-live={status.health === 'unavailable' ? 'assertive' : 'polite'}\n // Floating bottom-right dev overlay, lifted clear of the support-chat\n // launcher that sits in that corner. Third-party launchers ship their own\n // very high z-index, so the banner stacks ABOVE the bubble rather than\n // trying to outrank it. `max-w-4xl` keeps the action row on one line on\n // desktop; it still wraps (never scrolls) once the viewport is narrow.\n className={`fixed inset-x-3 bottom-20 z-banner flex flex-col gap-2 rounded-lg border px-4 py-3 text-sm shadow-lg sm:inset-x-auto sm:right-4 sm:max-w-4xl ${TONE_CLASSES[tone]}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium\">\n {headline}\n <span aria-hidden=\"true\"> \u00B7 </span>\n {title}\n </p>\n {issue?.detail ? <p className=\"mt-0.5 break-words\">{issue.detail}</p> : null}\n </div>\n {/* Dismiss stays pinned to the corner instead of joining the wrapping\n action row, where it used to orphan onto a line of its own. */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.devRuntime.actions.dismiss', 'Dismiss')}\n onClick={() => setDismissed(currentKey)}\n >\n <X className=\"size-4\" aria-hidden=\"true\" />\n </IconButton>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1\">\n {issue ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-expanded={expanded}\n onClick={() => setExpanded((value) => !value)}\n className={`whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`}\n >\n {expanded\n ? <ChevronUp className=\"mr-1 size-4\" aria-hidden=\"true\" />\n : <ChevronDown className=\"mr-1 size-4\" aria-hidden=\"true\" />}\n {expanded\n ? t('ui.devRuntime.actions.hideDetails', 'Hide details')\n : t('ui.devRuntime.actions.showDetails', 'Show details')}\n </Button>\n ) : null}\n {!isBusy ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={reloadPage}\n className={`whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`}\n >\n <RefreshCw className=\"mr-1 size-4\" aria-hidden=\"true\" />\n {t('ui.devRuntime.actions.retry', 'Retry')}\n </Button>\n ) : null}\n {!isBusy && token\n ? resolveOfferedActions(issue, { canConfirm }).map((action) => {\n const ActionIcon = ACTION_ICONS[action]\n return (\n <Button\n key={action}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={pendingAction !== null}\n onClick={() => { void runRecoveryAction(action) }}\n className={`whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`}\n >\n <ActionIcon className=\"mr-1 size-4\" aria-hidden=\"true\" />\n {t(`ui.devRuntime.actions.${action}`, DEFAULT_ACTION_COPY[action])}\n </Button>\n )\n })\n : null}\n {token ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-expanded={logsOpen}\n onClick={() => { void toggleLogs() }}\n className={`whitespace-nowrap ${TONE_ACTION_CLASSES[tone]}`}\n >\n <ScrollText className=\"mr-1 size-4\" aria-hidden=\"true\" />\n {logsOpen\n ? t('ui.devRuntime.actions.hideLogs', 'Hide logs')\n : t('ui.devRuntime.actions.viewLogs', 'View logs')}\n </Button>\n ) : null}\n </div>\n\n {actionError ? <p className=\"text-xs font-medium\">{actionError}</p> : null}\n\n {expanded && issue ? (\n <dl className=\"grid grid-cols-1 gap-x-6 gap-y-1 text-xs sm:grid-cols-2\">\n <DetailRow label={t('ui.devRuntime.details.code', 'Error code')} value={issue.code} />\n <DetailRow label={t('ui.devRuntime.details.source', 'Source')} value={issue.source} />\n <DetailRow label={t('ui.devRuntime.details.occurrences', 'Occurrences')} value={String(issue.occurrences)} />\n <DetailRow label={t('ui.devRuntime.details.generation', 'Runtime generation')} value={String(issue.generation)} />\n <DetailRow label={t('ui.devRuntime.details.firstSeen', 'First seen')} value={issue.firstSeenAt} />\n <DetailRow label={t('ui.devRuntime.details.lastSeen', 'Last seen')} value={issue.lastSeenAt} />\n {issue.path ? <DetailRow label={t('ui.devRuntime.details.path', 'Path')} value={issue.path} /> : null}\n </dl>\n ) : null}\n {logsOpen ? (\n <div className=\"rounded-md border border-current/20 bg-black/20\">\n {logs && logs.lines.length > 0 ? (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap break-words p-2 font-mono text-xs leading-relaxed\">\n {logs.lines.map((line) => `${line.at.slice(11, 19)} ${line.text}`).join('\\n')}\n </pre>\n ) : (\n <p className=\"p-2 text-xs\">{t('ui.devRuntime.logs.empty', 'No diagnostic lines yet.')}</p>\n )}\n {logsUrl ? (\n <p className=\"border-t border-current/20 px-2 py-1 text-xs opacity-80\">\n {t('ui.devRuntime.logs.splashHint', 'Full startup stream:')}{' '}\n <a className=\"underline\" href={logsUrl} target=\"_blank\" rel=\"noreferrer\">{logsUrl}</a>\n </p>\n ) : null}\n </div>\n ) : null}\n\n {canConfirm ? ConfirmDialogElement : null}\n </div>\n )\n}\n\nconst DEFAULT_ACTION_COPY: Record<RuntimeRecoveryAction, string> = {\n generate: 'Run generators',\n migrate: 'Run migrations',\n restart: 'Restart runtime',\n}\n\nconst DEFAULT_HEALTH_COPY: Record<RuntimeHealth, string> = {\n starting: 'Runtime starting',\n ready: 'Runtime ready',\n degraded: 'Runtime degraded',\n recovering: 'Runtime recovering',\n unavailable: 'Runtime unavailable',\n}\n\nfunction DetailRow({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex gap-2\">\n <dt className=\"shrink-0 opacity-80\">{label}</dt>\n <dd className=\"min-w-0 break-words font-mono\">{value}</dd>\n </div>\n )\n}\n\nexport default DevRuntimeDiagnosticsBanner\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { reportDevRuntimeError } from "@open-mercato/shared/lib/dev-runtime/report";
|
|
4
|
+
function isChunkLoadFailure(message) {
|
|
5
|
+
const haystack = message.toLowerCase();
|
|
6
|
+
return haystack.includes("chunkloaderror") || haystack.includes("loading chunk") || haystack.includes("loading css chunk");
|
|
7
|
+
}
|
|
8
|
+
function DevRuntimeReporter() {
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (typeof window === "undefined") return void 0;
|
|
11
|
+
const handleError = (event) => {
|
|
12
|
+
const message = event.message ?? "";
|
|
13
|
+
reportDevRuntimeError({
|
|
14
|
+
kind: isChunkLoadFailure(message) ? "chunk-load-error" : "window-error",
|
|
15
|
+
error: event.error,
|
|
16
|
+
message: message || void 0
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const handleRejection = (event) => {
|
|
20
|
+
reportDevRuntimeError({ kind: "unhandled-rejection", error: event.reason });
|
|
21
|
+
};
|
|
22
|
+
window.addEventListener("error", handleError);
|
|
23
|
+
window.addEventListener("unhandledrejection", handleRejection);
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener("error", handleError);
|
|
26
|
+
window.removeEventListener("unhandledrejection", handleRejection);
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
var DevRuntimeReporter_default = DevRuntimeReporter;
|
|
32
|
+
export {
|
|
33
|
+
DevRuntimeReporter,
|
|
34
|
+
DevRuntimeReporter_default as default
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=DevRuntimeReporter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/backend/dev/DevRuntimeReporter.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { reportDevRuntimeError } from '@open-mercato/shared/lib/dev-runtime/report'\n\nfunction isChunkLoadFailure(message: string): boolean {\n const haystack = message.toLowerCase()\n return haystack.includes('chunkloaderror')\n || haystack.includes('loading chunk')\n || haystack.includes('loading css chunk')\n}\n\n/**\n * Dev-only client island that forwards uncaught browser failures to the local\n * supervisor. It registers bounded listeners only, adds no context provider,\n * and stays silent when the collector token is absent (production, CI, or\n * diagnostics disabled).\n */\nexport function DevRuntimeReporter() {\n React.useEffect(() => {\n if (typeof window === 'undefined') return undefined\n\n const handleError = (event: ErrorEvent) => {\n const message = event.message ?? ''\n reportDevRuntimeError({\n kind: isChunkLoadFailure(message) ? 'chunk-load-error' : 'window-error',\n error: event.error,\n message: message || undefined,\n })\n }\n\n const handleRejection = (event: PromiseRejectionEvent) => {\n reportDevRuntimeError({ kind: 'unhandled-rejection', error: event.reason })\n }\n\n window.addEventListener('error', handleError)\n window.addEventListener('unhandledrejection', handleRejection)\n return () => {\n window.removeEventListener('error', handleError)\n window.removeEventListener('unhandledrejection', handleRejection)\n }\n }, [])\n\n return null\n}\n\nexport default DevRuntimeReporter\n"],
|
|
5
|
+
"mappings": ";AACA,YAAY,WAAW;AACvB,SAAS,6BAA6B;AAEtC,SAAS,mBAAmB,SAA0B;AACpD,QAAM,WAAW,QAAQ,YAAY;AACrC,SAAO,SAAS,SAAS,gBAAgB,KACpC,SAAS,SAAS,eAAe,KACjC,SAAS,SAAS,mBAAmB;AAC5C;AAQO,SAAS,qBAAqB;AACnC,QAAM,UAAU,MAAM;AACpB,QAAI,OAAO,WAAW,YAAa,QAAO;AAE1C,UAAM,cAAc,CAAC,UAAsB;AACzC,YAAM,UAAU,MAAM,WAAW;AACjC,4BAAsB;AAAA,QACpB,MAAM,mBAAmB,OAAO,IAAI,qBAAqB;AAAA,QACzD,OAAO,MAAM;AAAA,QACb,SAAS,WAAW;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,UAAM,kBAAkB,CAAC,UAAiC;AACxD,4BAAsB,EAAE,MAAM,uBAAuB,OAAO,MAAM,OAAO,CAAC;AAAA,IAC5E;AAEA,WAAO,iBAAiB,SAAS,WAAW;AAC5C,WAAO,iBAAiB,sBAAsB,eAAe;AAC7D,WAAO,MAAM;AACX,aAAO,oBAAoB,SAAS,WAAW;AAC/C,aAAO,oBAAoB,sBAAsB,eAAe;AAAA,IAClE;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAEA,IAAO,6BAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7152.1.a69e92f9c9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,14 +155,14 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
158
|
+
"@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
164
|
"@figma/code-connect": "^1.3.4",
|
|
165
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
165
|
+
"@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
|
|
166
166
|
"@testing-library/dom": "^10.4.1",
|
|
167
167
|
"@testing-library/jest-dom": "^7.0.0",
|
|
168
168
|
"@testing-library/react": "^16.3.1",
|