@merittdev/horus-lens 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/browser.cjs +31594 -0
  2. package/dist/browser.cjs.map +1 -0
  3. package/dist/browser.d.cts +125 -0
  4. package/dist/browser.d.ts +125 -0
  5. package/dist/browser.js +24 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/chunk-DU7HNRF4.js +1644 -0
  8. package/dist/chunk-DU7HNRF4.js.map +1 -0
  9. package/dist/chunk-DWOH2ENF.js +17613 -0
  10. package/dist/chunk-DWOH2ENF.js.map +1 -0
  11. package/dist/chunk-PZ5AY32C.js +10 -0
  12. package/dist/chunk-PZ5AY32C.js.map +1 -0
  13. package/dist/chunk-S6S3ZZQA.js +17225 -0
  14. package/dist/chunk-S6S3ZZQA.js.map +1 -0
  15. package/dist/dashboard.cjs +19774 -0
  16. package/dist/dashboard.cjs.map +1 -0
  17. package/dist/dashboard.css +2233 -0
  18. package/dist/dashboard.d.cts +707 -0
  19. package/dist/dashboard.d.ts +707 -0
  20. package/dist/dashboard.js +2237 -0
  21. package/dist/dashboard.js.map +1 -0
  22. package/dist/index.cjs +30040 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d-BA540Fae.d.ts +80 -0
  25. package/dist/index.d-Bzgtl7-B.d.cts +161 -0
  26. package/dist/index.d-Bzgtl7-B.d.ts +161 -0
  27. package/dist/index.d-DOCLVJGS.d.cts +80 -0
  28. package/dist/index.d.cts +906 -0
  29. package/dist/index.d.ts +906 -0
  30. package/dist/index.js +89 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/lens.min.js +770 -0
  33. package/dist/lens.min.js.map +1 -0
  34. package/dist/react.cjs +31683 -0
  35. package/dist/react.cjs.map +1 -0
  36. package/dist/react.d.cts +100 -0
  37. package/dist/react.d.ts +100 -0
  38. package/dist/react.js +108 -0
  39. package/dist/react.js.map +1 -0
  40. package/dist/rrweb-EYUUZIYR.js +30 -0
  41. package/dist/rrweb-EYUUZIYR.js.map +1 -0
  42. package/package.json +68 -0
