@lunora/studio 0.0.0 → 1.0.0-alpha.10

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 (80) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1403 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTIONS-DjXgvJb6.js +46 -0
  9. package/dist/packem_shared/ApiDocsPanel-yVSGHK3H.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-1vubqG5s.js +229 -0
  11. package/dist/packem_shared/ApiTab-rwttyQ1-.js +251 -0
  12. package/dist/packem_shared/AuditPanel-D9Eh7j7E.js +200 -0
  13. package/dist/packem_shared/CommandPalette-BtgkBzZI.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-C2eVi2Qv.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-B2DDM2Tn.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DEFAULT_LOCALE-CiX4W7Kr.js +27 -0
  19. package/dist/packem_shared/DataBrowser-CyckZRwj.js +4480 -0
  20. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  21. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  22. package/dist/packem_shared/ExportImportPanel-BGJqFK9X.js +197 -0
  23. package/dist/packem_shared/FileBrowser-0pCpdKPE.js +2932 -0
  24. package/dist/packem_shared/FunctionRunner-7NH3KcTg.js +343 -0
  25. package/dist/packem_shared/FunctionStatsPanel-CXynSuqT.js +375 -0
  26. package/dist/packem_shared/GlobalDataBrowser-DAxzB-Br.js +289 -0
  27. package/dist/packem_shared/HealthPanel-CyLs7qZm.js +648 -0
  28. package/dist/packem_shared/HomePanel-BOkFRbuj.js +1334 -0
  29. package/dist/packem_shared/InsightsPanel-CvXMoZuv.js +461 -0
  30. package/dist/packem_shared/LogsPanel-BrUstOk6.js +827 -0
  31. package/dist/packem_shared/MailPanel-GnnrNqL6.js +440 -0
  32. package/dist/packem_shared/MetricsPanel-BN_uUU-_.js +1611 -0
  33. package/dist/packem_shared/MigrationsPanel-DaZuUHje.js +239 -0
  34. package/dist/packem_shared/OpenRpcReferencePanel-vbcYbv3V.js +191 -0
  35. package/dist/packem_shared/PitrPanel-LJGNP_IX.js +252 -0
  36. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  37. package/dist/packem_shared/ScheduledJobs-UmVaMbJy.js +262 -0
  38. package/dist/packem_shared/SchemaViewer-Bam6ylvw.js +2512 -0
  39. package/dist/packem_shared/SecurityAdvisorPanel-DTmjUNqt.js +92 -0
  40. package/dist/packem_shared/SettingsPanel-C2wDK-8F.js +390 -0
  41. package/dist/packem_shared/ShardInput-gx_DZXXn.js +107 -0
  42. package/dist/packem_shared/SqlEditorPanel-D5wMIxcg.js +13 -0
  43. package/dist/packem_shared/Studio-DpgYe4qt.js +33 -0
  44. package/dist/packem_shared/StudioApp-C7aaizqc.js +657 -0
  45. package/dist/packem_shared/StudioI18nProvider-B-tDCZIi.js +48 -0
  46. package/dist/packem_shared/TableEditor-DzK3Z6rL.js +371 -0
  47. package/dist/packem_shared/advisor-view-CGcfMiPq.js +159 -0
  48. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  49. package/dist/packem_shared/app.d-Nbhpihxv.d.ts +300 -0
  50. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  51. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  52. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  53. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  54. package/dist/packem_shared/cf-links-2cktDedA.js +8 -0
  55. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  56. package/dist/packem_shared/data-grid-B5CaV3wS.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-CzUfqgsM.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-rXJEAhXS.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-FZ0psC1z.js +44 -0
  64. package/dist/packem_shared/reference-view-B4E49Lip.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-Smfl_FDF.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-9Mq0PdMi.js +10831 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-Bg7uzbvC.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-admin-query-4JQW2rr1.js +88 -0
  74. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  75. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  76. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  77. package/dist/standalone/studio.js +378 -0
  78. package/dist/styles.css +2 -0
  79. package/package.json +78 -17
  80. package/src/theme.css +59 -0
