@lunora/studio 0.0.0 → 1.0.0-alpha.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 (81) 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 +1402 -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_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.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/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.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-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.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-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,447 @@
1
+ 'use client';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useState, useEffect, 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 { useAutoRefresh } from './DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js';
14
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
15
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
16
+ import { f as fireAndForget, d as formatTimestamp, c as callOptions, e as errorMessage, b as copyToClipboard, a as adminRef } from './internal-BBZYexre.js';
17
+
18
+ function Separator({
19
+ className,
20
+ orientation = "horizontal",
21
+ ...props
22
+ }) {
23
+ return /* @__PURE__ */ jsxDEV(Separator$1, {
24
+ "data-slot": "separator",
25
+ orientation,
26
+ className: cn("shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", className),
27
+ ...props
28
+ }, void 0, false);
29
+ }
30
+
31
+ function ScrollArea(t0) {
32
+ const $ = c(14);
33
+ let children;
34
+ let className;
35
+ let props;
36
+ if ($[0] !== t0) {
37
+ ({
38
+ className,
39
+ children,
40
+ ...props
41
+ } = t0);
42
+ $[0] = t0;
43
+ $[1] = children;
44
+ $[2] = className;
45
+ $[3] = props;
46
+ } else {
47
+ children = $[1];
48
+ className = $[2];
49
+ props = $[3];
50
+ }
51
+ let t1;
52
+ if ($[4] !== className) {
53
+ t1 = cn("relative", className);
54
+ $[4] = className;
55
+ $[5] = t1;
56
+ } else {
57
+ t1 = $[5];
58
+ }
59
+ let t2;
60
+ if ($[6] !== children) {
61
+ t2 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Viewport, {
62
+ "data-slot": "scroll-area-viewport",
63
+ className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
64
+ children
65
+ }, void 0, false);
66
+ $[6] = children;
67
+ $[7] = t2;
68
+ } else {
69
+ t2 = $[7];
70
+ }
71
+ let t3;
72
+ let t4;
73
+ if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
74
+ t3 = /* @__PURE__ */ jsxDEV(ScrollBar, {}, void 0, false);
75
+ t4 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Corner, {}, void 0, false);
76
+ $[8] = t3;
77
+ $[9] = t4;
78
+ } else {
79
+ t3 = $[8];
80
+ t4 = $[9];
81
+ }
82
+ let t5;
83
+ if ($[10] !== props || $[11] !== t1 || $[12] !== t2) {
84
+ t5 = /* @__PURE__ */ jsxDEV(ScrollArea$1.Root, {
85
+ "data-slot": "scroll-area",
86
+ className: t1,
87
+ ...props,
88
+ children: [t2, t3, t4]
89
+ }, void 0, true);
90
+ $[10] = props;
91
+ $[11] = t1;
92
+ $[12] = t2;
93
+ $[13] = t5;
94
+ } else {
95
+ t5 = $[13];
96
+ }
97
+ return t5;
98
+ }
99
+ function ScrollBar({
100
+ className,
101
+ orientation = "vertical",
102
+ ...props
103
+ }) {
104
+ return /* @__PURE__ */ jsxDEV(ScrollArea$1.Scrollbar, {
105
+ "data-slot": "scroll-area-scrollbar",
106
+ "data-orientation": orientation,
107
+ orientation,
108
+ 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),
109
+ ...props,
110
+ children: /* @__PURE__ */ jsxDEV(ScrollArea$1.Thumb, {
111
+ "data-slot": "scroll-area-thumb",
112
+ className: "relative flex-1 rounded-md bg-border"
113
+ }, void 0, false)
114
+ }, void 0, false);
115
+ }
116
+
117
+ const GET_CAPTURED_MAIL = adminRef(ADMIN_FUNCTIONS.getCapturedMail);
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 [entries, setEntries] = useState([]);
148
+ const [error, setError] = useState(null);
149
+ const [selectedId, setSelectedId] = useState(null);
150
+ const [tab, setTab] = useState("html");
151
+ const [filter, setFilter] = useState("");
152
+ const refresh = async () => {
153
+ setError(null);
154
+ try {
155
+ const next = await client.query(GET_CAPTURED_MAIL, {
156
+ limit
157
+ }, callOptions(""));
158
+ setEntries(next.entries);
159
+ } catch (error_) {
160
+ setEntries([]);
161
+ setError(errorMessage(error_));
162
+ }
163
+ };
164
+ const clearInbox = async () => {
165
+ setError(null);
166
+ try {
167
+ await client.query(CLEAR_CAPTURED_MAIL, {}, callOptions(""));
168
+ setSelectedId(null);
169
+ await refresh();
170
+ } catch (error__0) {
171
+ setError(errorMessage(error__0));
172
+ }
173
+ };
174
+ const sendTest = async () => {
175
+ setError(null);
176
+ try {
177
+ await client.query(SEND_TEST_MAIL, {}, callOptions(""));
178
+ await refresh();
179
+ } catch (error__1) {
180
+ setError(errorMessage(error__1));
181
+ }
182
+ };
183
+ useEffect(() => {
184
+ fireAndForget(refresh());
185
+ }, [refresh]);
186
+ const reload = () => {
187
+ fireAndForget(refresh());
188
+ };
189
+ useAutoRefresh(reload, true);
190
+ const onClear = () => {
191
+ fireAndForget(clearInbox());
192
+ };
193
+ const onSendTest = () => {
194
+ fireAndForget(sendTest());
195
+ };
196
+ const onFilterChange = (event) => {
197
+ setFilter(event.currentTarget.value);
198
+ };
199
+ const onSelectMessage = (event_0) => {
200
+ const {
201
+ id
202
+ } = event_0.currentTarget.dataset;
203
+ if (id !== void 0) {
204
+ setSelectedId(id);
205
+ }
206
+ };
207
+ const onSelectTab = (event_1) => {
208
+ const next_0 = event_1.currentTarget.dataset["tab"];
209
+ if (next_0 === "headers" || next_0 === "html" || next_0 === "text") {
210
+ setTab(next_0);
211
+ }
212
+ };
213
+ const tabTitle = (value) => {
214
+ if (value === "html") {
215
+ return t("HTML");
216
+ }
217
+ if (value === "text") {
218
+ return t("Plain text");
219
+ }
220
+ return t("Headers");
221
+ };
222
+ const visible = useMemo(() => {
223
+ const needle = filter.trim().toLowerCase();
224
+ if (needle === "") {
225
+ return entries;
226
+ }
227
+ return entries.filter((entry) => `${entry.subject} ${recipientText(entry.to)}`.toLowerCase().includes(needle));
228
+ }, [entries, filter]);
229
+ const selected = useMemo(() => {
230
+ if (visible.length === 0) {
231
+ return void 0;
232
+ }
233
+ return visible.find((entry_0) => entry_0.id === selectedId) ?? visible[0];
234
+ }, [visible, selectedId]);
235
+ const link = selectedLink(selected);
236
+ const onCopyLink = () => {
237
+ if (link !== void 0) {
238
+ copyToClipboard(link);
239
+ }
240
+ };
241
+ const onOpenLink = () => {
242
+ if (link !== void 0 && "window" in globalThis) {
243
+ globalThis.window.open(link, "_blank", "noopener");
244
+ }
245
+ };
246
+ return /* @__PURE__ */ jsxDEV("div", {
247
+ className: "flex flex-col gap-4",
248
+ "data-testid": "mail-panel",
249
+ children: [/* @__PURE__ */ jsxDEV("div", {
250
+ className: "flex flex-wrap items-center gap-2",
251
+ children: [/* @__PURE__ */ jsxDEV(Button, {
252
+ "data-testid": "mail-send-test",
253
+ onClick: onSendTest,
254
+ size: "sm",
255
+ type: "button",
256
+ variant: "outline",
257
+ children: t("Send test")
258
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Button, {
259
+ "data-testid": "mail-clear",
260
+ disabled: entries.length === 0,
261
+ onClick: onClear,
262
+ size: "sm",
263
+ type: "button",
264
+ variant: "outline",
265
+ children: t("Clear inbox")
266
+ }, void 0, false), entries.length > 0 && /* @__PURE__ */ jsxDEV(Badge, {
267
+ className: "ml-auto",
268
+ "data-testid": "mail-count",
269
+ variant: "secondary",
270
+ children: t("{count} messages", {
271
+ count: entries.length
272
+ })
273
+ }, void 0, false)]
274
+ }, void 0, true), error !== null && /* @__PURE__ */ jsxDEV("p", {
275
+ className: "text-sm text-destructive",
276
+ "data-testid": "mail-error",
277
+ role: "alert",
278
+ children: error
279
+ }, void 0, false), error === null && entries.length === 0 && /* @__PURE__ */ jsxDEV(EmptyState, {
280
+ description: t("Email your app sends in dev is captured here — nothing is delivered."),
281
+ icon: /* @__PURE__ */ jsxDEV("svg", {
282
+ "aria-hidden": "true",
283
+ fill: "none",
284
+ stroke: "currentColor",
285
+ strokeLinecap: "round",
286
+ strokeLinejoin: "round",
287
+ strokeWidth: 1.6,
288
+ viewBox: "0 0 24 24",
289
+ children: [/* @__PURE__ */ jsxDEV("path", {
290
+ d: "M4 5h16v14H4z"
291
+ }, void 0, false), /* @__PURE__ */ jsxDEV("path", {
292
+ d: "m4 6 8 6 8-6"
293
+ }, void 0, false)]
294
+ }, void 0, true),
295
+ testId: "mail-empty",
296
+ title: t("No captured email.")
297
+ }, void 0, false), entries.length > 0 && /* @__PURE__ */ jsxDEV("div", {
298
+ className: "grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,22rem)_1fr]",
299
+ "data-testid": "mail-reader",
300
+ children: [/* @__PURE__ */ jsxDEV("div", {
301
+ className: "flex flex-col gap-2",
302
+ children: [/* @__PURE__ */ jsxDEV(Input, {
303
+ "aria-label": t("Search messages"),
304
+ "data-testid": "mail-search",
305
+ onChange: onFilterChange,
306
+ placeholder: t("Filter messages"),
307
+ type: "search",
308
+ value: filter
309
+ }, void 0, false), /* @__PURE__ */ jsxDEV(ScrollArea, {
310
+ className: "max-h-[32rem] rounded-xl border border-border bg-card shadow-xs",
311
+ "data-testid": "mail-list",
312
+ children: /* @__PURE__ */ jsxDEV("ul", {
313
+ className: "divide-y",
314
+ children: visible.map((entry_1) => {
315
+ const isActive = entry_1.id === selected?.id;
316
+ return /* @__PURE__ */ jsxDEV("li", {
317
+ children: /* @__PURE__ */ jsxDEV("button", {
318
+ 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" : ""}`,
319
+ "data-active": isActive,
320
+ "data-id": entry_1.id,
321
+ "data-testid": "mail-list-item",
322
+ onClick: onSelectMessage,
323
+ type: "button",
324
+ children: [/* @__PURE__ */ jsxDEV("span", {
325
+ className: "w-full truncate font-medium",
326
+ children: entry_1.subject || t("(no subject)")
327
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
328
+ className: "w-full truncate text-xs text-muted-foreground",
329
+ children: recipientText(entry_1.to)
330
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
331
+ className: "text-xs text-muted-foreground tabular-nums",
332
+ children: formatTimestamp(entry_1.capturedAt, "—")
333
+ }, void 0, false)]
334
+ }, void 0, true)
335
+ }, entry_1.id, false);
336
+ })
337
+ }, void 0, false)
338
+ }, void 0, false)]
339
+ }, void 0, true), selected !== void 0 && /* @__PURE__ */ jsxDEV("div", {
340
+ className: "flex min-w-0 flex-col gap-3 rounded-xl border border-border bg-card p-4 shadow-xs",
341
+ "data-testid": "mail-detail",
342
+ children: [/* @__PURE__ */ jsxDEV("div", {
343
+ className: "flex flex-col gap-1",
344
+ children: [/* @__PURE__ */ jsxDEV("h2", {
345
+ className: "truncate text-base font-semibold",
346
+ "data-testid": "mail-subject",
347
+ children: selected.subject || t("(no subject)")
348
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dl", {
349
+ className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs text-muted-foreground",
350
+ children: [/* @__PURE__ */ jsxDEV("dt", {
351
+ children: t("From")
352
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
353
+ className: "truncate font-mono",
354
+ children: selected.from ?? "—"
355
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dt", {
356
+ children: t("To")
357
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
358
+ className: "truncate font-mono",
359
+ children: recipientText(selected.to)
360
+ }, void 0, false), selected.cc !== void 0 && selected.cc.length > 0 && /* @__PURE__ */ jsxDEV(Fragment, {
361
+ children: [/* @__PURE__ */ jsxDEV("dt", {
362
+ children: t("Cc")
363
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
364
+ className: "truncate font-mono",
365
+ children: recipientText(selected.cc)
366
+ }, void 0, false)]
367
+ }, void 0, true), /* @__PURE__ */ jsxDEV("dt", {
368
+ children: t("Sent")
369
+ }, void 0, false), /* @__PURE__ */ jsxDEV("dd", {
370
+ className: "tabular-nums",
371
+ children: formatTimestamp(selected.capturedAt, "—")
372
+ }, void 0, false)]
373
+ }, void 0, true)]
374
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
375
+ className: "flex flex-wrap items-center gap-2",
376
+ "data-testid": "mail-actions",
377
+ children: [/* @__PURE__ */ jsxDEV(Button, {
378
+ "data-testid": "mail-copy-link",
379
+ disabled: link === void 0,
380
+ onClick: onCopyLink,
381
+ size: "sm",
382
+ type: "button",
383
+ variant: "outline",
384
+ children: t("Copy link")
385
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Button, {
386
+ "data-testid": "mail-open-link",
387
+ disabled: link === void 0,
388
+ onClick: onOpenLink,
389
+ size: "sm",
390
+ type: "button",
391
+ variant: "outline",
392
+ children: t("Open in new tab")
393
+ }, void 0, false)]
394
+ }, void 0, true), /* @__PURE__ */ jsxDEV(Separator, {}, void 0, false), /* @__PURE__ */ jsxDEV("div", {
395
+ className: "flex items-center gap-1",
396
+ "data-testid": "mail-tabs",
397
+ role: "tablist",
398
+ children: ["html", "text", "headers"].map((value_0) => /* @__PURE__ */ jsxDEV(Button, {
399
+ "aria-selected": tab === value_0,
400
+ "data-tab": value_0,
401
+ "data-testid": `mail-tab-${value_0}`,
402
+ onClick: onSelectTab,
403
+ role: "tab",
404
+ size: "sm",
405
+ type: "button",
406
+ variant: tab === value_0 ? "secondary" : "ghost",
407
+ children: tabTitle(value_0)
408
+ }, value_0, false))
409
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
410
+ className: "min-h-[12rem]",
411
+ children: [tab === "html" && (selected.html === void 0 ? /* @__PURE__ */ jsxDEV("p", {
412
+ className: "text-sm text-muted-foreground",
413
+ children: t("No HTML body.")
414
+ }, void 0, false) : (
415
+ // Fully sandboxed (no `allow-scripts`): captured markup renders but
416
+ // cannot execute in the studio. A restrictive CSP additionally blocks
417
+ // remote resource loads (tracking pixels / `@import`) so opening a
418
+ // captured message can't phone home — only inline styles + data: images.
419
+ /* @__PURE__ */ jsxDEV("iframe", {
420
+ className: "h-[24rem] w-full rounded-md border bg-white",
421
+ "data-testid": "mail-preview-html",
422
+ sandbox: "",
423
+ srcDoc: withPreviewCsp(selected.html),
424
+ title: t("Email preview")
425
+ }, void 0, false)
426
+ )), tab === "text" && (selected.text === void 0 ? /* @__PURE__ */ jsxDEV("p", {
427
+ className: "text-sm text-muted-foreground",
428
+ children: t("No text body.")
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-text",
432
+ children: selected.text
433
+ }, void 0, false)), tab === "headers" && (selected.headers === void 0 || Object.keys(selected.headers).length === 0 ? /* @__PURE__ */ jsxDEV("p", {
434
+ className: "text-sm text-muted-foreground",
435
+ children: t("No headers.")
436
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("pre", {
437
+ className: "overflow-auto whitespace-pre-wrap rounded-md border bg-muted/30 p-3 text-xs",
438
+ "data-testid": "mail-preview-headers",
439
+ children: Object.entries(selected.headers).map(([name, value_1]) => `${name}: ${value_1}`).join("\n")
440
+ }, void 0, false))]
441
+ }, void 0, true)]
442
+ }, void 0, true)]
443
+ }, void 0, true)]
444
+ }, void 0, true);
445
+ };
446
+
447
+ export { MailPanel };