@@ -0,0 +1,2237 @@
1
+ import "./chunk-PZ5AY32C.js";
2
+
3
+ // ../dashboard/dist/index.js
4
+ import { createContext, useContext } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import { useCallback, useEffect, useState } from "react";
7
+
8
+ // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
9
+ function r(e) {
10
+ var t, f, n = "";
11
+ if ("string" == typeof e || "number" == typeof e) n += e;
12
+ else if ("object" == typeof e) if (Array.isArray(e)) {
13
+ var o = e.length;
14
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
15
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
16
+ return n;
17
+ }
18
+ function clsx() {
19
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
20
+ return n;
21
+ }
22
+ var clsx_default = clsx;
23
+
24
+ // ../dashboard/dist/index.js
25
+ import { jsx as jsx2 } from "react/jsx-runtime";
26
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
27
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
28
+ import { useId, useRef as useRef3, useState as useState4 } from "react";
29
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
30
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
31
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
32
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
33
+ import { useEffect as useEffect5, useState as useState6 } from "react";
34
+ import { useEffect as useEffect4, useState as useState5 } from "react";
35
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
36
+ import { jsx as jsx8 } from "react/jsx-runtime";
37
+ import { useEffect as useEffect6, useState as useState7 } from "react";
38
+ import { jsx as jsx9 } from "react/jsx-runtime";
39
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
40
+ import { useEffect as useEffect7, useState as useState8 } from "react";
41
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
42
+ var LensApiError = class extends Error {
43
+ constructor(status, code, message) {
44
+ super(message);
45
+ this.status = status;
46
+ this.code = code;
47
+ this.name = "LensApiError";
48
+ }
49
+ status;
50
+ code;
51
+ };
52
+ function joinUrl(baseUrl, path) {
53
+ return `${baseUrl.replace(/\/+$/, "")}${path}`;
54
+ }
55
+ function createLensApi(config) {
56
+ async function request(path, init) {
57
+ const token = await config.getToken();
58
+ const project = config.project?.();
59
+ const res = await fetch(joinUrl(config.baseUrl, path), {
60
+ ...init,
61
+ headers: {
62
+ Accept: "application/json",
63
+ ...token ? config.tokenHeader && config.tokenHeader.toLowerCase() !== "authorization" ? { [config.tokenHeader]: token } : { Authorization: `Bearer ${token}` } : {},
64
+ ...project ? { "x-lens-project": project } : {},
65
+ ...init?.headers ?? {}
66
+ }
67
+ });
68
+ if (!res.ok) {
69
+ let code = String(res.status);
70
+ let message = res.statusText || "Request failed";
71
+ try {
72
+ const body = await res.json();
73
+ if (body?.error) code = body.error;
74
+ if (body?.message) message = body.message;
75
+ } catch {
76
+ }
77
+ throw new LensApiError(res.status, code, message);
78
+ }
79
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
80
+ return void 0;
81
+ }
82
+ const text = await res.text();
83
+ return text ? JSON.parse(text) : void 0;
84
+ }
85
+ return {
86
+ async listReports(query) {
87
+ const params = new URLSearchParams();
88
+ if (query?.status) params.set("status", query.status);
89
+ if (query?.q) params.set("q", query.q);
90
+ if (query?.limit != null) params.set("limit", String(query.limit));
91
+ if (query?.cursor) params.set("cursor", query.cursor);
92
+ const qs = params.toString();
93
+ return request(`/v1/reports${qs ? `?${qs}` : ""}`);
94
+ },
95
+ async getReport(id) {
96
+ return request(`/v1/reports/${encodeURIComponent(id)}`);
97
+ },
98
+ async getAssetUrl(id, kind) {
99
+ const { url } = await request(
100
+ `/v1/reports/${encodeURIComponent(id)}/assets/${encodeURIComponent(kind)}`
101
+ );
102
+ return url;
103
+ },
104
+ async getAssetJson(id, kind) {
105
+ return request(`/v1/reports/${encodeURIComponent(id)}/assets/${kind}/raw`);
106
+ },
107
+ async listIntegrations() {
108
+ const { integrations } = await request("/v1/integrations");
109
+ return integrations;
110
+ },
111
+ async connectIntegration(provider, opts) {
112
+ return request(
113
+ `/v1/integrations/${encodeURIComponent(provider)}/connect`,
114
+ {
115
+ method: "POST",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify(opts?.returnUrl != null ? { returnUrl: opts.returnUrl } : {})
118
+ }
119
+ );
120
+ },
121
+ async completeIntegrationDev(provider, account) {
122
+ return request(
123
+ `/v1/integrations/${encodeURIComponent(provider)}/callback`,
124
+ {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify(account != null ? { account } : {})
128
+ }
129
+ );
130
+ },
131
+ async activateIntegration(provider) {
132
+ await request(`/v1/integrations/${encodeURIComponent(provider)}/activate`, {
133
+ method: "POST"
134
+ });
135
+ return this.listIntegrations();
136
+ },
137
+ async disconnectIntegration(provider) {
138
+ await request(`/v1/integrations/${encodeURIComponent(provider)}`, {
139
+ method: "DELETE"
140
+ });
141
+ return this.listIntegrations();
142
+ },
143
+ async updateIntegrationConfig(provider, config2) {
144
+ const res = await request(
145
+ `/v1/integrations/${encodeURIComponent(provider)}/config`,
146
+ {
147
+ method: "PATCH",
148
+ headers: { "Content-Type": "application/json" },
149
+ body: JSON.stringify(config2)
150
+ }
151
+ );
152
+ if (Array.isArray(res)) return res;
153
+ if (res && Array.isArray(res.integrations)) return res.integrations;
154
+ return this.listIntegrations();
155
+ },
156
+ async createProject(body) {
157
+ return request("/v1/projects", {
158
+ method: "POST",
159
+ headers: { "Content-Type": "application/json" },
160
+ body: JSON.stringify(body)
161
+ });
162
+ },
163
+ async getProject() {
164
+ return request("/v1/project");
165
+ },
166
+ async updateProject(body) {
167
+ return request("/v1/project", {
168
+ method: "PATCH",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify(body)
171
+ });
172
+ },
173
+ async setS3Config(body) {
174
+ return request("/v1/project/s3", {
175
+ method: "PUT",
176
+ headers: { "Content-Type": "application/json" },
177
+ body: JSON.stringify(body)
178
+ });
179
+ },
180
+ async clearS3Config() {
181
+ return request("/v1/project/s3", { method: "DELETE" });
182
+ },
183
+ async getStorageStatus() {
184
+ return request("/v1/storage");
185
+ }
186
+ };
187
+ }
188
+ var LensApiContext = createContext(null);
189
+ function LensDashboardProvider(props) {
190
+ const provided = /* @__PURE__ */ jsx(LensApiContext.Provider, { value: props.api, children: props.children });
191
+ if (!props.theme) return provided;
192
+ return /* @__PURE__ */ jsx("div", { className: `lens-db-theme-scope lens-db--${props.theme}`, children: provided });
193
+ }
194
+ function useLensApi() {
195
+ const api = useContext(LensApiContext);
196
+ if (!api) {
197
+ throw new Error(
198
+ "[lens] No LensApi found in context. Wrap your dashboard in <LensDashboardProvider api={createLensApi(...)}>."
199
+ );
200
+ }
201
+ return api;
202
+ }
203
+ function relativeTime(iso, now = Date.now()) {
204
+ if (!iso) return "\u2014";
205
+ const then = new Date(iso).getTime();
206
+ if (Number.isNaN(then)) return "\u2014";
207
+ const diff = now - then;
208
+ const sec = Math.round(diff / 1e3);
209
+ if (sec < 5) return "just now";
210
+ if (sec < 60) return `${sec}s ago`;
211
+ const min = Math.round(sec / 60);
212
+ if (min < 60) return `${min}m ago`;
213
+ const hr = Math.round(min / 60);
214
+ if (hr < 24) return `${hr}h ago`;
215
+ const day = Math.round(hr / 24);
216
+ if (day < 7) return `${day}d ago`;
217
+ const wk = Math.round(day / 7);
218
+ if (wk < 5) return `${wk}w ago`;
219
+ const mo = Math.round(day / 30);
220
+ if (mo < 12) return `${mo}mo ago`;
221
+ const yr = Math.round(day / 365);
222
+ return `${yr}y ago`;
223
+ }
224
+ function formatTimestamp(iso) {
225
+ if (!iso) return "\u2014";
226
+ const d = new Date(iso);
227
+ if (Number.isNaN(d.getTime())) return "\u2014";
228
+ return d.toLocaleString();
229
+ }
230
+ function truncate(value, max = 80) {
231
+ if (value.length <= max) return value;
232
+ return `${value.slice(0, max - 1).trimEnd()}\u2026`;
233
+ }
234
+ function urlPath(raw) {
235
+ if (!raw) return "\u2014";
236
+ try {
237
+ const u = new URL(raw);
238
+ return `${u.pathname}${u.search}` || "/";
239
+ } catch {
240
+ return raw;
241
+ }
242
+ }
243
+ function formatDuration(ms) {
244
+ if (ms == null) return "\u2014";
245
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
246
+ return `${(ms / 1e3).toFixed(2)}s`;
247
+ }
248
+ var LABELS = {
249
+ initiated: "Initiated",
250
+ submitted: "Submitted",
251
+ triaged: "Triaged",
252
+ linked: "Linked",
253
+ resolved: "Resolved"
254
+ };
255
+ function StatusBadge({ status }) {
256
+ return /* @__PURE__ */ jsx2("span", { className: clsx_default("lens-db__badge", `lens-db__badge--${status}`), children: LABELS[status] });
257
+ }
258
+ function base({ size = 16, ...rest }) {
259
+ return {
260
+ width: size,
261
+ height: size,
262
+ viewBox: "0 0 24 24",
263
+ fill: "none",
264
+ stroke: "currentColor",
265
+ strokeWidth: 1.5,
266
+ strokeLinecap: "round",
267
+ strokeLinejoin: "round",
268
+ "aria-hidden": true,
269
+ focusable: false,
270
+ ...rest
271
+ };
272
+ }
273
+ function IconPlay(props) {
274
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M8 5.5v13l11-6.5-11-6.5Z", fill: "currentColor", stroke: "none" }) });
275
+ }
276
+ function IconPause(props) {
277
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
278
+ /* @__PURE__ */ jsx3("rect", { x: "7", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" }),
279
+ /* @__PURE__ */ jsx3("rect", { x: "13.5", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" })
280
+ ] });
281
+ }
282
+ function IconClose(props) {
283
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M18 6 6 18M6 6l12 12" }) });
284
+ }
285
+ function IconChevronLeft(props) {
286
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M15 6l-6 6 6 6" }) });
287
+ }
288
+ function IconChevronRight(props) {
289
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M9 6l6 6-6 6" }) });
290
+ }
291
+ function IconExternal(props) {
292
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
293
+ /* @__PURE__ */ jsx3("path", { d: "M14 5h5v5M19 5l-8 8" }),
294
+ /* @__PURE__ */ jsx3("path", { d: "M18 13.5V18a1.5 1.5 0 0 1-1.5 1.5H6A1.5 1.5 0 0 1 4.5 18V7.5A1.5 1.5 0 0 1 6 6h4.5" })
295
+ ] });
296
+ }
297
+ function IconCopy(props) {
298
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
299
+ /* @__PURE__ */ jsx3("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
300
+ /* @__PURE__ */ jsx3("path", { d: "M5 15V6a1 1 0 0 1 1-1h9" })
301
+ ] });
302
+ }
303
+ function IconCheck(props) {
304
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" }) });
305
+ }
306
+ function IconCheckCircle(props) {
307
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
308
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "9" }),
309
+ /* @__PURE__ */ jsx3("path", { d: "m8.5 12 2.5 2.5 4.5-5" })
310
+ ] });
311
+ }
312
+ function IconAlertTriangle(props) {
313
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
314
+ /* @__PURE__ */ jsx3("path", { d: "M12 4.5 21 19.5H3L12 4.5Z" }),
315
+ /* @__PURE__ */ jsx3("path", { d: "M12 10v4.5" }),
316
+ /* @__PURE__ */ jsx3("path", { d: "M12 17.25h.01" })
317
+ ] });
318
+ }
319
+ function IconInbox(props) {
320
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
321
+ /* @__PURE__ */ jsx3("path", { d: "M4 13h4l1.5 2.5h5L16 13h4" }),
322
+ /* @__PURE__ */ jsx3("path", { d: "M6.5 5h11l3 8v4.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5V13l3-8Z" })
323
+ ] });
324
+ }
325
+ function brand({ size = 16, ...rest }) {
326
+ return {
327
+ width: size,
328
+ height: size,
329
+ viewBox: "0 0 24 24",
330
+ fill: "currentColor",
331
+ "aria-hidden": true,
332
+ focusable: false,
333
+ ...rest
334
+ };
335
+ }
336
+ function IconGitHub(props) {
337
+ return /* @__PURE__ */ jsx3("svg", { ...brand(props), children: /* @__PURE__ */ jsx3("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" }) });
338
+ }
339
+ function IconLinear(props) {
340
+ return /* @__PURE__ */ jsx3("svg", { ...brand(props), children: /* @__PURE__ */ jsx3("path", { d: "M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z" }) });
341
+ }
342
+ function IconJira(props) {
343
+ return /* @__PURE__ */ jsx3("svg", { ...brand(props), children: /* @__PURE__ */ jsx3("path", { d: "M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z" }) });
344
+ }
345
+ var SEARCH_DEBOUNCE_MS = 350;
346
+ function ReportsTable(props) {
347
+ const { onSelect, status, pageSize = 20, className } = props;
348
+ const api = useLensApi();
349
+ const [reports, setReports] = useState([]);
350
+ const [nextCursor, setNextCursor] = useState(null);
351
+ const [loading, setLoading] = useState(true);
352
+ const [error, setError] = useState(void 0);
353
+ const [input, setInput] = useState("");
354
+ const [query, setQuery] = useState("");
355
+ const [pageCursor, setPageCursor] = useState(void 0);
356
+ const [history, setHistory] = useState([]);
357
+ const load = useCallback(
358
+ async (cursor, q) => {
359
+ setLoading(true);
360
+ setError(void 0);
361
+ try {
362
+ const result = await api.listReports({
363
+ status,
364
+ q: q || void 0,
365
+ limit: pageSize,
366
+ cursor
367
+ });
368
+ setReports(result.reports);
369
+ setNextCursor(result.nextCursor);
370
+ } catch (err) {
371
+ setError(err instanceof Error ? err : new Error(String(err)));
372
+ } finally {
373
+ setLoading(false);
374
+ }
375
+ },
376
+ [api, status, pageSize]
377
+ );
378
+ useEffect(() => {
379
+ const timer = setTimeout(() => setQuery(input.trim()), SEARCH_DEBOUNCE_MS);
380
+ return () => clearTimeout(timer);
381
+ }, [input]);
382
+ useEffect(() => {
383
+ setHistory([]);
384
+ setPageCursor(void 0);
385
+ void load(void 0, query);
386
+ }, [query, load]);
387
+ function goNext() {
388
+ if (!nextCursor) return;
389
+ setHistory((h) => [...h, pageCursor]);
390
+ setPageCursor(nextCursor);
391
+ void load(nextCursor, query);
392
+ }
393
+ function goPrev() {
394
+ if (history.length === 0) return;
395
+ const prev = history[history.length - 1];
396
+ setHistory((h) => h.slice(0, -1));
397
+ setPageCursor(prev);
398
+ void load(prev, query);
399
+ }
400
+ const canPrev = history.length > 0;
401
+ const canNext = nextCursor !== null;
402
+ const isOnlyPage = !canPrev && !canNext;
403
+ const contextText = isOnlyPage ? `Showing ${reports.length}` : `${reports.length} on this page`;
404
+ return /* @__PURE__ */ jsxs2("div", { className: clsx_default("lens-db", "lens-db__table-wrap", className), children: [
405
+ /* @__PURE__ */ jsxs2("div", { className: "lens-db__table-toolbar", children: [
406
+ /* @__PURE__ */ jsx4(
407
+ "input",
408
+ {
409
+ type: "search",
410
+ className: "lens-db__input lens-db__table-search",
411
+ value: input,
412
+ placeholder: "Search comment or URL\u2026",
413
+ "aria-label": "Search reports by comment or URL",
414
+ onChange: (e) => setInput(e.target.value),
415
+ onKeyDown: (e) => {
416
+ if (e.key === "Escape") {
417
+ e.preventDefault();
418
+ setInput("");
419
+ }
420
+ }
421
+ }
422
+ ),
423
+ !loading && !error && reports.length > 0 && /* @__PURE__ */ jsx4("span", { className: "lens-db__table-context lens-db__muted", children: contextText })
424
+ ] }),
425
+ /* @__PURE__ */ jsxs2("table", { className: "lens-db__table", children: [
426
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
427
+ /* @__PURE__ */ jsx4("th", { children: "Created" }),
428
+ /* @__PURE__ */ jsx4("th", { children: "Comment" }),
429
+ /* @__PURE__ */ jsx4("th", { children: "Path" }),
430
+ /* @__PURE__ */ jsx4("th", { children: "Status" }),
431
+ /* @__PURE__ */ jsx4("th", { className: "lens-db__num", children: "Errors" }),
432
+ /* @__PURE__ */ jsx4("th", { className: "lens-db__center", children: "Replay" }),
433
+ /* @__PURE__ */ jsx4("th", { children: "Issue" })
434
+ ] }) }),
435
+ /* @__PURE__ */ jsx4("tbody", { children: loading ? /* @__PURE__ */ jsx4(SkeletonRows, { rows: 6 }) : error ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsxs2("td", { colSpan: 7, className: "lens-db__error", children: [
436
+ "Failed to load reports: ",
437
+ error.message,
438
+ " ",
439
+ /* @__PURE__ */ jsx4(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ className: "lens-db__link-btn",
444
+ onClick: () => void load(pageCursor, query),
445
+ children: "Retry"
446
+ }
447
+ )
448
+ ] }) }) : reports.length === 0 ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsxs2("td", { colSpan: 7, className: "lens-db__empty", children: [
449
+ /* @__PURE__ */ jsx4("span", { className: "lens-db__empty-icon", children: /* @__PURE__ */ jsx4(IconInbox, { size: 32 }) }),
450
+ query ? /* @__PURE__ */ jsxs2(Fragment, { children: [
451
+ /* @__PURE__ */ jsx4("div", { className: "lens-db__empty-title", children: "No matching reports" }),
452
+ /* @__PURE__ */ jsxs2("div", { className: "lens-db__empty-hint", children: [
453
+ "No reports match \u201C",
454
+ query,
455
+ "\u201D. Try a different search."
456
+ ] })
457
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
458
+ /* @__PURE__ */ jsx4("div", { className: "lens-db__empty-title", children: "No reports yet" }),
459
+ /* @__PURE__ */ jsxs2("div", { className: "lens-db__empty-hint", children: [
460
+ "Visit your app with ",
461
+ /* @__PURE__ */ jsx4("code", { children: "?lens=true" }),
462
+ " and submit one."
463
+ ] })
464
+ ] })
465
+ ] }) }) : reports.map((r2) => /* @__PURE__ */ jsxs2(
466
+ "tr",
467
+ {
468
+ className: clsx_default("lens-db__row", onSelect && "lens-db__row--clickable"),
469
+ onClick: onSelect ? () => onSelect(r2.id) : void 0,
470
+ tabIndex: onSelect ? 0 : void 0,
471
+ onKeyDown: onSelect ? (e) => {
472
+ if (e.key === "Enter" || e.key === " ") {
473
+ e.preventDefault();
474
+ onSelect(r2.id);
475
+ }
476
+ } : void 0,
477
+ children: [
478
+ /* @__PURE__ */ jsx4("td", { className: "lens-db__muted lens-db__tnum", title: r2.createdAt, children: relativeTime(r2.createdAt) }),
479
+ /* @__PURE__ */ jsx4("td", { children: r2.comment ? /* @__PURE__ */ jsx4("span", { className: "lens-db__cell-comment", title: r2.comment, children: r2.comment }) : /* @__PURE__ */ jsx4("span", { className: "lens-db__muted", children: "\u2014" }) }),
480
+ /* @__PURE__ */ jsx4("td", { className: "lens-db__mono lens-db__muted", title: r2.url, children: urlPath(r2.url) }),
481
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4(StatusBadge, { status: r2.status }) }),
482
+ /* @__PURE__ */ jsx4("td", { className: "lens-db__num", children: r2.errorCount > 0 ? /* @__PURE__ */ jsx4("span", { className: "lens-db__badge lens-db__badge--danger", children: r2.errorCount }) : /* @__PURE__ */ jsx4("span", { className: "lens-db__muted", children: "0" }) }),
483
+ /* @__PURE__ */ jsx4("td", { className: "lens-db__center", children: r2.hasReplay ? /* @__PURE__ */ jsx4("span", { className: "lens-db__play", title: "Session replay captured", "aria-label": "Has replay", children: /* @__PURE__ */ jsx4(IconPlay, { size: 14 }) }) : /* @__PURE__ */ jsx4("span", { className: "lens-db__muted", "aria-label": "No replay", children: "\u2014" }) }),
484
+ /* @__PURE__ */ jsx4("td", { children: r2.externalIssue ? /* @__PURE__ */ jsx4(
485
+ "a",
486
+ {
487
+ className: "lens-db__link",
488
+ href: r2.externalIssue.url,
489
+ target: "_blank",
490
+ rel: "noreferrer",
491
+ onClick: (e) => e.stopPropagation(),
492
+ children: r2.externalIssue.key
493
+ }
494
+ ) : /* @__PURE__ */ jsx4("span", { className: "lens-db__muted", children: "\u2014" }) })
495
+ ]
496
+ },
497
+ r2.id
498
+ )) })
499
+ ] }),
500
+ !error && (canPrev || canNext) && /* @__PURE__ */ jsxs2("div", { className: "lens-db__table-footer lens-db__pager", children: [
501
+ /* @__PURE__ */ jsxs2(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ className: "lens-db__btn lens-db__btn--ghost",
506
+ disabled: !canPrev || loading,
507
+ onClick: goPrev,
508
+ children: [
509
+ /* @__PURE__ */ jsx4(IconChevronLeft, { size: 16 }),
510
+ "Prev"
511
+ ]
512
+ }
513
+ ),
514
+ /* @__PURE__ */ jsxs2(
515
+ "button",
516
+ {
517
+ type: "button",
518
+ className: "lens-db__btn lens-db__btn--ghost",
519
+ disabled: !canNext || loading,
520
+ onClick: goNext,
521
+ children: [
522
+ "Next",
523
+ /* @__PURE__ */ jsx4(IconChevronRight, { size: 16 })
524
+ ]
525
+ }
526
+ )
527
+ ] })
528
+ ] });
529
+ }
530
+ function SkeletonRows({ rows }) {
531
+ return /* @__PURE__ */ jsx4(Fragment, { children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx4("tr", { className: "lens-db__row lens-db__row--skeleton", "aria-hidden": true, children: Array.from({ length: 7 }).map((__, j) => /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4("span", { className: "lens-db__skeleton" }) }, j)) }, i)) });
532
+ }
533
+ function useAsync(fn, deps) {
534
+ const [data, setData] = useState2(void 0);
535
+ const [error, setError] = useState2(void 0);
536
+ const [loading, setLoading] = useState2(true);
537
+ const [nonce, setNonce] = useState2(0);
538
+ const runId = useRef(0);
539
+ const reload = useCallback2(() => setNonce((n) => n + 1), []);
540
+ useEffect2(() => {
541
+ const id = ++runId.current;
542
+ let active = true;
543
+ setLoading(true);
544
+ setError(void 0);
545
+ fn().then((result) => {
546
+ if (active && id === runId.current) {
547
+ setData(result);
548
+ setLoading(false);
549
+ }
550
+ }).catch((err) => {
551
+ if (active && id === runId.current) {
552
+ setError(err instanceof Error ? err : new Error(String(err)));
553
+ setLoading(false);
554
+ }
555
+ });
556
+ return () => {
557
+ active = false;
558
+ };
559
+ }, [...deps, nonce]);
560
+ return { data, error, loading, reload };
561
+ }
562
+ var SPEEDS = [1, 2, 4];
563
+ var FALLBACK_W = 16;
564
+ var FALLBACK_H = 10;
565
+ function formatTime(ms) {
566
+ const total = Math.max(0, Math.floor(ms / 1e3));
567
+ const m = Math.floor(total / 60);
568
+ const s = total % 60;
569
+ return `${m}:${s.toString().padStart(2, "0")}`;
570
+ }
571
+ function readViewport(events) {
572
+ for (const e of events) {
573
+ if (e && typeof e === "object" && e.type === 4) {
574
+ const data = e.data;
575
+ if (data && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
576
+ return { width: data.width, height: data.height };
577
+ }
578
+ }
579
+ }
580
+ return null;
581
+ }
582
+ function ReplayPlayer(props) {
583
+ const { reportId, className } = props;
584
+ const api = useLensApi();
585
+ const frameRef = useRef2(null);
586
+ const replayerRef = useRef2(null);
587
+ const rafRef = useRef2(null);
588
+ const totalRef = useRef2(0);
589
+ const finishedRef = useRef2(false);
590
+ const playingRef = useRef2(false);
591
+ const [status, setStatus] = useState3("loading");
592
+ const [message, setMessage] = useState3(void 0);
593
+ const [playing, setPlaying] = useState3(false);
594
+ const [speed, setSpeed] = useState3(1);
595
+ const [skipInactive, setSkipInactive] = useState3(true);
596
+ const [elapsed, setElapsed] = useState3(0);
597
+ const [total, setTotal] = useState3(0);
598
+ const stopRaf = useCallback3(() => {
599
+ if (rafRef.current != null) {
600
+ cancelAnimationFrame(rafRef.current);
601
+ rafRef.current = null;
602
+ }
603
+ }, []);
604
+ const safeCurrentTime = useCallback3(() => {
605
+ const r2 = replayerRef.current;
606
+ if (!r2) return 0;
607
+ const t = r2.getCurrentTime();
608
+ if (!Number.isFinite(t) || t < 0) return 0;
609
+ return totalRef.current > 0 ? Math.min(t, totalRef.current) : t;
610
+ }, []);
611
+ const tick = useCallback3(() => {
612
+ if (!replayerRef.current) return;
613
+ setElapsed(safeCurrentTime());
614
+ rafRef.current = requestAnimationFrame(tick);
615
+ }, [safeCurrentTime]);
616
+ const startRaf = useCallback3(() => {
617
+ stopRaf();
618
+ rafRef.current = requestAnimationFrame(tick);
619
+ }, [stopRaf, tick]);
620
+ const togglePlay = useCallback3(() => {
621
+ const r2 = replayerRef.current;
622
+ if (!r2) return;
623
+ if (playingRef.current) {
624
+ r2.pause();
625
+ playingRef.current = false;
626
+ setPlaying(false);
627
+ stopRaf();
628
+ } else {
629
+ const from = finishedRef.current ? 0 : safeCurrentTime();
630
+ finishedRef.current = false;
631
+ r2.play(from);
632
+ playingRef.current = true;
633
+ setPlaying(true);
634
+ startRaf();
635
+ }
636
+ }, [safeCurrentTime, startRaf, stopRaf]);
637
+ const seekTo = useCallback3((offsetMs) => {
638
+ const r2 = replayerRef.current;
639
+ if (!r2) return;
640
+ const clamped = totalRef.current > 0 ? Math.max(0, Math.min(offsetMs, totalRef.current)) : Math.max(0, offsetMs);
641
+ finishedRef.current = false;
642
+ if (playingRef.current) {
643
+ r2.play(clamped);
644
+ } else {
645
+ r2.pause(clamped);
646
+ }
647
+ setElapsed(clamped);
648
+ }, []);
649
+ const changeSpeed = useCallback3((next) => {
650
+ setSpeed(next);
651
+ replayerRef.current?.setConfig({ speed: next });
652
+ }, []);
653
+ const toggleSkipInactive = useCallback3(() => {
654
+ setSkipInactive((prev) => {
655
+ const next = !prev;
656
+ replayerRef.current?.setConfig({ skipInactive: next });
657
+ return next;
658
+ });
659
+ }, []);
660
+ const trackRef = useRef2(null);
661
+ const offsetFromPointer = useCallback3((clientX) => {
662
+ const track = trackRef.current;
663
+ if (!track || totalRef.current <= 0) return 0;
664
+ const rect = track.getBoundingClientRect();
665
+ const frac = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
666
+ return Math.max(0, Math.min(1, frac)) * totalRef.current;
667
+ }, []);
668
+ const onTrackPointerDown = useCallback3(
669
+ (e) => {
670
+ if (!replayerRef.current || totalRef.current <= 0) return;
671
+ e.currentTarget.setPointerCapture(e.pointerId);
672
+ seekTo(offsetFromPointer(e.clientX));
673
+ },
674
+ [offsetFromPointer, seekTo]
675
+ );
676
+ const onTrackPointerMove = useCallback3(
677
+ (e) => {
678
+ if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;
679
+ seekTo(offsetFromPointer(e.clientX));
680
+ },
681
+ [offsetFromPointer, seekTo]
682
+ );
683
+ const onTrackKeyDown = useCallback3(
684
+ (e) => {
685
+ const step = totalRef.current * 0.05;
686
+ if (e.key === "ArrowLeft") {
687
+ e.preventDefault();
688
+ seekTo(safeCurrentTime() - step);
689
+ } else if (e.key === "ArrowRight") {
690
+ e.preventDefault();
691
+ seekTo(safeCurrentTime() + step);
692
+ }
693
+ },
694
+ [seekTo]
695
+ );
696
+ useEffect3(() => {
697
+ let cancelled = false;
698
+ let observer = null;
699
+ setStatus("loading");
700
+ setMessage(void 0);
701
+ setPlaying(false);
702
+ playingRef.current = false;
703
+ finishedRef.current = false;
704
+ setElapsed(0);
705
+ setTotal(0);
706
+ totalRef.current = 0;
707
+ async function mount() {
708
+ try {
709
+ const events = await api.getAssetJson(reportId, "replay");
710
+ if (cancelled) return;
711
+ if (!Array.isArray(events) || events.length === 0) {
712
+ setStatus("empty");
713
+ return;
714
+ }
715
+ const mod = await import("./rrweb-EYUUZIYR.js");
716
+ if (cancelled) return;
717
+ const frame = frameRef.current;
718
+ if (!frame) return;
719
+ frame.innerHTML = "";
720
+ const viewport = readViewport(events) ?? { width: FALLBACK_W, height: FALLBACK_H };
721
+ frame.style.aspectRatio = `${viewport.width} / ${viewport.height}`;
722
+ const replayer = new mod.Replayer(events, {
723
+ root: frame,
724
+ mouseTail: false,
725
+ skipInactive: true,
726
+ speed: 1
727
+ });
728
+ if (cancelled) {
729
+ replayer.destroy();
730
+ return;
731
+ }
732
+ replayerRef.current = replayer;
733
+ const meta = replayer.getMetaData();
734
+ totalRef.current = meta.totalTime;
735
+ setTotal(meta.totalTime);
736
+ const baseWidth = replayer.iframe.offsetWidth || viewport.width;
737
+ const applyScale = () => {
738
+ const el = frameRef.current;
739
+ if (!el || baseWidth <= 0) return;
740
+ const scale = el.clientWidth / baseWidth;
741
+ const wrapper = replayer.wrapper;
742
+ wrapper.style.transformOrigin = "top left";
743
+ wrapper.style.transform = `scale(${scale})`;
744
+ };
745
+ applyScale();
746
+ observer = new ResizeObserver(applyScale);
747
+ observer.observe(frame);
748
+ replayer.on(mod.ReplayerEvents.Finish, () => {
749
+ finishedRef.current = true;
750
+ playingRef.current = false;
751
+ setPlaying(false);
752
+ stopRaf();
753
+ setElapsed(totalRef.current);
754
+ });
755
+ setStatus("ready");
756
+ } catch (err) {
757
+ if (cancelled) return;
758
+ setStatus("error");
759
+ setMessage(err instanceof Error ? err.message : String(err));
760
+ }
761
+ }
762
+ void mount();
763
+ return () => {
764
+ cancelled = true;
765
+ stopRaf();
766
+ observer?.disconnect();
767
+ try {
768
+ replayerRef.current?.destroy();
769
+ } catch {
770
+ }
771
+ replayerRef.current = null;
772
+ };
773
+ }, [api, reportId, stopRaf]);
774
+ const pct = total > 0 ? Math.min(100, elapsed / total * 100) : 0;
775
+ return /* @__PURE__ */ jsxs3("div", { className: clsx_default("lens-db", "lens-db__replay", className), children: [
776
+ status === "loading" && /* @__PURE__ */ jsx5("div", { className: "lens-db__replay-state", children: "Loading replay\u2026" }),
777
+ status === "empty" && /* @__PURE__ */ jsx5("div", { className: "lens-db__replay-state lens-db__muted", children: "No replay captured." }),
778
+ status === "error" && /* @__PURE__ */ jsxs3("div", { className: "lens-db__replay-state lens-db__error", children: [
779
+ "No replay captured",
780
+ message ? ` \u2014 ${message}` : "",
781
+ "."
782
+ ] }),
783
+ /* @__PURE__ */ jsxs3("div", { className: "lens-db__rp", "data-visible": status === "ready", children: [
784
+ /* @__PURE__ */ jsx5("div", { ref: frameRef, className: "lens-db__rp-frame" }),
785
+ /* @__PURE__ */ jsxs3("div", { className: "lens-db__rp-bar", children: [
786
+ /* @__PURE__ */ jsx5(
787
+ "button",
788
+ {
789
+ type: "button",
790
+ className: "lens-db__rp-play",
791
+ onClick: togglePlay,
792
+ "aria-label": playing ? "Pause replay" : "Play replay",
793
+ "aria-pressed": playing,
794
+ children: playing ? /* @__PURE__ */ jsx5(IconPause, { size: 16 }) : /* @__PURE__ */ jsx5(IconPlay, { size: 16 })
795
+ }
796
+ ),
797
+ /* @__PURE__ */ jsx5("span", { className: "lens-db__rp-time", children: formatTime(elapsed) }),
798
+ /* @__PURE__ */ jsx5(
799
+ "div",
800
+ {
801
+ ref: trackRef,
802
+ className: "lens-db__rp-track",
803
+ role: "slider",
804
+ tabIndex: 0,
805
+ "aria-label": "Seek",
806
+ "aria-valuemin": 0,
807
+ "aria-valuemax": Math.round(total),
808
+ "aria-valuenow": Math.round(elapsed),
809
+ "aria-valuetext": `${formatTime(elapsed)} of ${formatTime(total)}`,
810
+ onPointerDown: onTrackPointerDown,
811
+ onPointerMove: onTrackPointerMove,
812
+ onKeyDown: onTrackKeyDown,
813
+ children: /* @__PURE__ */ jsx5("div", { className: "lens-db__rp-fill", style: { width: `${pct}%` } })
814
+ }
815
+ ),
816
+ /* @__PURE__ */ jsx5("span", { className: "lens-db__rp-time", children: formatTime(total) }),
817
+ /* @__PURE__ */ jsx5("div", { className: "lens-db__rp-speeds", role: "group", "aria-label": "Playback speed", children: SPEEDS.map((s) => /* @__PURE__ */ jsxs3(
818
+ "button",
819
+ {
820
+ type: "button",
821
+ className: clsx_default("lens-db__rp-speed", speed === s && "lens-db__rp-speed--on"),
822
+ onClick: () => changeSpeed(s),
823
+ "aria-pressed": speed === s,
824
+ children: [
825
+ s,
826
+ "\xD7"
827
+ ]
828
+ },
829
+ s
830
+ )) }),
831
+ /* @__PURE__ */ jsx5(
832
+ "button",
833
+ {
834
+ type: "button",
835
+ className: clsx_default("lens-db__rp-skip", skipInactive && "lens-db__rp-skip--on"),
836
+ onClick: toggleSkipInactive,
837
+ "aria-pressed": skipInactive,
838
+ children: "Skip idle"
839
+ }
840
+ )
841
+ ] })
842
+ ] })
843
+ ] });
844
+ }
845
+ function ReportDetail(props) {
846
+ const { reportId, onClose, className } = props;
847
+ const api = useLensApi();
848
+ const { data: report, error, loading, reload } = useAsync(
849
+ () => api.getReport(reportId),
850
+ [reportId]
851
+ );
852
+ return /* @__PURE__ */ jsxs4("div", { className: clsx_default("lens-db", "lens-db__detail", className), children: [
853
+ loading && /* @__PURE__ */ jsx6("div", { className: "lens-db__detail-loading", children: "Loading report\u2026" }),
854
+ error && /* @__PURE__ */ jsxs4("div", { className: "lens-db__error", children: [
855
+ "Failed to load report: ",
856
+ error.message,
857
+ " ",
858
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "lens-db__link-btn", onClick: reload, children: "Retry" })
859
+ ] }),
860
+ report && /* @__PURE__ */ jsx6(ReportDetailBody, { report, onClose })
861
+ ] });
862
+ }
863
+ function ReportDetailBody({ report, onClose }) {
864
+ const meta = report.metadata;
865
+ const [tab, setTab] = useState4("overview");
866
+ const [replayMounted, setReplayMounted] = useState4(false);
867
+ const uid = useId();
868
+ const tabRefs = useRef3({
869
+ overview: null,
870
+ replay: null,
871
+ logs: null,
872
+ network: null
873
+ });
874
+ const errorCount = meta?.errors.length ?? 0;
875
+ const consoleCount = meta?.consoleTail.length ?? 0;
876
+ const networkCount = meta?.networkTail.length ?? 0;
877
+ const tabs = [
878
+ { id: "overview", label: "Overview" },
879
+ { id: "replay", label: "Replay" },
880
+ { id: "logs", label: "Logs", count: errorCount + consoleCount },
881
+ { id: "network", label: "Network", count: networkCount }
882
+ ];
883
+ function select(id) {
884
+ if (id === "replay") setReplayMounted(true);
885
+ setTab(id);
886
+ }
887
+ function onTabKeyDown(e, index) {
888
+ let next = index;
889
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (index + 1) % tabs.length;
890
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (index - 1 + tabs.length) % tabs.length;
891
+ else if (e.key === "Home") next = 0;
892
+ else if (e.key === "End") next = tabs.length - 1;
893
+ else return;
894
+ e.preventDefault();
895
+ const nextTab = tabs[next]?.id;
896
+ if (!nextTab) return;
897
+ select(nextTab);
898
+ tabRefs.current[nextTab]?.focus();
899
+ }
900
+ const tabId = (id) => `${uid}-tab-${id}`;
901
+ const panelId = (id) => `${uid}-panel-${id}`;
902
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
903
+ /* @__PURE__ */ jsxs4("header", { className: "lens-db__detail-header", children: [
904
+ /* @__PURE__ */ jsxs4("div", { className: "lens-db__detail-header-main", children: [
905
+ /* @__PURE__ */ jsx6(StatusBadge, { status: report.status }),
906
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__detail-time", title: formatTimestamp(report.createdAt), children: relativeTime(report.createdAt) }),
907
+ report.externalIssue && /* @__PURE__ */ jsxs4(
908
+ "a",
909
+ {
910
+ className: "lens-db__link lens-db__link-inline",
911
+ href: report.externalIssue.url,
912
+ target: "_blank",
913
+ rel: "noreferrer",
914
+ children: [
915
+ report.externalIssue.provider,
916
+ " \xB7 ",
917
+ report.externalIssue.key,
918
+ /* @__PURE__ */ jsx6(IconExternal, { size: 13 })
919
+ ]
920
+ }
921
+ )
922
+ ] }),
923
+ onClose && /* @__PURE__ */ jsx6("button", { type: "button", className: "lens-db__icon-btn", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx6(IconClose, {}) })
924
+ ] }),
925
+ /* @__PURE__ */ jsx6("div", { className: "lens-db__tabs", role: "tablist", "aria-label": "Report sections", children: tabs.map((t, i) => {
926
+ const active = t.id === tab;
927
+ return /* @__PURE__ */ jsxs4(
928
+ "button",
929
+ {
930
+ type: "button",
931
+ role: "tab",
932
+ id: tabId(t.id),
933
+ ref: (el) => {
934
+ tabRefs.current[t.id] = el;
935
+ },
936
+ "aria-selected": active,
937
+ "aria-controls": panelId(t.id),
938
+ tabIndex: active ? 0 : -1,
939
+ className: clsx_default("lens-db__tab", active && "lens-db__tab--active"),
940
+ onClick: () => select(t.id),
941
+ onKeyDown: (e) => onTabKeyDown(e, i),
942
+ children: [
943
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__tab-label", children: t.label }),
944
+ t.count != null && t.count > 0 && /* @__PURE__ */ jsx6("span", { className: "lens-db__tab-count", children: t.count })
945
+ ]
946
+ },
947
+ t.id
948
+ );
949
+ }) }),
950
+ /* @__PURE__ */ jsxs4("div", { className: "lens-db__tabpanels", children: [
951
+ tab === "overview" && /* @__PURE__ */ jsx6("div", { role: "tabpanel", id: panelId("overview"), "aria-labelledby": tabId("overview"), tabIndex: 0, children: /* @__PURE__ */ jsx6(OverviewPanel, { report, meta }) }),
952
+ tab === "logs" && /* @__PURE__ */ jsx6("div", { role: "tabpanel", id: panelId("logs"), "aria-labelledby": tabId("logs"), tabIndex: 0, children: /* @__PURE__ */ jsx6(LogsPanel, { errors: meta?.errors ?? [], consoleEntries: meta?.consoleTail ?? [] }) }),
953
+ tab === "network" && /* @__PURE__ */ jsx6("div", { role: "tabpanel", id: panelId("network"), "aria-labelledby": tabId("network"), tabIndex: 0, children: /* @__PURE__ */ jsx6(NetworkPanel, { entries: meta?.networkTail ?? [] }) }),
954
+ replayMounted && /* @__PURE__ */ jsx6(
955
+ "div",
956
+ {
957
+ role: "tabpanel",
958
+ id: panelId("replay"),
959
+ "aria-labelledby": tabId("replay"),
960
+ tabIndex: 0,
961
+ hidden: tab !== "replay",
962
+ children: /* @__PURE__ */ jsx6("div", { className: "lens-db__panel", children: /* @__PURE__ */ jsx6(ReplayPlayer, { reportId: report.id }) })
963
+ }
964
+ )
965
+ ] })
966
+ ] });
967
+ }
968
+ function OverviewPanel({ report, meta }) {
969
+ const hasScreenshot = report.assets.some((a) => a.kind === "screenshot");
970
+ const attachments = report.assets.filter((a) => a.kind !== "screenshot");
971
+ if (!meta) {
972
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
973
+ hasScreenshot && /* @__PURE__ */ jsx6(Section, { title: "Screenshot", children: /* @__PURE__ */ jsx6(Screenshot, { reportId: report.id }) }),
974
+ /* @__PURE__ */ jsx6(Section, { title: "Report", children: /* @__PURE__ */ jsx6("p", { className: "lens-db__muted", children: "This report has no metadata yet (still initiating)." }) })
975
+ ] });
976
+ }
977
+ const hasTarget = Boolean(meta.target && (meta.target.domPath || meta.target.reactComponents?.length));
978
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
979
+ meta.comment && /* @__PURE__ */ jsx6(Section, { title: "Comment", children: /* @__PURE__ */ jsx6("p", { className: "lens-db__comment", children: meta.comment }) }),
980
+ hasScreenshot && /* @__PURE__ */ jsx6(Section, { title: "Screenshot", children: /* @__PURE__ */ jsx6(Screenshot, { reportId: report.id }) }),
981
+ /* @__PURE__ */ jsx6(Section, { title: "Environment", children: /* @__PURE__ */ jsx6(EnvironmentGrid, { env: meta.env, app: meta.app }) }),
982
+ hasTarget && meta.target && /* @__PURE__ */ jsxs4(Section, { title: "Target element", children: [
983
+ meta.target.domPath && /* @__PURE__ */ jsx6("code", { className: "lens-db__dompath", children: meta.target.domPath }),
984
+ meta.target.reactComponents && meta.target.reactComponents.length > 0 && /* @__PURE__ */ jsx6("div", { className: "lens-db__chips", children: meta.target.reactComponents.map((c, i) => /* @__PURE__ */ jsx6("span", { className: "lens-db__chip", children: c }, `${c}-${i}`)) })
985
+ ] }),
986
+ attachments.length > 0 && /* @__PURE__ */ jsx6(Section, { title: "Attachments", children: /* @__PURE__ */ jsx6(Attachments, { reportId: report.id, assets: attachments }) })
987
+ ] });
988
+ }
989
+ function Screenshot({ reportId }) {
990
+ const api = useLensApi();
991
+ const { data: url, error, loading } = useAsync(
992
+ () => api.getAssetUrl(reportId, "screenshot"),
993
+ [reportId]
994
+ );
995
+ if (loading) {
996
+ return /* @__PURE__ */ jsx6("div", { className: "lens-db__shot lens-db__shot--loading", "aria-hidden": true });
997
+ }
998
+ if (error || !url) return null;
999
+ return /* @__PURE__ */ jsx6(
1000
+ "a",
1001
+ {
1002
+ className: "lens-db__shot",
1003
+ href: url,
1004
+ target: "_blank",
1005
+ rel: "noreferrer",
1006
+ title: "Open full screenshot in a new tab",
1007
+ children: /* @__PURE__ */ jsx6("img", { className: "lens-db__shot-img", src: url, alt: "Captured screenshot", loading: "lazy" })
1008
+ }
1009
+ );
1010
+ }
1011
+ function dedupeErrors(errors) {
1012
+ const byMessage = /* @__PURE__ */ new Map();
1013
+ const order = [];
1014
+ for (const entry of errors) {
1015
+ const existing = byMessage.get(entry.message);
1016
+ if (existing) {
1017
+ existing.count += 1;
1018
+ } else {
1019
+ const group = { entry, count: 1 };
1020
+ byMessage.set(entry.message, group);
1021
+ order.push(group);
1022
+ }
1023
+ }
1024
+ return order;
1025
+ }
1026
+ function LogsPanel({ errors, consoleEntries }) {
1027
+ const groups = dedupeErrors(errors);
1028
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1029
+ /* @__PURE__ */ jsx6(Section, { title: `Errors${errors.length ? ` (${groups.length})` : ""}`, children: groups.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx6("div", { className: "lens-db__scroll", children: /* @__PURE__ */ jsx6("ul", { className: "lens-db__errlog", children: groups.map((g, i) => /* @__PURE__ */ jsx6(ErrorRow, { group: g }, i)) }) }) }),
1030
+ /* @__PURE__ */ jsx6(Section, { title: "Console", children: consoleEntries.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx6("div", { className: "lens-db__scroll", children: /* @__PURE__ */ jsx6(ConsoleTail, { entries: consoleEntries }) }) })
1031
+ ] });
1032
+ }
1033
+ function ErrorRow({ group }) {
1034
+ const { entry, count } = group;
1035
+ const firstLine = entry.message.split("\n")[0] ?? entry.message;
1036
+ return /* @__PURE__ */ jsx6("li", { className: "lens-db__errlog-item", children: /* @__PURE__ */ jsxs4("details", { className: "lens-db__errlog-details", children: [
1037
+ /* @__PURE__ */ jsxs4("summary", { className: "lens-db__errlog-summary", children: [
1038
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__errlog-msg", children: truncate(firstLine, 140) }),
1039
+ count > 1 && /* @__PURE__ */ jsxs4("span", { className: "lens-db__count-badge", children: [
1040
+ "\xD7",
1041
+ count
1042
+ ] }),
1043
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__errlog-source", children: entry.source })
1044
+ ] }),
1045
+ /* @__PURE__ */ jsxs4("div", { className: "lens-db__errlog-body", children: [
1046
+ entry.message !== firstLine && /* @__PURE__ */ jsx6("pre", { className: "lens-db__stack", children: entry.message }),
1047
+ entry.stack && /* @__PURE__ */ jsx6("pre", { className: "lens-db__stack", children: entry.stack })
1048
+ ] })
1049
+ ] }) });
1050
+ }
1051
+ function ConsoleTail({ entries }) {
1052
+ return /* @__PURE__ */ jsx6("div", { className: "lens-db__console", children: entries.map((e, i) => /* @__PURE__ */ jsxs4("div", { className: clsx_default("lens-db__console-line", `lens-db__console-line--${e.level}`), children: [
1053
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__console-level", children: e.level }),
1054
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__console-args", children: e.args.join(" ") })
1055
+ ] }, i)) });
1056
+ }
1057
+ function NetworkPanel({ entries }) {
1058
+ return /* @__PURE__ */ jsx6("div", { className: "lens-db__panel", children: entries.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx6(NetworkTail, { entries }) });
1059
+ }
1060
+ function NetworkTail({ entries }) {
1061
+ return /* @__PURE__ */ jsxs4("table", { className: "lens-db__nettable", children: [
1062
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs4("tr", { children: [
1063
+ /* @__PURE__ */ jsx6("th", { children: "Method" }),
1064
+ /* @__PURE__ */ jsx6("th", { children: "Path" }),
1065
+ /* @__PURE__ */ jsx6("th", { className: "lens-db__num", children: "Status" }),
1066
+ /* @__PURE__ */ jsx6("th", { className: "lens-db__num", children: "Duration" })
1067
+ ] }) }),
1068
+ /* @__PURE__ */ jsx6("tbody", { children: entries.map((e, i) => {
1069
+ const bad = e.error != null || e.ok === false || e.status != null && (e.status < 200 || e.status >= 300);
1070
+ return /* @__PURE__ */ jsxs4("tr", { className: clsx_default(bad && "lens-db__netrow--bad"), children: [
1071
+ /* @__PURE__ */ jsx6("td", { className: "lens-db__mono", children: e.method }),
1072
+ /* @__PURE__ */ jsx6("td", { className: "lens-db__mono lens-db__muted", title: e.url, children: urlPath(e.url) }),
1073
+ /* @__PURE__ */ jsx6("td", { className: "lens-db__num lens-db__mono lens-db__net-status", children: e.error ? "ERR" : e.status ?? "\u2014" }),
1074
+ /* @__PURE__ */ jsx6("td", { className: "lens-db__num lens-db__muted", children: formatDuration(e.durationMs) })
1075
+ ] }, i);
1076
+ }) })
1077
+ ] });
1078
+ }
1079
+ function Section({ title, children }) {
1080
+ return /* @__PURE__ */ jsxs4("section", { className: "lens-db__section", children: [
1081
+ /* @__PURE__ */ jsx6("h3", { className: "lens-db__section-title", children: title }),
1082
+ /* @__PURE__ */ jsx6("div", { className: "lens-db__section-body", children })
1083
+ ] });
1084
+ }
1085
+ function EnvironmentGrid({ env, app }) {
1086
+ const rows = [
1087
+ ["Browser", env.browser],
1088
+ ["OS", env.os],
1089
+ ["Viewport", `${env.viewport.width}\xD7${env.viewport.height}`],
1090
+ ["Language", env.language],
1091
+ ["Timezone", env.timezone],
1092
+ ["URL", env.url],
1093
+ ["Route", env.route],
1094
+ ["Release", app?.release],
1095
+ ["Git SHA", app?.gitSha]
1096
+ ];
1097
+ const monoLabels = /* @__PURE__ */ new Set(["URL", "Route", "Release", "Git SHA"]);
1098
+ return /* @__PURE__ */ jsx6("dl", { className: "lens-db__grid", children: rows.filter(([, v]) => v != null && v !== "").map(([label, value]) => /* @__PURE__ */ jsxs4("div", { className: "lens-db__grid-row", children: [
1099
+ /* @__PURE__ */ jsx6("dt", { className: "lens-db__grid-label", children: label }),
1100
+ /* @__PURE__ */ jsx6("dd", { className: clsx_default("lens-db__grid-value", monoLabels.has(label) && "lens-db__mono"), children: value })
1101
+ ] }, label)) });
1102
+ }
1103
+ function Attachments({ reportId, assets }) {
1104
+ const api = useLensApi();
1105
+ const [busy, setBusy] = useState4(null);
1106
+ const [err, setErr] = useState4(void 0);
1107
+ async function open(kind) {
1108
+ setBusy(kind);
1109
+ setErr(void 0);
1110
+ try {
1111
+ const url = await api.getAssetUrl(reportId, kind);
1112
+ window.open(url, "_blank", "noopener,noreferrer");
1113
+ } catch (e) {
1114
+ setErr(e instanceof Error ? e.message : String(e));
1115
+ } finally {
1116
+ setBusy(null);
1117
+ }
1118
+ }
1119
+ return /* @__PURE__ */ jsxs4("div", { className: "lens-db__attachments", children: [
1120
+ err && /* @__PURE__ */ jsx6("div", { className: "lens-db__error", children: err }),
1121
+ /* @__PURE__ */ jsx6("ul", { className: "lens-db__attachment-list", children: assets.map((a) => /* @__PURE__ */ jsxs4("li", { className: "lens-db__attachment", children: [
1122
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__attachment-kind", children: a.kind }),
1123
+ /* @__PURE__ */ jsx6("span", { className: "lens-db__muted lens-db__attachment-type", children: a.contentType }),
1124
+ a.byteSize != null && /* @__PURE__ */ jsx6("span", { className: "lens-db__muted lens-db__attachment-size", children: formatBytes(a.byteSize) }),
1125
+ /* @__PURE__ */ jsx6(
1126
+ "button",
1127
+ {
1128
+ type: "button",
1129
+ className: "lens-db__link-btn",
1130
+ disabled: busy === a.kind,
1131
+ onClick: () => void open(a.kind),
1132
+ children: busy === a.kind ? "Opening\u2026" : "Download"
1133
+ }
1134
+ )
1135
+ ] }, a.key)) })
1136
+ ] });
1137
+ }
1138
+ function formatBytes(bytes) {
1139
+ if (bytes < 1024) return `${bytes} B`;
1140
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1141
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1142
+ }
1143
+ var PROVIDER_META = {
1144
+ linear: { name: "Linear", Icon: IconLinear },
1145
+ jira: { name: "Jira", Icon: IconJira },
1146
+ github: { name: "GitHub", Icon: IconGitHub }
1147
+ };
1148
+ var PROVIDER_ORDER = ["linear", "jira", "github"];
1149
+ var CONFIG_FIELD = {
1150
+ github: {
1151
+ key: "repo",
1152
+ label: "Repository",
1153
+ placeholder: "owner/repo",
1154
+ requiredHint: "Set a repository to enable issue creation"
1155
+ },
1156
+ jira: {
1157
+ key: "projectKey",
1158
+ label: "Project key",
1159
+ placeholder: "BUG",
1160
+ requiredHint: "Set a project key to enable issue creation"
1161
+ },
1162
+ linear: {
1163
+ key: "teamKey",
1164
+ label: "Team",
1165
+ placeholder: "First team (default)"
1166
+ }
1167
+ };
1168
+ function IntegrationList(props) {
1169
+ const {
1170
+ integrations,
1171
+ busy,
1172
+ title,
1173
+ description,
1174
+ flash,
1175
+ onFlashDismiss,
1176
+ loading,
1177
+ loadError,
1178
+ onRetry,
1179
+ onConnect,
1180
+ onActivate,
1181
+ onDisconnect,
1182
+ onCompleteDev,
1183
+ onSaveConfig,
1184
+ className
1185
+ } = props;
1186
+ useEffect4(() => {
1187
+ if (!flash || !onFlashDismiss) return;
1188
+ const t = setTimeout(onFlashDismiss, 4e3);
1189
+ return () => clearTimeout(t);
1190
+ }, [flash, onFlashDismiss]);
1191
+ const byProvider = /* @__PURE__ */ new Map();
1192
+ for (const it of integrations) byProvider.set(it.provider, it);
1193
+ const hasHeader = Boolean(title || description);
1194
+ return /* @__PURE__ */ jsxs5("div", { className: clsx_default("lens-db__intlist", className), children: [
1195
+ hasHeader && /* @__PURE__ */ jsxs5("div", { className: "lens-db__intlist-head", children: [
1196
+ title && /* @__PURE__ */ jsx7("div", { className: "lens-db__intlist-title", children: title }),
1197
+ description && /* @__PURE__ */ jsx7("div", { className: "lens-db__intlist-desc lens-db__muted", children: description })
1198
+ ] }),
1199
+ flash && /* @__PURE__ */ jsxs5(
1200
+ "div",
1201
+ {
1202
+ className: clsx_default(
1203
+ "lens-db__intlist-banner",
1204
+ flash.kind === "ok" ? "lens-db__intlist-banner--ok" : "lens-db__intlist-banner--error"
1205
+ ),
1206
+ role: "status",
1207
+ children: [
1208
+ flash.kind === "ok" ? /* @__PURE__ */ jsx7(IconCheckCircle, { size: 16 }) : /* @__PURE__ */ jsx7(IconAlertTriangle, { size: 16 }),
1209
+ /* @__PURE__ */ jsx7("span", { children: flash.message })
1210
+ ]
1211
+ }
1212
+ ),
1213
+ loading ? /* @__PURE__ */ jsx7("div", { className: "lens-db__intlist-rows", "aria-busy": "true", children: PROVIDER_ORDER.map((p) => /* @__PURE__ */ jsxs5("div", { className: "lens-db__introw lens-db__introw--skeleton", children: [
1214
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__skeleton lens-db__intmark-skeleton" }),
1215
+ /* @__PURE__ */ jsxs5("span", { className: "lens-db__intbody", children: [
1216
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__skeleton", style: { width: "35%" } }),
1217
+ /* @__PURE__ */ jsx7(
1218
+ "span",
1219
+ {
1220
+ className: "lens-db__skeleton",
1221
+ style: { width: "55%", marginTop: "7px", height: "10px" }
1222
+ }
1223
+ )
1224
+ ] })
1225
+ ] }, p)) }) : loadError ? /* @__PURE__ */ jsxs5("div", { className: "lens-db__intlist-load-error", children: [
1226
+ "Failed to load integrations: ",
1227
+ loadError,
1228
+ onRetry && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1229
+ " ",
1230
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "lens-db__link-btn", onClick: onRetry, children: "Retry" })
1231
+ ] })
1232
+ ] }) : /* @__PURE__ */ jsx7(
1233
+ "div",
1234
+ {
1235
+ className: "lens-db__intlist-rows",
1236
+ role: "radiogroup",
1237
+ "aria-label": "Issue destination",
1238
+ children: PROVIDER_ORDER.map((provider) => {
1239
+ const meta = PROVIDER_META[provider];
1240
+ const Icon = meta.Icon;
1241
+ const integration = byProvider.get(provider);
1242
+ const status = integration?.status ?? "disconnected";
1243
+ const active = integration?.active ?? false;
1244
+ const account = integration?.account ?? null;
1245
+ const isBusy = busy === provider;
1246
+ const isConnected = status === "connected";
1247
+ const statusLine = status === "connected" ? account ? `Connected as ${account}` : "Connected" : status === "pending" ? "Waiting for authorization\u2026" : "Not connected";
1248
+ return /* @__PURE__ */ jsxs5(
1249
+ "div",
1250
+ {
1251
+ className: clsx_default("lens-db__introw", active && "lens-db__introw--active"),
1252
+ children: [
1253
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__intmark", "aria-hidden": true, children: /* @__PURE__ */ jsx7(Icon, { size: 20 }) }),
1254
+ /* @__PURE__ */ jsxs5("span", { className: "lens-db__intbody", children: [
1255
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__intname", children: meta.name }),
1256
+ /* @__PURE__ */ jsxs5("span", { className: "lens-db__intstatus", children: [
1257
+ /* @__PURE__ */ jsx7(
1258
+ "span",
1259
+ {
1260
+ className: clsx_default(
1261
+ "lens-db__intdot",
1262
+ status === "connected" ? "lens-db__intdot--on" : status === "pending" ? "lens-db__intdot--pending" : "lens-db__intdot--off"
1263
+ ),
1264
+ "aria-hidden": true
1265
+ }
1266
+ ),
1267
+ /* @__PURE__ */ jsx7("span", { children: statusLine })
1268
+ ] })
1269
+ ] }),
1270
+ /* @__PURE__ */ jsxs5("span", { className: "lens-db__introw-right", children: [
1271
+ isConnected && /* @__PURE__ */ jsxs5(
1272
+ "button",
1273
+ {
1274
+ type: "button",
1275
+ role: "radio",
1276
+ "aria-checked": active,
1277
+ "aria-label": active ? `${meta.name} is the issue destination` : `Make ${meta.name} the issue destination`,
1278
+ className: clsx_default(
1279
+ "lens-db__intradio",
1280
+ active && "lens-db__intradio--active"
1281
+ ),
1282
+ disabled: isBusy || active,
1283
+ onClick: () => !active && onActivate(provider),
1284
+ children: [
1285
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__intradio-dot", children: active && /* @__PURE__ */ jsx7("span", { className: "lens-db__intradio-fill" }) }),
1286
+ "Destination"
1287
+ ]
1288
+ }
1289
+ ),
1290
+ status === "pending" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1291
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__intwait", children: "waiting for authorization\u2026" }),
1292
+ /* @__PURE__ */ jsx7(
1293
+ "button",
1294
+ {
1295
+ type: "button",
1296
+ className: "lens-db__btn lens-db__btn--sm",
1297
+ disabled: isBusy,
1298
+ onClick: () => onCompleteDev(provider),
1299
+ children: isBusy ? "Completing\u2026" : "Complete connection (dev)"
1300
+ }
1301
+ )
1302
+ ] }),
1303
+ status === "disconnected" && /* @__PURE__ */ jsx7(
1304
+ "button",
1305
+ {
1306
+ type: "button",
1307
+ className: "lens-db__btn lens-db__btn--sm",
1308
+ disabled: isBusy,
1309
+ onClick: () => onConnect(provider),
1310
+ children: isBusy ? "Opening\u2026" : "Connect"
1311
+ }
1312
+ ),
1313
+ status !== "disconnected" && /* @__PURE__ */ jsx7(
1314
+ "button",
1315
+ {
1316
+ type: "button",
1317
+ className: "lens-db__btn lens-db__btn--sm lens-db__btn--danger-ghost",
1318
+ "aria-label": `Disconnect ${meta.name}`,
1319
+ disabled: isBusy,
1320
+ onClick: () => onDisconnect(provider),
1321
+ children: "Disconnect"
1322
+ }
1323
+ )
1324
+ ] }),
1325
+ isConnected && /* @__PURE__ */ jsx7(
1326
+ IntegrationConfigLine,
1327
+ {
1328
+ provider,
1329
+ config: integration?.config ?? {},
1330
+ active,
1331
+ disabled: isBusy,
1332
+ onSave: (config) => onSaveConfig(provider, config)
1333
+ }
1334
+ )
1335
+ ]
1336
+ },
1337
+ provider
1338
+ );
1339
+ })
1340
+ }
1341
+ )
1342
+ ] });
1343
+ }
1344
+ function IntegrationConfigLine({
1345
+ provider,
1346
+ config,
1347
+ active,
1348
+ disabled,
1349
+ onSave
1350
+ }) {
1351
+ const field = CONFIG_FIELD[provider];
1352
+ const saved = config[field.key] ?? "";
1353
+ const [value, setValue] = useState5(saved);
1354
+ const [busy, setBusy] = useState5(false);
1355
+ const [done, setDone] = useState5(false);
1356
+ useEffect4(() => {
1357
+ setValue(saved);
1358
+ }, [saved]);
1359
+ const dirty = value.trim() !== saved;
1360
+ async function save() {
1361
+ if (busy || !dirty) return;
1362
+ setBusy(true);
1363
+ setDone(false);
1364
+ const next = { ...config };
1365
+ next[field.key] = value.trim() || void 0;
1366
+ try {
1367
+ await onSave(next);
1368
+ setDone(true);
1369
+ setTimeout(() => setDone(false), 2e3);
1370
+ } catch {
1371
+ } finally {
1372
+ setBusy(false);
1373
+ }
1374
+ }
1375
+ const showHint = Boolean(field.requiredHint) && active && !saved;
1376
+ return /* @__PURE__ */ jsxs5("div", { className: "lens-db__intconfig", children: [
1377
+ /* @__PURE__ */ jsxs5("label", { className: "lens-db__intconfig-field", children: [
1378
+ /* @__PURE__ */ jsx7("span", { className: "lens-db__intconfig-label", children: field.label }),
1379
+ /* @__PURE__ */ jsx7(
1380
+ "input",
1381
+ {
1382
+ className: "lens-db__intconfig-input",
1383
+ type: "text",
1384
+ value,
1385
+ placeholder: field.placeholder,
1386
+ disabled: disabled || busy,
1387
+ spellCheck: false,
1388
+ autoCapitalize: "off",
1389
+ autoCorrect: "off",
1390
+ onChange: (e) => setValue(e.target.value),
1391
+ onKeyDown: (e) => {
1392
+ if (e.key === "Enter") {
1393
+ e.preventDefault();
1394
+ void save();
1395
+ }
1396
+ }
1397
+ }
1398
+ )
1399
+ ] }),
1400
+ busy ? /* @__PURE__ */ jsx7("span", { className: "lens-db__intconfig-busy", "aria-live": "polite", "aria-label": "Saving", children: /* @__PURE__ */ jsx7("span", { className: "lens-db__spinner", "aria-hidden": true }) }) : done ? /* @__PURE__ */ jsx7("span", { className: "lens-db__intconfig-done", "aria-live": "polite", "aria-label": "Saved", children: /* @__PURE__ */ jsx7(IconCheck, { size: 14 }) }) : dirty ? /* @__PURE__ */ jsx7(
1401
+ "button",
1402
+ {
1403
+ type: "button",
1404
+ className: "lens-db__btn lens-db__btn--sm",
1405
+ disabled,
1406
+ onClick: () => void save(),
1407
+ children: "Save"
1408
+ }
1409
+ ) : null,
1410
+ showHint && /* @__PURE__ */ jsx7("span", { className: "lens-db__intconfig-hint", children: field.requiredHint })
1411
+ ] });
1412
+ }
1413
+ var PROVIDER_NAME = {
1414
+ linear: "Linear",
1415
+ jira: "Jira",
1416
+ github: "GitHub"
1417
+ };
1418
+ function currentReturnUrl() {
1419
+ return typeof window !== "undefined" ? window.location.href : void 0;
1420
+ }
1421
+ function providerLabel(raw) {
1422
+ return PROVIDER_NAME[raw] ?? raw;
1423
+ }
1424
+ function consumeOAuthCallback() {
1425
+ if (typeof window === "undefined") return null;
1426
+ const params = new URLSearchParams(window.location.search);
1427
+ const connected = params.get("lens_connected");
1428
+ const error = params.get("lens_error");
1429
+ if (connected == null && error == null) return null;
1430
+ params.delete("lens_connected");
1431
+ params.delete("lens_error");
1432
+ try {
1433
+ const qs = params.toString();
1434
+ const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
1435
+ window.history.replaceState(window.history.state, "", url);
1436
+ } catch {
1437
+ }
1438
+ return { connected: connected ?? void 0, error: error ?? void 0 };
1439
+ }
1440
+ function IntegrationSettings(props) {
1441
+ const api = useLensApi();
1442
+ const { data, error, loading, reload } = useAsync(() => api.listIntegrations(), []);
1443
+ const [busy, setBusy] = useState6(null);
1444
+ const [list, setList] = useState6(null);
1445
+ const [flash, setFlash] = useState6(null);
1446
+ useEffect5(() => {
1447
+ const outcome = consumeOAuthCallback();
1448
+ if (!outcome) return;
1449
+ if (outcome.connected) {
1450
+ setFlash({ kind: "ok", message: `${providerLabel(outcome.connected)} connected` });
1451
+ reload();
1452
+ }
1453
+ if (outcome.error) {
1454
+ setFlash({ kind: "error", message: outcome.error });
1455
+ }
1456
+ }, []);
1457
+ const integrations = list ?? data ?? [];
1458
+ async function run(provider, fn) {
1459
+ setBusy(provider);
1460
+ try {
1461
+ const next = await fn();
1462
+ if (Array.isArray(next)) setList(next);
1463
+ else setList(await api.listIntegrations());
1464
+ } catch (err) {
1465
+ setFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1466
+ } finally {
1467
+ setBusy(null);
1468
+ }
1469
+ }
1470
+ function connect(provider) {
1471
+ return run(provider, async () => {
1472
+ const { authorizeUrl } = await api.connectIntegration(provider, {
1473
+ returnUrl: currentReturnUrl()
1474
+ });
1475
+ window.open(authorizeUrl, "_blank", "noopener,noreferrer");
1476
+ return api.listIntegrations();
1477
+ });
1478
+ }
1479
+ const completeDev = (provider) => run(provider, async () => {
1480
+ await api.completeIntegrationDev(provider);
1481
+ return api.listIntegrations();
1482
+ });
1483
+ const activate = (provider) => run(provider, () => api.activateIntegration(provider));
1484
+ const disconnect = (provider) => run(provider, () => api.disconnectIntegration(provider));
1485
+ async function saveConfig(provider, config) {
1486
+ setBusy(provider);
1487
+ try {
1488
+ setList(await api.updateIntegrationConfig(provider, config));
1489
+ } catch (err) {
1490
+ setFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1491
+ throw err;
1492
+ } finally {
1493
+ setBusy(null);
1494
+ }
1495
+ }
1496
+ return /* @__PURE__ */ jsx8("div", { className: clsx_default("lens-db", props.className), children: /* @__PURE__ */ jsx8(
1497
+ IntegrationList,
1498
+ {
1499
+ integrations,
1500
+ busy,
1501
+ title: "Issue destination",
1502
+ description: "Reports create issues in exactly one connected tracker.",
1503
+ flash,
1504
+ onFlashDismiss: () => setFlash(null),
1505
+ loading: loading && !list,
1506
+ loadError: error && !list ? error.message : null,
1507
+ onRetry: reload,
1508
+ onConnect: (p) => void connect(p),
1509
+ onActivate: (p) => void activate(p),
1510
+ onDisconnect: (p) => void disconnect(p),
1511
+ onCompleteDev: (p) => void completeDev(p),
1512
+ onSaveConfig: saveConfig
1513
+ }
1514
+ ) });
1515
+ }
1516
+ var MARKUP_RULES = [
1517
+ { cls: "com", re: /<!--[\s\S]*?-->|\/\*[\s\S]*?\*\/|\/\/[^\n]*/y },
1518
+ { cls: "str", re: /"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`/y },
1519
+ { cls: "tag", re: /<\/?[a-zA-Z][\w.-]*|\/>|>/y },
1520
+ {
1521
+ cls: "kw",
1522
+ re: /\b(?:import|from|export|function|return|const|let|var|async|await|new|window|default)\b/y
1523
+ },
1524
+ { cls: "attr", re: /\b[a-zA-Z_][\w-]*(?==)/y },
1525
+ { cls: "num", re: /\b\d+(?:\.\d+)?\b/y }
1526
+ ];
1527
+ var ENV_RULES = [
1528
+ { cls: "com", re: /#[^\n]*/y },
1529
+ { cls: "attr", re: /^[A-Z][A-Z0-9_]*(?==)/my },
1530
+ { cls: "str", re: /(?<==)[^\n]+/y }
1531
+ ];
1532
+ function highlightCode(code, lang) {
1533
+ const rules = lang === "env" ? ENV_RULES : MARKUP_RULES;
1534
+ const out = [];
1535
+ let pos = 0;
1536
+ let plainStart = 0;
1537
+ let key = 0;
1538
+ const flushPlain = (until) => {
1539
+ if (until > plainStart) out.push(code.slice(plainStart, until));
1540
+ };
1541
+ while (pos < code.length) {
1542
+ let matched = false;
1543
+ for (const rule of rules) {
1544
+ rule.re.lastIndex = pos;
1545
+ const m = rule.re.exec(code);
1546
+ if (m && m[0].length > 0) {
1547
+ flushPlain(pos);
1548
+ out.push(
1549
+ /* @__PURE__ */ jsx9("span", { className: `lens-db__tok-${rule.cls}`, children: m[0] }, key++)
1550
+ );
1551
+ pos += m[0].length;
1552
+ plainStart = pos;
1553
+ matched = true;
1554
+ break;
1555
+ }
1556
+ }
1557
+ if (!matched) pos += 1;
1558
+ }
1559
+ flushPlain(code.length);
1560
+ return out;
1561
+ }
1562
+ var STEPS = ["Project", "Destination", "Asset storage", "Install"];
1563
+ var PROVIDER_NAME2 = {
1564
+ linear: "Linear",
1565
+ jira: "Jira",
1566
+ github: "GitHub"
1567
+ };
1568
+ var S3_ENV_SNIPPET = `# Lens API asset storage (S3 or compatible)
1569
+ LENS_S3_BUCKET=your-bucket
1570
+ LENS_S3_REGION=us-east-1
1571
+ AWS_ACCESS_KEY_ID=AKIA...
1572
+ AWS_SECRET_ACCESS_KEY=...
1573
+ # Optional (R2 / MinIO):
1574
+ # LENS_S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com
1575
+ # LENS_S3_FORCE_PATH_STYLE=true`;
1576
+ function parseOrigins(raw) {
1577
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1578
+ }
1579
+ function currentReturnUrl2() {
1580
+ return typeof window !== "undefined" ? window.location.href : void 0;
1581
+ }
1582
+ function providerLabel2(raw) {
1583
+ return PROVIDER_NAME2[raw] ?? raw;
1584
+ }
1585
+ function sameOrigins(a, b) {
1586
+ return a.length === b.length && a.every((v, i) => v === b[i]);
1587
+ }
1588
+ function consumeOAuthCallback2() {
1589
+ if (typeof window === "undefined") return null;
1590
+ const params = new URLSearchParams(window.location.search);
1591
+ const connected = params.get("lens_connected");
1592
+ const error = params.get("lens_error");
1593
+ if (connected == null && error == null) return null;
1594
+ params.delete("lens_connected");
1595
+ params.delete("lens_error");
1596
+ try {
1597
+ const qs = params.toString();
1598
+ const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
1599
+ window.history.replaceState(window.history.state, "", url);
1600
+ } catch {
1601
+ }
1602
+ return { connected: connected ?? void 0, error: error ?? void 0 };
1603
+ }
1604
+ function CopyButton({
1605
+ value,
1606
+ label = "Copy",
1607
+ ghost = false
1608
+ }) {
1609
+ const [copied, setCopied] = useState7(false);
1610
+ return /* @__PURE__ */ jsxs6(
1611
+ "button",
1612
+ {
1613
+ type: "button",
1614
+ className: clsx_default(
1615
+ "lens-db__copy-btn",
1616
+ ghost && "lens-db__copy-btn--ghost",
1617
+ copied && "lens-db__copy-btn--done"
1618
+ ),
1619
+ "aria-label": copied ? "Copied" : label,
1620
+ onClick: () => {
1621
+ void navigator.clipboard?.writeText(value).then(
1622
+ () => {
1623
+ setCopied(true);
1624
+ setTimeout(() => setCopied(false), 2e3);
1625
+ },
1626
+ () => void 0
1627
+ );
1628
+ },
1629
+ children: [
1630
+ copied ? /* @__PURE__ */ jsx10(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx10(IconCopy, { size: 14 }),
1631
+ copied ? "Copied" : label
1632
+ ]
1633
+ }
1634
+ );
1635
+ }
1636
+ function OnboardingWizard(props) {
1637
+ const api = useLensApi();
1638
+ const [step, setStep] = useState7(0);
1639
+ const [name, setName] = useState7("");
1640
+ const [origins, setOrigins] = useState7("");
1641
+ const [dashboardUrl, setDashboardUrl] = useState7("");
1642
+ const [project, setProject] = useState7(null);
1643
+ const [accessId, setAccessId] = useState7(null);
1644
+ const [accessSecret, setAccessSecret] = useState7(null);
1645
+ const [step1Busy, setStep1Busy] = useState7(false);
1646
+ const [step1Error, setStep1Error] = useState7(void 0);
1647
+ const [loadingExisting, setLoadingExisting] = useState7(Boolean(props.existingProject));
1648
+ const [integrations, setIntegrations] = useState7(null);
1649
+ const [intBusy, setIntBusy] = useState7(null);
1650
+ const [intLoadError, setIntLoadError] = useState7(void 0);
1651
+ const [intFlash, setIntFlash] = useState7(null);
1652
+ const [storage, setStorage] = useState7(null);
1653
+ const [storageError, setStorageError] = useState7(void 0);
1654
+ const [finishBusy, setFinishBusy] = useState7(false);
1655
+ const [finishError, setFinishError] = useState7(void 0);
1656
+ useEffect6(() => {
1657
+ if (!props.existingProject) return;
1658
+ let active = true;
1659
+ setLoadingExisting(true);
1660
+ api.getProject().then((p) => {
1661
+ if (!active) return;
1662
+ setProject(p);
1663
+ setAccessId(p.accessId);
1664
+ setName(p.name);
1665
+ setOrigins(p.allowedOrigins.join("\n"));
1666
+ setDashboardUrl(p.dashboardUrl ?? "");
1667
+ setLoadingExisting(false);
1668
+ }).catch((err) => {
1669
+ if (!active) return;
1670
+ setStep1Error(err instanceof Error ? err.message : String(err));
1671
+ setLoadingExisting(false);
1672
+ });
1673
+ return () => {
1674
+ active = false;
1675
+ };
1676
+ }, []);
1677
+ useEffect6(() => {
1678
+ if (step !== 1) return;
1679
+ const outcome = consumeOAuthCallback2();
1680
+ if (!outcome) return;
1681
+ if (outcome.connected) {
1682
+ setIntFlash({ kind: "ok", message: `${providerLabel2(outcome.connected)} connected` });
1683
+ api.listIntegrations().then(setIntegrations).catch(() => void 0);
1684
+ }
1685
+ if (outcome.error) setIntFlash({ kind: "error", message: outcome.error });
1686
+ }, [step]);
1687
+ useEffect6(() => {
1688
+ if (step !== 1 || integrations !== null) return;
1689
+ let active = true;
1690
+ api.listIntegrations().then((list) => active && setIntegrations(list)).catch(
1691
+ (err) => active ? setIntLoadError(err instanceof Error ? err.message : String(err)) : void 0
1692
+ );
1693
+ return () => {
1694
+ active = false;
1695
+ };
1696
+ }, [step, integrations, api]);
1697
+ async function submitStep1() {
1698
+ if (props.existingProject) {
1699
+ if (!project) return;
1700
+ if (!name.trim()) {
1701
+ setStep1Error("Project name is required.");
1702
+ return;
1703
+ }
1704
+ const nextOrigins = parseOrigins(origins);
1705
+ const nextDashboardUrl = dashboardUrl.trim() || null;
1706
+ const changed = name.trim() !== project.name || !sameOrigins(nextOrigins, project.allowedOrigins) || nextDashboardUrl !== (project.dashboardUrl ?? null);
1707
+ if (!changed) {
1708
+ setStep(1);
1709
+ return;
1710
+ }
1711
+ setStep1Busy(true);
1712
+ setStep1Error(void 0);
1713
+ try {
1714
+ const p = await api.updateProject({
1715
+ name: name.trim(),
1716
+ allowedOrigins: nextOrigins,
1717
+ dashboardUrl: nextDashboardUrl
1718
+ });
1719
+ setProject(p);
1720
+ setName(p.name);
1721
+ setOrigins(p.allowedOrigins.join("\n"));
1722
+ setDashboardUrl(p.dashboardUrl ?? "");
1723
+ setStep(1);
1724
+ } catch (err) {
1725
+ setStep1Error(err instanceof Error ? err.message : String(err));
1726
+ } finally {
1727
+ setStep1Busy(false);
1728
+ }
1729
+ return;
1730
+ }
1731
+ if (!name.trim()) {
1732
+ setStep1Error("Project name is required.");
1733
+ return;
1734
+ }
1735
+ setStep1Busy(true);
1736
+ setStep1Error(void 0);
1737
+ try {
1738
+ const res = await api.createProject({
1739
+ name: name.trim(),
1740
+ allowedOrigins: parseOrigins(origins)
1741
+ });
1742
+ setAccessId(res.accessId);
1743
+ setAccessSecret(res.accessSecret);
1744
+ props.onProjectCreated?.(res);
1745
+ let created = res.project;
1746
+ const nextDashboardUrl = dashboardUrl.trim();
1747
+ if (nextDashboardUrl) {
1748
+ try {
1749
+ created = await api.updateProject({ dashboardUrl: nextDashboardUrl });
1750
+ setDashboardUrl(created.dashboardUrl ?? "");
1751
+ } catch {
1752
+ }
1753
+ }
1754
+ setProject(created);
1755
+ setStep(1);
1756
+ } catch (err) {
1757
+ setStep1Error(err instanceof Error ? err.message : String(err));
1758
+ } finally {
1759
+ setStep1Busy(false);
1760
+ }
1761
+ }
1762
+ async function runIntegration(provider, fn) {
1763
+ setIntBusy(provider);
1764
+ try {
1765
+ await fn();
1766
+ setIntegrations(await api.listIntegrations());
1767
+ } catch (err) {
1768
+ setIntFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1769
+ } finally {
1770
+ setIntBusy(null);
1771
+ }
1772
+ }
1773
+ const connect = (provider) => runIntegration(provider, async () => {
1774
+ const { authorizeUrl } = await api.connectIntegration(provider, {
1775
+ returnUrl: currentReturnUrl2()
1776
+ });
1777
+ window.open(authorizeUrl, "_blank", "noopener,noreferrer");
1778
+ });
1779
+ const completeDev = (provider) => runIntegration(provider, async () => {
1780
+ await api.completeIntegrationDev(provider);
1781
+ });
1782
+ const activate = (provider) => runIntegration(provider, async () => {
1783
+ await api.activateIntegration(provider);
1784
+ });
1785
+ const disconnect = (provider) => runIntegration(provider, async () => {
1786
+ await api.disconnectIntegration(provider);
1787
+ });
1788
+ async function saveConfig(provider, config) {
1789
+ setIntBusy(provider);
1790
+ try {
1791
+ setIntegrations(await api.updateIntegrationConfig(provider, config));
1792
+ } catch (err) {
1793
+ setIntFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1794
+ throw err;
1795
+ } finally {
1796
+ setIntBusy(null);
1797
+ }
1798
+ }
1799
+ useEffect6(() => {
1800
+ if (step !== 2 || storage !== null) return;
1801
+ let active = true;
1802
+ api.getStorageStatus().then((s) => active && setStorage(s)).catch(
1803
+ (err) => active ? setStorageError(err instanceof Error ? err.message : String(err)) : void 0
1804
+ );
1805
+ return () => {
1806
+ active = false;
1807
+ };
1808
+ }, [step, storage, api]);
1809
+ async function finish() {
1810
+ setFinishBusy(true);
1811
+ setFinishError(void 0);
1812
+ try {
1813
+ const p = await api.updateProject({ onboarded: true });
1814
+ setProject(p);
1815
+ props.onComplete?.({ project: p, accessId: accessId ?? p.accessId, accessSecret });
1816
+ } catch (err) {
1817
+ setFinishError(err instanceof Error ? err.message : String(err));
1818
+ } finally {
1819
+ setFinishBusy(false);
1820
+ }
1821
+ }
1822
+ const activeProvider = integrations?.find((i) => i.active)?.provider ?? null;
1823
+ const idForSnippets = accessId ?? project?.accessId ?? "YOUR_ACCESS_ID";
1824
+ const scriptSnippet = `<script>
1825
+ window.Lens=window.Lens||function(){(Lens.q=Lens.q||[]).push(arguments)};
1826
+ Lens('init', '${idForSnippets}');
1827
+ </script>
1828
+ <script async src="https://cdn.merittdev.com/lens/v1/lens.min.js"></script>`;
1829
+ const reactSnippet = `import { LensProvider } from '@merittdev/horus-lens/react';
1830
+
1831
+ export function App() {
1832
+ return (
1833
+ <LensProvider accessId="${idForSnippets}">
1834
+ {/* your app */}
1835
+ </LensProvider>
1836
+ );
1837
+ }`;
1838
+ return /* @__PURE__ */ jsxs6("div", { className: clsx_default("lens-db", "lens-db__wizard", props.className), children: [
1839
+ /* @__PURE__ */ jsx10("ol", { className: "lens-db__stepper", children: STEPS.map((label, i) => /* @__PURE__ */ jsxs6(
1840
+ "li",
1841
+ {
1842
+ className: clsx_default(
1843
+ "lens-db__step",
1844
+ i === step && "lens-db__step--current",
1845
+ i < step && "lens-db__step--done"
1846
+ ),
1847
+ children: [
1848
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__step-dot", children: i < step ? /* @__PURE__ */ jsx10(IconCheck, { size: 14 }) : i + 1 }),
1849
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__step-label", children: label })
1850
+ ]
1851
+ },
1852
+ label
1853
+ )) }),
1854
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-body", children: [
1855
+ step === 0 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
1856
+ /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Create your project" }),
1857
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Name it and list the origins allowed to submit reports." }),
1858
+ loadingExisting ? /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Loading project\u2026" }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
1859
+ /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1860
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Project name" }),
1861
+ /* @__PURE__ */ jsx10(
1862
+ "input",
1863
+ {
1864
+ className: "lens-db__input",
1865
+ type: "text",
1866
+ value: name,
1867
+ disabled: step1Busy,
1868
+ placeholder: "Acme Web App",
1869
+ onChange: (e) => setName(e.target.value)
1870
+ }
1871
+ )
1872
+ ] }),
1873
+ /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1874
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Allowed origins" }),
1875
+ /* @__PURE__ */ jsx10(
1876
+ "textarea",
1877
+ {
1878
+ className: "lens-db__input lens-db__textarea",
1879
+ rows: 4,
1880
+ value: origins,
1881
+ disabled: step1Busy,
1882
+ placeholder: "https://app.acme.com\nhttps://admin.acme.com",
1883
+ onChange: (e) => setOrigins(e.target.value)
1884
+ }
1885
+ ),
1886
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__hint", children: "One origin per line." })
1887
+ ] }),
1888
+ /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1889
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Dashboard URL" }),
1890
+ /* @__PURE__ */ jsx10(
1891
+ "input",
1892
+ {
1893
+ className: "lens-db__input",
1894
+ type: "url",
1895
+ value: dashboardUrl,
1896
+ disabled: step1Busy,
1897
+ placeholder: "https://admin.acme.com/lens",
1898
+ onChange: (e) => setDashboardUrl(e.target.value)
1899
+ }
1900
+ ),
1901
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__hint", children: "Issues link reports back to this page." })
1902
+ ] }),
1903
+ props.existingProject && /* @__PURE__ */ jsx10("div", { className: "lens-db__note", children: "Loaded from your existing project \u2014 edit the name or origins and Continue to save. The access secret was shown only when the project was created and can't be displayed again." })
1904
+ ] }),
1905
+ step1Error && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: step1Error })
1906
+ ] }),
1907
+ step === 1 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
1908
+ /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Choose an issue destination" }),
1909
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Reports create issues in exactly one connected tracker. You can skip this and pick a destination later." }),
1910
+ /* @__PURE__ */ jsx10(
1911
+ IntegrationList,
1912
+ {
1913
+ integrations: integrations ?? [],
1914
+ busy: intBusy,
1915
+ flash: intFlash,
1916
+ onFlashDismiss: () => setIntFlash(null),
1917
+ loading: integrations === null && !intLoadError,
1918
+ loadError: intLoadError ?? null,
1919
+ onRetry: () => {
1920
+ setIntLoadError(void 0);
1921
+ setIntegrations(null);
1922
+ },
1923
+ onConnect: (p) => void connect(p),
1924
+ onActivate: (p) => void activate(p),
1925
+ onDisconnect: (p) => void disconnect(p),
1926
+ onCompleteDev: (p) => void completeDev(p),
1927
+ onSaveConfig: saveConfig
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__hint", children: activeProvider ? `Issues will route to ${providerLabel2(activeProvider)}.` : "No destination selected yet \u2014 issues will queue until you pick one." })
1931
+ ] }),
1932
+ step === 2 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
1933
+ /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Asset storage" }),
1934
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Replays and screenshots upload straight to S3. Credentials are configured through environment variables on your Lens API deployment \u2014 never through this UI." }),
1935
+ !storage && !storageError && /* @__PURE__ */ jsxs6("div", { className: "lens-db__verify", children: [
1936
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__spinner", "aria-hidden": true }),
1937
+ " Checking storage\u2026"
1938
+ ] }),
1939
+ storage?.mode === "s3" && /* @__PURE__ */ jsxs6("div", { className: "lens-db__storage-ok", children: [
1940
+ /* @__PURE__ */ jsx10(IconCheckCircle, { size: 16 }),
1941
+ /* @__PURE__ */ jsxs6("div", { children: [
1942
+ /* @__PURE__ */ jsx10("div", { className: "lens-db__storage-ok-title", children: "Asset storage configured" }),
1943
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__muted", children: [
1944
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__mono", children: storage.bucket }),
1945
+ " \xB7 ",
1946
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__mono", children: storage.region }),
1947
+ storage.source === "project" ? " \u2014 project credentials" : " \u2014 from the Lens API environment"
1948
+ ] })
1949
+ ] })
1950
+ ] }),
1951
+ storage?.mode === "local" && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1952
+ /* @__PURE__ */ jsx10(
1953
+ "div",
1954
+ {
1955
+ className: storage.localAllowed ? "lens-db__warn-banner" : "lens-db__error",
1956
+ children: storage.localAllowed ? "Development mode: assets are stored on the API host disk. Configure S3 before going to production \u2014 report intake is rejected without it." : "No S3 bucket is configured. Report intake is disabled until asset storage is connected."
1957
+ }
1958
+ ),
1959
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__label", children: "Set these on your Lens API deployment, then restart it:" }),
1960
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__codewrap", children: [
1961
+ /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(S3_ENV_SNIPPET, "env") }),
1962
+ /* @__PURE__ */ jsx10(CopyButton, { value: S3_ENV_SNIPPET })
1963
+ ] })
1964
+ ] }),
1965
+ storageError && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: storageError })
1966
+ ] }),
1967
+ step === 3 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
1968
+ /* @__PURE__ */ jsxs6("h3", { className: "lens-db__wizard-title lens-db__wizard-title--ok", children: [
1969
+ /* @__PURE__ */ jsx10(IconCheckCircle, { size: 16 }),
1970
+ "Setup complete"
1971
+ ] }),
1972
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Use these credentials to install the Lens widget in your app." }),
1973
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred", children: [
1974
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Access ID" }),
1975
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred-row", children: [
1976
+ /* @__PURE__ */ jsx10("code", { className: "lens-db__code-inline", children: idForSnippets }),
1977
+ /* @__PURE__ */ jsx10(CopyButton, { value: idForSnippets })
1978
+ ] })
1979
+ ] }),
1980
+ accessSecret ? /* @__PURE__ */ jsxs6("div", { className: "lens-db__warn-banner", children: [
1981
+ /* @__PURE__ */ jsx10("span", { className: "lens-db__warn-icon", children: /* @__PURE__ */ jsx10(IconAlertTriangle, { size: 16 }) }),
1982
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__warn-body", children: [
1983
+ /* @__PURE__ */ jsx10("div", { className: "lens-db__warn-title", children: "Store your access secret now" }),
1984
+ /* @__PURE__ */ jsx10("p", { className: "lens-db__warn-text", children: "This is the only time it will be shown. Save it in your server-side secret store \u2014 it will never be displayed again." }),
1985
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred-row", children: [
1986
+ /* @__PURE__ */ jsx10("code", { className: "lens-db__code-inline lens-db__code-inline--secret", children: accessSecret }),
1987
+ /* @__PURE__ */ jsx10(CopyButton, { value: accessSecret, label: "Copy secret" })
1988
+ ] })
1989
+ ] })
1990
+ ] }) : /* @__PURE__ */ jsx10("div", { className: "lens-db__note", children: "The access secret isn't available here \u2014 it's shown only once, when the project is first created." }),
1991
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__snippet", children: [
1992
+ /* @__PURE__ */ jsx10("div", { className: "lens-db__snippet-head", children: /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Script-tag loader" }) }),
1993
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__codeblock", children: [
1994
+ /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(scriptSnippet, "markup") }),
1995
+ /* @__PURE__ */ jsx10(CopyButton, { value: scriptSnippet, ghost: true })
1996
+ ] })
1997
+ ] }),
1998
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__snippet", children: [
1999
+ /* @__PURE__ */ jsx10("div", { className: "lens-db__snippet-head", children: /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "React" }) }),
2000
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__codeblock", children: [
2001
+ /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(reactSnippet, "markup") }),
2002
+ /* @__PURE__ */ jsx10(CopyButton, { value: reactSnippet, ghost: true })
2003
+ ] })
2004
+ ] }),
2005
+ finishError && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: finishError })
2006
+ ] })
2007
+ ] }),
2008
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-footer", children: [
2009
+ /* @__PURE__ */ jsx10(
2010
+ "button",
2011
+ {
2012
+ type: "button",
2013
+ className: "lens-db__btn lens-db__btn--ghost",
2014
+ disabled: step === 0 || step1Busy || finishBusy,
2015
+ onClick: () => setStep((s) => Math.max(0, s - 1)),
2016
+ children: "Back"
2017
+ }
2018
+ ),
2019
+ /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-footer-right", children: [
2020
+ step === 0 && /* @__PURE__ */ jsx10(
2021
+ "button",
2022
+ {
2023
+ type: "button",
2024
+ className: "lens-db__btn lens-db__btn--primary",
2025
+ disabled: step1Busy || loadingExisting || props.existingProject && !project,
2026
+ onClick: () => void submitStep1(),
2027
+ children: props.existingProject ? step1Busy ? "Saving\u2026" : "Continue" : step1Busy ? "Creating\u2026" : "Create project"
2028
+ }
2029
+ ),
2030
+ step === 1 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
2031
+ /* @__PURE__ */ jsx10(
2032
+ "button",
2033
+ {
2034
+ type: "button",
2035
+ className: "lens-db__btn lens-db__btn--ghost",
2036
+ onClick: () => setStep(2),
2037
+ children: "Skip"
2038
+ }
2039
+ ),
2040
+ /* @__PURE__ */ jsx10(
2041
+ "button",
2042
+ {
2043
+ type: "button",
2044
+ className: "lens-db__btn lens-db__btn--primary",
2045
+ onClick: () => setStep(2),
2046
+ children: "Next"
2047
+ }
2048
+ )
2049
+ ] }),
2050
+ step === 2 && /* @__PURE__ */ jsx10(
2051
+ "button",
2052
+ {
2053
+ type: "button",
2054
+ className: "lens-db__btn lens-db__btn--primary",
2055
+ disabled: !storage || storage.mode === "local" && !storage.localAllowed,
2056
+ onClick: () => setStep(3),
2057
+ children: "Continue"
2058
+ }
2059
+ ),
2060
+ step === 3 && /* @__PURE__ */ jsx10(
2061
+ "button",
2062
+ {
2063
+ type: "button",
2064
+ className: "lens-db__btn lens-db__btn--primary",
2065
+ disabled: finishBusy,
2066
+ onClick: () => void finish(),
2067
+ children: finishBusy ? "Finishing\u2026" : "Finish"
2068
+ }
2069
+ )
2070
+ ] })
2071
+ ] })
2072
+ ] });
2073
+ }
2074
+ function parseOrigins2(raw) {
2075
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
2076
+ }
2077
+ function ProjectSettings(props) {
2078
+ const api = useLensApi();
2079
+ const { data, error, loading, reload } = useAsync(() => api.getProject(), []);
2080
+ const [name, setName] = useState8("");
2081
+ const [origins, setOrigins] = useState8("");
2082
+ const [dashboardUrl, setDashboardUrl] = useState8("");
2083
+ const [saving, setSaving] = useState8(false);
2084
+ const [saveError, setSaveError] = useState8(void 0);
2085
+ const [saved, setSaved] = useState8(false);
2086
+ const [copied, setCopied] = useState8(false);
2087
+ useEffect7(() => {
2088
+ if (!data) return;
2089
+ setName(data.name);
2090
+ setOrigins(data.allowedOrigins.join("\n"));
2091
+ setDashboardUrl(data.dashboardUrl ?? "");
2092
+ }, [data]);
2093
+ async function save() {
2094
+ if (!name.trim()) {
2095
+ setSaveError("Project name is required.");
2096
+ return;
2097
+ }
2098
+ setSaving(true);
2099
+ setSaveError(void 0);
2100
+ setSaved(false);
2101
+ try {
2102
+ const p = await api.updateProject({
2103
+ name: name.trim(),
2104
+ allowedOrigins: parseOrigins2(origins),
2105
+ dashboardUrl: dashboardUrl.trim() || null
2106
+ });
2107
+ setName(p.name);
2108
+ setOrigins(p.allowedOrigins.join("\n"));
2109
+ setDashboardUrl(p.dashboardUrl ?? "");
2110
+ setSaved(true);
2111
+ setTimeout(() => setSaved(false), 2e3);
2112
+ } catch (err) {
2113
+ setSaveError(err instanceof Error ? err.message : String(err));
2114
+ } finally {
2115
+ setSaving(false);
2116
+ }
2117
+ }
2118
+ const accessId = data?.accessId ?? "";
2119
+ return /* @__PURE__ */ jsxs7("div", { className: clsx_default("lens-db", "lens-db__panel", props.className), children: [
2120
+ /* @__PURE__ */ jsxs7("div", { children: [
2121
+ /* @__PURE__ */ jsx11("h3", { className: "lens-db__wizard-title", children: "Project settings" }),
2122
+ /* @__PURE__ */ jsx11("p", { className: "lens-db__muted", children: "Update the project name and the origins allowed to submit reports." })
2123
+ ] }),
2124
+ loading && !data && /* @__PURE__ */ jsx11("p", { className: "lens-db__muted", children: "Loading project\u2026" }),
2125
+ error && !data && /* @__PURE__ */ jsxs7("div", { className: "lens-db__error", children: [
2126
+ "Failed to load project: ",
2127
+ error.message,
2128
+ " ",
2129
+ /* @__PURE__ */ jsx11("button", { type: "button", className: "lens-db__link-btn", onClick: reload, children: "Retry" })
2130
+ ] }),
2131
+ data && /* @__PURE__ */ jsxs7(Fragment5, { children: [
2132
+ /* @__PURE__ */ jsxs7("label", { className: "lens-db__field", children: [
2133
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__label", children: "Project name" }),
2134
+ /* @__PURE__ */ jsx11(
2135
+ "input",
2136
+ {
2137
+ className: "lens-db__input",
2138
+ type: "text",
2139
+ value: name,
2140
+ disabled: saving,
2141
+ placeholder: "Acme Web App",
2142
+ onChange: (e) => setName(e.target.value)
2143
+ }
2144
+ )
2145
+ ] }),
2146
+ /* @__PURE__ */ jsxs7("label", { className: "lens-db__field", children: [
2147
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__label", children: "Allowed origins" }),
2148
+ /* @__PURE__ */ jsx11(
2149
+ "textarea",
2150
+ {
2151
+ className: "lens-db__input lens-db__textarea",
2152
+ rows: 4,
2153
+ value: origins,
2154
+ disabled: saving,
2155
+ placeholder: "https://app.acme.com\nhttps://admin.acme.com",
2156
+ onChange: (e) => setOrigins(e.target.value)
2157
+ }
2158
+ ),
2159
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__hint", children: "One origin per line." })
2160
+ ] }),
2161
+ /* @__PURE__ */ jsxs7("label", { className: "lens-db__field", children: [
2162
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__label", children: "Dashboard URL" }),
2163
+ /* @__PURE__ */ jsx11(
2164
+ "input",
2165
+ {
2166
+ className: "lens-db__input",
2167
+ type: "url",
2168
+ value: dashboardUrl,
2169
+ disabled: saving,
2170
+ placeholder: "https://admin.acme.com/lens",
2171
+ onChange: (e) => setDashboardUrl(e.target.value)
2172
+ }
2173
+ ),
2174
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__hint", children: "Issues link reports back to this page." })
2175
+ ] }),
2176
+ /* @__PURE__ */ jsxs7("div", { className: "lens-db__field", children: [
2177
+ /* @__PURE__ */ jsx11("span", { className: "lens-db__label", children: "Access ID" }),
2178
+ /* @__PURE__ */ jsxs7("div", { className: "lens-db__cred-row", children: [
2179
+ /* @__PURE__ */ jsx11("code", { className: "lens-db__code-inline", children: accessId }),
2180
+ /* @__PURE__ */ jsxs7(
2181
+ "button",
2182
+ {
2183
+ type: "button",
2184
+ className: clsx_default("lens-db__copy-btn", copied && "lens-db__copy-btn--done"),
2185
+ "aria-label": copied ? "Copied" : "Copy access ID",
2186
+ onClick: () => {
2187
+ void navigator.clipboard?.writeText(accessId).then(
2188
+ () => {
2189
+ setCopied(true);
2190
+ setTimeout(() => setCopied(false), 2e3);
2191
+ },
2192
+ () => void 0
2193
+ );
2194
+ },
2195
+ children: [
2196
+ copied ? /* @__PURE__ */ jsx11(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx11(IconCopy, { size: 14 }),
2197
+ copied ? "Copied" : "Copy"
2198
+ ]
2199
+ }
2200
+ )
2201
+ ] })
2202
+ ] }),
2203
+ saveError && /* @__PURE__ */ jsx11("div", { className: "lens-db__error", children: saveError }),
2204
+ /* @__PURE__ */ jsxs7("div", { className: "lens-db__panel-actions", children: [
2205
+ /* @__PURE__ */ jsx11(
2206
+ "button",
2207
+ {
2208
+ type: "button",
2209
+ className: "lens-db__btn lens-db__btn--primary",
2210
+ disabled: saving,
2211
+ onClick: () => void save(),
2212
+ children: saving ? "Saving\u2026" : "Save"
2213
+ }
2214
+ ),
2215
+ saved && /* @__PURE__ */ jsxs7("span", { className: "lens-db__flash lens-db__flash--ok", children: [
2216
+ /* @__PURE__ */ jsx11(IconCheck, { size: 14 }),
2217
+ "Saved"
2218
+ ] })
2219
+ ] })
2220
+ ] })
2221
+ ] });
2222
+ }
2223
+ export {
2224
+ IntegrationSettings,
2225
+ LensApiError,
2226
+ LensDashboardProvider,
2227
+ OnboardingWizard,
2228
+ ProjectSettings,
2229
+ ReplayPlayer,
2230
+ ReportDetail,
2231
+ ReportsTable,
2232
+ StatusBadge,
2233
+ createLensApi,
2234
+ useAsync,
2235
+ useLensApi
2236
+ };
2237
+ //# sourceMappingURL=dashboard.js.map