@@ -0,0 +1,440 @@
1
+ 'use client';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useState, useMemo } from 'react';
4
+ import { B as Badge } from './badge-B2PKA1-5.js';
5
+ import { B as Button } from './button-BhsN2uZH.js';
6
+ import { E as EmptyState } from './empty-state-DY_oe0k6.js';
7
+ import { I as Input } from './input-XH4r1Pt1.js';
8
+ import { c } from 'react/compiler-runtime';
9
+ import { ScrollArea as ScrollArea$1 } from '@base-ui/react/scroll-area';
10
+ import { c as cn } from './utils-B05Dmz_H.js';
11
+ import { jsxDEV, Fragment } from 'react/jsx-dev-runtime';
12
+ import { Separator as Separator$1 } from '@base-ui/react/separator';
13
+ import { u as useAdminQuery } from './use-admin-query-4JQW2rr1.js';
14
+ import { useAutoRefresh } from './DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js';
15
+ import { useT } from './DEFAULT_LOCALE-CiX4W7Kr.js';
16
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTIONS-DjXgvJb6.js';
17
+ import { b as formatTimestamp, f as fireAndForget, g as copyToClipboard, c as callOptions, e as errorMessage, a as adminRef } from './internal-rXJEAhXS.js';
18
+
19
+ function Separator({
20
+ className,
21
+ orientation = "horizontal",
22
+ ...props
23
+ }) {
24
+ return /* @__PURE__ */ jsxDEV(Separator$1, {
25
+ "data-slot": "separator",
26
+ orientation,
27
+ className: cn("shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", className),
28
+ ...props
29
+ }, void 0, false);
30
+ }
31
+
32
+ function ScrollArea(t0) {
33
+ const $ = c(14);
34
+ let children;
35
+ let className;
36
+ let props;
37
+ if ($[0] !== t0) {
38
+ ({
39
+ className,
40
+ children,
41
+ ...props
42
+ } = t0);
43
+ $[0] = t0;
44
+ $[1] = children;
45
+ $[2] = className;
46
+ $[3] = props;
47
+ } else {
48
+ children = $[1];
49
+ className = $[2];
50
+ props = $[3];
51
+ }
52
+ let t1;
53
+ if ($[4] !== className) {
54
+ t1 = cn("relative", className);
55
+ $[4] = className;
56
+ $[5] = t1;
57
+ } else {
58
+ t1 = $[5];
59
+ }
60
+ let t2;
61
+ if ($[6] !== children) {
62
+ t2 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Viewport, {
63
+ "data-slot": "scroll-area-viewport",
64
+ className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
65
+ children
66
+ }, void 0, false);
67
+ $[6] = children;
68
+ $[7] = t2;
69
+ } else {
70
+ t2 = $[7];
71
+ }
72
+ let t3;
73
+ let t4;
74
+ if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
75
+ t3 = /* @__PURE__ */ jsxDEV(ScrollBar, {}, void 0, false);
76
+ t4 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Corner, {}, void 0, false);
77
+ $[8] = t3;
78
+ $[9] = t4;
79
+ } else {
80
+ t3 = $[8];
81
+ t4 = $[9];
82
+ }
83
+ let t5;
84
+ if ($[10] !== props || $[11] !== t1 || $[12] !== t2) {
85
+ t5 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Root, {
86
+ "data-slot": "scroll-area",
87
+ className: t1,
88
+ ...props,
89
+ children: [t2, t3, t4]
90
+ }, void 0, true);
91
+ $[10] = props;
92
+ $[11] = t1;
93
+ $[12] = t2;
94
+ $[13] = t5;
95
+ } else {
96
+ t5 = $[13];
97
+ }
98
+ return t5;
99
+ }
100
+ function ScrollBar({
101
+ className,
102
+ orientation = "vertical",
103
+ ...props
104
+ }) {
105
+ return /* @__PURE__ */ jsxDEV(ScrollArea$1.Scrollbar, {
106
+ "data-slot": "scroll-area-scrollbar",
107
+ "data-orientation": orientation,
108
+ orientation,
109
+ className: cn("flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-s data-vertical:border-s-transparent", className),
110
+ ...props,
111
+ children: /* @__PURE__ */ jsxDEV(ScrollArea$1.Thumb, {
112
+ "data-slot": "scroll-area-thumb",
113
+ className: "relative flex-1 rounded-md bg-border"
114
+ }, void 0, false)
115
+ }, void 0, false);
116
+ }
117
+
118
+ const CLEAR_CAPTURED_MAIL = adminRef(ADMIN_FUNCTIONS.clearCapturedMail);
119
+ const SEND_TEST_MAIL = adminRef(ADMIN_FUNCTIONS.sendTestMail);
120
+ const LINK_PATTERN = /https?:\/\/[^\s"'<>)]+/i;
121
+ const firstLink = (text) => {
122
+ if (text === void 0) {
123
+ return void 0;
124
+ }
125
+ const match = LINK_PATTERN.exec(text);
126
+ return match?.[0];
127
+ };
128
+ const selectedLink = (mail) => {
129
+ if (mail === void 0) {
130
+ return void 0;
131
+ }
132
+ return firstLink(mail.html) ?? firstLink(mail.text);
133
+ };
134
+ const PREVIEW_CSP = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:">`;
135
+ const withPreviewCsp = (html) => `${PREVIEW_CSP}${html}`;
136
+ const recipientText = (value) => {
137
+ if (value === void 0) {
138
+ return "";
139
+ }
140
+ return Array.isArray(value) ? value.join(", ") : value;
141
+ };
142
+ const MailPanel = ({
143
+ limit = 100
144
+ }) => {
145
+ const client = useLunora();
146
+ const t = useT();
147
+ const {
148
+ data,
149
+ error: readError,
150
+ isLoading,
151
+ refetch
152
+ } = useAdminQuery(ADMIN_FUNCTIONS.getCapturedMail, {
153
+ limit
154
+ });
155
+ const [actionError, setActionError] = useState(null);
156
+ const [selectedId, setSelectedId] = useState(null);
157
+ const [tab, setTab] = useState("html");
158
+ const [filter, setFilter] = useState("");
159
+ const entries = useMemo(() => data?.entries ?? [], [data]);
160
+ const error = readError ?? actionError;
161
+ const clearInbox = async () => {
162
+ setActionError(null);
163
+ try {
164
+ await client.query(CLEAR_CAPTURED_MAIL, {}, callOptions(""));
165
+ setSelectedId(null);
166
+ refetch();
167
+ } catch (error_) {
168
+ setActionError(errorMessage(error_));
169
+ }
170
+ };
171
+ const sendTest = async () => {
172
+ setActionError(null);
173
+ try {
174
+ await client.query(SEND_TEST_MAIL, {}, callOptions(""));
175
+ refetch();
176
+ } catch (error__0) {
177
+ setActionError(errorMessage(error__0));
178
+ }
179
+ };
180
+ useAutoRefresh(() => {
181
+ refetch();
182
+ }, true);
183
+ const onClear = () => {
184
+ fireAndForget(clearInbox());
185
+ };
186
+ const onSendTest = () => {
187
+ fireAndForget(sendTest());
188
+ };
189
+ const onFilterChange = (event) => {
190
+ setFilter(event.currentTarget.value);
191
+ };
192
+ const onSelectMessage = (event_0) => {
193
+ const {
194
+ id
195
+ } = event_0.currentTarget.dataset;
196
+ if (id !== void 0) {
197
+ setSelectedId(id);
198
+ }
199
+ };
200
+ const onSelectTab = (event_1) => {
201
+ const next = event_1.currentTarget.dataset["tab"];
202
+ if (next === "headers" || next === "html" || next === "text") {
203
+ setTab(next);
204
+ }
205
+ };
206
+ const tabTitle = (value) => {
207
+ if (value === "html") {
208
+ return t("HTML");
209
+ }
210
+ if (value === "text") {
211
+ return t("Plain text");
212
+ }
213
+ return t("Headers");
214
+ };
215
+ const visible = useMemo(() => {
216
+ const needle = filter.trim().toLowerCase();
217
+ if (needle === "") {
218
+ return entries;
219
+ }
220
+ return entries.filter((entry) => `${entry.subject} ${recipientText(entry.to)}`.toLowerCase().includes(needle));
221
+ }, [entries, filter]);
222
+ const selected = useMemo(() => {
223
+ if (visible.length === 0) {
224
+ return void 0;
225
+ }
226
+ return visible.find((entry_0) => entry_0.id === selectedId) ?? visible[0];
227
+ }, [visible, selectedId]);
228
+ const link = selectedLink(selected);
229
+ const onCopyLink = () => {
230
+ if (link !== void 0) {
231
+ copyToClipboard(link);
232
+ }
233
+ };
234
+ const onOpenLink = () => {
235
+ if (link !== void 0 && "window" in globalThis) {
236
+ globalThis.window.open(link, "_blank", "noopener");
237
+ }
238
+ };
239
+ return /* @__PURE__ */ jsxDEV("div", {
240
+ className: "flex flex-col gap-4",
241
+ "data-testid": "mail-panel",
242
+ children: [/* @__PURE__ */ jsxDEV("div", {
243
+ className: "flex flex-wrap items-center gap-2",
244
+ children: [/* @__PURE__ */ jsxDEV(Button, {
245
+ "data-testid": "mail-send-test",
246
+ onClick: onSendTest,
247
+ size: "sm",
248
+ type: "button",
249
+ variant: "outline",
250
+ children: t("Send test")
251
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Button, {
252
+ "data-testid": "mail-clear",
253
+ disabled: entries.length === 0,
254
+ onClick: onClear,
255
+ size: "sm",
256
+ type: "button",
257
+ variant: "outline",
258
+ children: t("Clear inbox")
259
+ }, void 0, false), entries.length > 0 && /* @__PURE__ */ jsxDEV(Badge, {
260
+ className: "ml-auto",
261
+ "data-testid": "mail-count",
262
+ variant: "secondary",
263
+ children: t("{count} messages", {
264
+ count: entries.length
265
+ })
266
+ }, void 0, false)]
267
+ }, void 0, true), error !== null && /* @__PURE__ */ jsxDEV("p", {
268
+ className: "text-sm text-destructive",
269
+ "data-testid": "mail-error",
270
+ role: "alert",
271
+ children: error
272
+ }, void 0, false), error === null && !isLoading && entries.length === 0 && /* @__PURE__ */ jsxDEV(EmptyState, {
273
+ description: t("Email your app sends in dev is captured here — nothing is delivered."),
274
+ icon: /* @__PURE__ */ jsxDEV("svg", {
275
+ "aria-hidden": "true",
276
+ fill: "none",
277
+ stroke: "currentColor",
278
+ strokeLinecap: "round",
279
+ strokeLinejoin: "round",
280
+ strokeWidth: 1.6,
281
+ viewBox: "0 0 24 24",
282
+ children: [/* @__PURE__ */ jsxDEV("path", {
283
+ d: "M4 5h16v14H4z"
284
+ }, void 0, false), /* @__PURE__ */ jsxDEV("path", {
285
+ d: "m4 6 8 6 8-6"
286
+ }, void 0, false)]
287
+ }, void 0, true),
288
+ testId: "mail-empty",
289
+ title: t("No captured email.")
290
+ }, void 0, false), entries.length > 0 && /* @__PURE__ */ jsxDEV("div", {
291
+ className: "grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,22rem)_1fr]",
292
+ "data-testid": "mail-reader",
293
+ children: [/* @__PURE__ */ jsxDEV("div", {
294
+ className: "flex flex-col gap-2",
295
+ children: [/* @__PURE__ */ jsxDEV(Input, {
296
+ "aria-label": t("Search messages"),
297
+ "data-testid": "mail-search",
298
+ onChange: onFilterChange,
299
+ placeholder: t("Filter messages"),
300
+ type: "search",
301
+ value: filter
302
+ }, void 0, false), /* @__PURE__ */ jsxDEV(ScrollArea, {
303
+ className: "max-h-[32rem] rounded-xl border border-border bg-card shadow-xs",
304
+ "data-testid": "mail-list",
305
+ children: /* @__PURE__ */ jsxDEV("ul", {
306
+ className: "divide-y",
307
+ children: visible.map((entry_1) => {
308
+ const isActive = entry_1.id === selected?.id;
309
+ return /* @__PURE__ */ jsxDEV("li", {
310
+ children: /* @__PURE__ */ jsxDEV("button", {
311
+ className: `flex w-full flex-col items-start gap-0.5 px-3 py-2 text-left text-sm hover:bg-muted/60 ${isActive ? "bg-muted" : ""}`,
312
+ "data-active": isActive,
313
+ "data-id": entry_1.id,
314
+ "data-testid": "mail-list-item",
315
+ onClick: onSelectMessage,
316
+ type: "button",
317
+ children: [/* @__PURE__ */ jsxDEV("span", {
318
+ className: "w-full truncate font-medium",
319
+ children: entry_1.subject || t("(no subject)")
320
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
321
+ className: "w-full truncate text-xs text-muted-foreground",
322
+ children: recipientText(entry_1.to)
323
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
324
+ className: "text-xs text-muted-foreground tabular-nums",
325
+ children: formatTimestamp(entry_1.capturedAt, "—")
326
+ }, void 0, false)]
327
+ }, void 0, true)
328
+ }, entry_1.id, false);
329
+ })
330
+ }, void 0, false)
331
+ }, void 0, false)]
332
+ }, void 0, true), selected !== void 0 && /* @__PURE__ */ jsxDEV("div", {
333
+ className: "flex min-w-0 flex-col gap-3 rounded-xl border border-border bg-card p-4 shadow-xs",
334
+ "data-testid": "mail-detail",
335
+ children: [/* @__PURE__ */ jsxDEV("div", {
336
+ className: "flex flex-col gap-1",
337
+ children: [/* @__PURE__ */ jsxDEV("h2", {
338
+ className: "truncate text-base font-semibold",
339
+ "data-testid": "mail-subject",
340
+ children: selected.subject || t("(no subject)")
341
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dl", {
342
+ className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs text-muted-foreground",
343
+ children: [/* @__PURE__ */ jsxDEV("dt", {
344
+ children: t("From")
345
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
346
+ className: "truncate font-mono",
347
+ children: selected.from ?? "—"
348
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dt", {
349
+ children: t("To")
350
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
351
+ className: "truncate font-mono",
352
+ children: recipientText(selected.to)
353
+ }, void 0, false), selected.cc !== void 0 && selected.cc.length > 0 && /* @__PURE__ */ jsxDEV(Fragment, {
354
+ children: [/* @__PURE__ */ jsxDEV("dt", {
355
+ children: t("Cc")
356
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
357
+ className: "truncate font-mono",
358
+ children: recipientText(selected.cc)
359
+ }, void 0, false)]
360
+ }, void 0, true), /* @__PURE__ */ jsxDEV("dt", {
361
+ children: t("Sent")
362
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
363
+ className: "tabular-nums",
364
+ children: formatTimestamp(selected.capturedAt, "—")
365
+ }, void 0, false)]
366
+ }, void 0, true)]
367
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
368
+ className: "flex flex-wrap items-center gap-2",
369
+ "data-testid": "mail-actions",
370
+ children: [/* @__PURE__ */ jsxDEV(Button, {
371
+ "data-testid": "mail-copy-link",
372
+ disabled: link === void 0,
373
+ onClick: onCopyLink,
374
+ size: "sm",
375
+ type: "button",
376
+ variant: "outline",
377
+ children: t("Copy link")
378
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Button, {
379
+ "data-testid": "mail-open-link",
380
+ disabled: link === void 0,
381
+ onClick: onOpenLink,
382
+ size: "sm",
383
+ type: "button",
384
+ variant: "outline",
385
+ children: t("Open in new tab")
386
+ }, void 0, false)]
387
+ }, void 0, true), /* @__PURE__ */ jsxDEV(Separator, {}, void 0, false), /* @__PURE__ */ jsxDEV("div", {
388
+ className: "flex items-center gap-1",
389
+ "data-testid": "mail-tabs",
390
+ role: "tablist",
391
+ children: ["html", "text", "headers"].map((value_0) => /* @__PURE__ */ jsxDEV(Button, {
392
+ "aria-selected": tab === value_0,
393
+ "data-tab": value_0,
394
+ "data-testid": `mail-tab-${value_0}`,
395
+ onClick: onSelectTab,
396
+ role: "tab",
397
+ size: "sm",
398
+ type: "button",
399
+ variant: tab === value_0 ? "secondary" : "ghost",
400
+ children: tabTitle(value_0)
401
+ }, value_0, false))
402
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
403
+ className: "min-h-[12rem]",
404
+ children: [tab === "html" && (selected.html === void 0 ? /* @__PURE__ */ jsxDEV("p", {
405
+ className: "text-sm text-muted-foreground",
406
+ children: t("No HTML body.")
407
+ }, void 0, false) : (
408
+ // Fully sandboxed (no `allow-scripts`): captured markup renders but
409
+ // cannot execute in the studio. A restrictive CSP additionally blocks
410
+ // remote resource loads (tracking pixels / `@import`) so opening a
411
+ // captured message can't phone home — only inline styles + data: images.
412
+ /* @__PURE__ */ jsxDEV("iframe", {
413
+ className: "h-[24rem] w-full rounded-md border bg-white",
414
+ "data-testid": "mail-preview-html",
415
+ sandbox: "",
416
+ srcDoc: withPreviewCsp(selected.html),
417
+ title: t("Email preview")
418
+ }, void 0, false)
419
+ )), tab === "text" && (selected.text === void 0 ? /* @__PURE__ */ jsxDEV("p", {
420
+ className: "text-sm text-muted-foreground",
421
+ children: t("No text body.")
422
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("pre", {
423
+ className: "overflow-auto whitespace-pre-wrap rounded-md border bg-muted/30 p-3 text-xs",
424
+ "data-testid": "mail-preview-text",
425
+ children: selected.text
426
+ }, void 0, false)), tab === "headers" && (selected.headers === void 0 || Object.keys(selected.headers).length === 0 ? /* @__PURE__ */ jsxDEV("p", {
427
+ className: "text-sm text-muted-foreground",
428
+ children: t("No headers.")
429
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("pre", {
430
+ className: "overflow-auto whitespace-pre-wrap rounded-md border bg-muted/30 p-3 text-xs",
431
+ "data-testid": "mail-preview-headers",
432
+ children: Object.entries(selected.headers).map(([name, value_1]) => `${name}: ${value_1}`).join("\n")
433
+ }, void 0, false))]
434
+ }, void 0, true)]
435
+ }, void 0, true)]
436
+ }, void 0, true)]
437
+ }, void 0, true);
438
+ };
439
+
440
+ export { MailPanel };