@mugenlabs/logtape-devtools 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1234 @@
1
+ // src/store.ts
2
+ function createLogStore(maxSize = 1e3) {
3
+ let records = [];
4
+ let max = maxSize;
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ function notify() {
7
+ for (const listener of listeners) {
8
+ listener();
9
+ }
10
+ }
11
+ return {
12
+ getSnapshot: () => records,
13
+ addRecord: (record) => {
14
+ records = [...records, record];
15
+ if (records.length > max) {
16
+ records = records.slice(records.length - max);
17
+ }
18
+ notify();
19
+ },
20
+ clear: () => {
21
+ records = [];
22
+ notify();
23
+ },
24
+ subscribe: (listener) => {
25
+ listeners.add(listener);
26
+ return () => {
27
+ listeners.delete(listener);
28
+ };
29
+ },
30
+ setMaxSize: (size) => {
31
+ max = size;
32
+ if (records.length > max) {
33
+ records = records.slice(records.length - max);
34
+ notify();
35
+ }
36
+ }
37
+ };
38
+ }
39
+ var defaultLogStore = createLogStore();
40
+
41
+ // src/plugin/logtape-devtools-plugin.tsx
42
+ import { useCallback as useCallback3, useMemo as useMemo2, useState as useState2, useSyncExternalStore } from "react";
43
+
44
+ // src/plugin/components/log-list.tsx
45
+ import { useEffect, useRef } from "react";
46
+
47
+ // src/plugin/theme.ts
48
+ var theme = {
49
+ colors: {
50
+ background: "#1e1e1e",
51
+ surface: "#1a1a1a",
52
+ surfaceHover: "#2a2a2a",
53
+ surfaceSelected: "#2a2a3a",
54
+ border: "#333",
55
+ borderInput: "#444",
56
+ borderFocus: "#6366f1",
57
+ textPrimary: "#e0e0e0",
58
+ textSecondary: "#888",
59
+ textMuted: "#666",
60
+ textDimmed: "#555",
61
+ white: "#fff",
62
+ accent: "#6366f1",
63
+ accentHover: "#818cf8",
64
+ levels: {
65
+ trace: { bg: "#1a1a2e", color: "#8888bb", badge: "#5555aa" },
66
+ debug: { bg: "#1a2a1a", color: "#88bb88", badge: "#55aa55" },
67
+ info: { bg: "#1a2a3a", color: "#60a5fa", badge: "#3b82f6" },
68
+ warning: { bg: "#2a2a1a", color: "#facc15", badge: "#eab308" },
69
+ error: { bg: "#2a1a1a", color: "#ef4444", badge: "#dc2626" },
70
+ fatal: { bg: "#3a0a0a", color: "#ff6b6b", badge: "#b91c1c" }
71
+ },
72
+ scrollbar: "#444",
73
+ scrollbarHover: "#555"
74
+ },
75
+ fontFamily: {
76
+ mono: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
77
+ sans: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
78
+ },
79
+ fontSize: {
80
+ xs: "9px",
81
+ sm: "10px",
82
+ md: "11px",
83
+ base: "12px",
84
+ lg: "13px"
85
+ },
86
+ radius: {
87
+ sm: "3px",
88
+ md: "4px",
89
+ lg: "6px",
90
+ pill: "10px"
91
+ },
92
+ spacing: {
93
+ xs: "2px",
94
+ sm: "4px",
95
+ md: "6px",
96
+ lg: "8px",
97
+ xl: "12px",
98
+ xxl: "16px"
99
+ }
100
+ };
101
+
102
+ // src/plugin/components/log-row.tsx
103
+ import { useCallback } from "react";
104
+
105
+ // src/plugin/icons.tsx
106
+ import { jsx, jsxs } from "react/jsx-runtime";
107
+ var defaults = { size: 14, color: "currentColor" };
108
+ var Svg = ({
109
+ size = defaults.size,
110
+ color = defaults.color,
111
+ style,
112
+ children
113
+ }) => /* @__PURE__ */ jsx(
114
+ "svg",
115
+ {
116
+ "aria-hidden": "true",
117
+ fill: "none",
118
+ height: size,
119
+ stroke: color,
120
+ strokeLinecap: "round",
121
+ strokeLinejoin: "round",
122
+ strokeWidth: 2,
123
+ style,
124
+ viewBox: "0 0 24 24",
125
+ width: size,
126
+ xmlns: "http://www.w3.org/2000/svg",
127
+ children
128
+ }
129
+ );
130
+ var PlayIcon = (props) => /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" }) });
131
+ var PauseIcon = (props) => /* @__PURE__ */ jsxs(Svg, { ...props, children: [
132
+ /* @__PURE__ */ jsx("rect", { height: 18, rx: 1, width: 5, x: 14, y: 3 }),
133
+ /* @__PURE__ */ jsx("rect", { height: 18, rx: 1, width: 5, x: 5, y: 3 })
134
+ ] });
135
+ var XIcon = (props) => /* @__PURE__ */ jsxs(Svg, { ...props, children: [
136
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
137
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
138
+ ] });
139
+ var CheckIcon = (props) => /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
140
+ var ChevronRightIcon = (props) => /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
141
+ var PanelLeftCloseIcon = (props) => /* @__PURE__ */ jsxs(Svg, { ...props, children: [
142
+ /* @__PURE__ */ jsx("rect", { height: 18, rx: 2, width: 18, x: 3, y: 3 }),
143
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
144
+ /* @__PURE__ */ jsx("path", { d: "m16 15-3-3 3-3" })
145
+ ] });
146
+ var PanelLeftOpenIcon = (props) => /* @__PURE__ */ jsxs(Svg, { ...props, children: [
147
+ /* @__PURE__ */ jsx("rect", { height: 18, rx: 2, width: 18, x: 3, y: 3 }),
148
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
149
+ /* @__PURE__ */ jsx("path", { d: "m14 9 3 3-3 3" })
150
+ ] });
151
+ var Trash2Icon = (props) => /* @__PURE__ */ jsxs(Svg, { ...props, children: [
152
+ /* @__PURE__ */ jsx("path", { d: "M10 11v6" }),
153
+ /* @__PURE__ */ jsx("path", { d: "M14 11v6" }),
154
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
155
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
156
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
157
+ ] });
158
+
159
+ // src/plugin/components/level-badge.tsx
160
+ import { jsx as jsx2 } from "react/jsx-runtime";
161
+ var LEVEL_LABELS = {
162
+ trace: "TRC",
163
+ debug: "DBG",
164
+ info: "INF",
165
+ warning: "WRN",
166
+ error: "ERR",
167
+ fatal: "FTL"
168
+ };
169
+ var LevelBadge = ({ level }) => {
170
+ const colors = theme.colors.levels[level];
171
+ return /* @__PURE__ */ jsx2(
172
+ "span",
173
+ {
174
+ style: {
175
+ background: colors.badge,
176
+ borderRadius: theme.radius.sm,
177
+ color: theme.colors.white,
178
+ display: "inline-block",
179
+ fontFamily: theme.fontFamily.mono,
180
+ fontSize: theme.fontSize.sm,
181
+ fontWeight: 600,
182
+ lineHeight: 1,
183
+ minWidth: "28px",
184
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
185
+ textAlign: "center",
186
+ textTransform: "uppercase"
187
+ },
188
+ children: LEVEL_LABELS[level]
189
+ }
190
+ );
191
+ };
192
+
193
+ // src/plugin/components/log-detail.tsx
194
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
195
+ function formatTimestamp(ts) {
196
+ const d = new Date(ts);
197
+ return d.toLocaleTimeString(void 0, {
198
+ hour12: false,
199
+ hour: "2-digit",
200
+ minute: "2-digit",
201
+ second: "2-digit"
202
+ });
203
+ }
204
+ function safeStringify(value, indent = 2) {
205
+ try {
206
+ return JSON.stringify(value, null, indent);
207
+ } catch {
208
+ return String(value);
209
+ }
210
+ }
211
+ var DetailRow = ({ label, children }) => /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: theme.spacing.lg, marginBottom: theme.spacing.md }, children: [
212
+ /* @__PURE__ */ jsx3(
213
+ "span",
214
+ {
215
+ style: {
216
+ color: theme.colors.textMuted,
217
+ flexShrink: 0,
218
+ fontSize: theme.fontSize.md,
219
+ minWidth: "72px",
220
+ textAlign: "right"
221
+ },
222
+ children: label
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: theme.fontSize.base, wordBreak: "break-all" }, children })
226
+ ] });
227
+ var LogDetail = ({ record }) => {
228
+ const levelColors = theme.colors.levels[record.level];
229
+ const hasProperties = Object.keys(record.properties).length > 0;
230
+ return /* @__PURE__ */ jsxs2(
231
+ "div",
232
+ {
233
+ "data-testid": "log-detail",
234
+ style: {
235
+ background: levelColors.bg,
236
+ borderTop: `1px solid ${theme.colors.border}`,
237
+ padding: `${theme.spacing.lg} ${theme.spacing.xl}`
238
+ },
239
+ children: [
240
+ /* @__PURE__ */ jsx3(DetailRow, { label: "Time", children: formatTimestamp(record.timestamp) }),
241
+ /* @__PURE__ */ jsx3(DetailRow, { label: "Level", children: /* @__PURE__ */ jsx3("span", { style: { color: levelColors.color, fontWeight: 600 }, children: record.level }) }),
242
+ /* @__PURE__ */ jsx3(DetailRow, { label: "Category", children: /* @__PURE__ */ jsx3("span", { style: { color: theme.colors.accent, fontFamily: theme.fontFamily.mono }, children: record.category.join(".") }) }),
243
+ record.caller && /* @__PURE__ */ jsx3(DetailRow, { label: "Caller", children: /* @__PURE__ */ jsx3("span", { style: { color: theme.colors.textSecondary, fontFamily: theme.fontFamily.mono }, children: record.caller }) }),
244
+ /* @__PURE__ */ jsx3(DetailRow, { label: "Message", children: /* @__PURE__ */ jsx3("span", { style: { color: theme.colors.textPrimary }, children: record.messageText }) }),
245
+ hasProperties && /* @__PURE__ */ jsx3(DetailRow, { label: "Data", children: /* @__PURE__ */ jsx3(
246
+ "pre",
247
+ {
248
+ style: {
249
+ background: "rgba(0,0,0,0.3)",
250
+ borderRadius: theme.radius.md,
251
+ color: theme.colors.textPrimary,
252
+ fontFamily: theme.fontFamily.mono,
253
+ fontSize: theme.fontSize.md,
254
+ margin: 0,
255
+ maxHeight: "200px",
256
+ overflow: "auto",
257
+ padding: theme.spacing.lg,
258
+ whiteSpace: "pre-wrap",
259
+ wordBreak: "break-all"
260
+ },
261
+ children: safeStringify(record.properties)
262
+ }
263
+ ) })
264
+ ]
265
+ }
266
+ );
267
+ };
268
+
269
+ // src/plugin/components/log-row.tsx
270
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
271
+ function formatTime(ts) {
272
+ const d = new Date(ts);
273
+ return d.toLocaleTimeString(void 0, {
274
+ hour12: false,
275
+ hour: "2-digit",
276
+ minute: "2-digit",
277
+ second: "2-digit"
278
+ });
279
+ }
280
+ var LogRow = ({ record, expanded, onToggle }) => {
281
+ const levelColors = theme.colors.levels[record.level];
282
+ const handleClick = useCallback(() => {
283
+ onToggle(expanded ? null : record.id);
284
+ }, [expanded, onToggle, record.id]);
285
+ return /* @__PURE__ */ jsxs3("div", { children: [
286
+ /* @__PURE__ */ jsxs3(
287
+ "button",
288
+ {
289
+ "aria-expanded": expanded,
290
+ "data-lt-interactive": "",
291
+ "data-testid": "log-row",
292
+ onClick: handleClick,
293
+ style: {
294
+ alignItems: "center",
295
+ background: expanded ? levelColors.bg : "transparent",
296
+ border: "none",
297
+ borderBottom: `1px solid ${theme.colors.border}`,
298
+ cursor: "pointer",
299
+ display: "flex",
300
+ gap: theme.spacing.lg,
301
+ padding: `${theme.spacing.md} ${theme.spacing.xl}`,
302
+ textAlign: "left",
303
+ transition: "background 0.1s, filter 0.1s",
304
+ width: "100%"
305
+ },
306
+ type: "button",
307
+ children: [
308
+ /* @__PURE__ */ jsx4(
309
+ "span",
310
+ {
311
+ style: {
312
+ color: theme.colors.textDimmed,
313
+ flexShrink: 0,
314
+ fontFamily: theme.fontFamily.mono,
315
+ fontSize: theme.fontSize.md
316
+ },
317
+ children: formatTime(record.timestamp)
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsx4(LevelBadge, { level: record.level }),
321
+ /* @__PURE__ */ jsx4(
322
+ "span",
323
+ {
324
+ style: {
325
+ color: theme.colors.accent,
326
+ flexShrink: 0,
327
+ fontFamily: theme.fontFamily.mono,
328
+ fontSize: theme.fontSize.md,
329
+ maxWidth: "180px",
330
+ overflow: "hidden",
331
+ textOverflow: "ellipsis",
332
+ whiteSpace: "nowrap"
333
+ },
334
+ children: record.category.join(".")
335
+ }
336
+ ),
337
+ /* @__PURE__ */ jsx4(
338
+ "span",
339
+ {
340
+ style: {
341
+ color: theme.colors.textPrimary,
342
+ flex: 1,
343
+ fontSize: theme.fontSize.base,
344
+ overflow: "hidden",
345
+ textOverflow: "ellipsis",
346
+ whiteSpace: "nowrap"
347
+ },
348
+ children: record.messageText
349
+ }
350
+ ),
351
+ record.caller && /* @__PURE__ */ jsx4(
352
+ "span",
353
+ {
354
+ style: {
355
+ color: theme.colors.textDimmed,
356
+ flexShrink: 0,
357
+ fontFamily: theme.fontFamily.mono,
358
+ fontSize: theme.fontSize.sm,
359
+ maxWidth: "200px",
360
+ opacity: 0.7,
361
+ overflow: "hidden",
362
+ textOverflow: "ellipsis",
363
+ whiteSpace: "nowrap"
364
+ },
365
+ children: record.caller
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsx4(
369
+ "span",
370
+ {
371
+ style: {
372
+ color: theme.colors.textDimmed,
373
+ display: "flex",
374
+ flexShrink: 0,
375
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
376
+ transition: "transform 0.15s"
377
+ },
378
+ children: /* @__PURE__ */ jsx4(ChevronRightIcon, { size: 12 })
379
+ }
380
+ )
381
+ ]
382
+ }
383
+ ),
384
+ expanded && /* @__PURE__ */ jsx4(LogDetail, { record })
385
+ ] });
386
+ };
387
+
388
+ // src/plugin/components/log-list.tsx
389
+ import { jsx as jsx5 } from "react/jsx-runtime";
390
+ var LogList = ({ records, expandedId, onToggle, autoScroll }) => {
391
+ const containerRef = useRef(null);
392
+ const isAtBottomRef = useRef(true);
393
+ const recordCount = records.length;
394
+ useEffect(() => {
395
+ const el = containerRef.current;
396
+ if (!(el && autoScroll && isAtBottomRef.current)) {
397
+ return;
398
+ }
399
+ el.scrollTop = el.scrollHeight;
400
+ }, [autoScroll, recordCount]);
401
+ const handleScroll = () => {
402
+ const el = containerRef.current;
403
+ if (!el) {
404
+ return;
405
+ }
406
+ isAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
407
+ };
408
+ if (records.length === 0) {
409
+ return /* @__PURE__ */ jsx5(
410
+ "div",
411
+ {
412
+ "data-testid": "log-list-empty",
413
+ style: {
414
+ alignItems: "center",
415
+ color: theme.colors.textMuted,
416
+ display: "flex",
417
+ flex: 1,
418
+ fontSize: theme.fontSize.lg,
419
+ justifyContent: "center"
420
+ },
421
+ children: "No logs yet"
422
+ }
423
+ );
424
+ }
425
+ return /* @__PURE__ */ jsx5(
426
+ "div",
427
+ {
428
+ "data-testid": "log-list",
429
+ onScroll: handleScroll,
430
+ ref: containerRef,
431
+ style: {
432
+ flex: 1,
433
+ overflow: "auto",
434
+ scrollbarColor: `${theme.colors.scrollbar} transparent`,
435
+ scrollbarWidth: "thin"
436
+ },
437
+ children: records.map((record) => /* @__PURE__ */ jsx5(
438
+ LogRow,
439
+ {
440
+ expanded: expandedId === record.id,
441
+ onToggle,
442
+ record
443
+ },
444
+ record.id
445
+ ))
446
+ }
447
+ );
448
+ };
449
+
450
+ // src/plugin/components/toolbar.tsx
451
+ import { Combobox } from "@base-ui-components/react/combobox";
452
+ import { Popover } from "@base-ui-components/react/popover";
453
+ import { Debouncer } from "@tanstack/pacer";
454
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState } from "react";
455
+
456
+ // src/types.ts
457
+ var LOG_LEVELS = ["trace", "debug", "info", "warning", "error", "fatal"];
458
+
459
+ // src/plugin/components/toolbar.tsx
460
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
461
+ var HOVER_STYLE_ID = "logtape-devtools-hover";
462
+ function useHoverStyles() {
463
+ useEffect2(() => {
464
+ if (document.getElementById(HOVER_STYLE_ID)) {
465
+ return;
466
+ }
467
+ const style = document.createElement("style");
468
+ style.id = HOVER_STYLE_ID;
469
+ style.textContent = `
470
+ [data-lt-interactive]:hover {
471
+ filter: brightness(1.25);
472
+ }
473
+ [data-lt-interactive]:active {
474
+ filter: brightness(0.9);
475
+ }
476
+ [data-lt-clear-btn]:hover {
477
+ color: ${theme.colors.textPrimary} !important;
478
+ }
479
+ [data-lt-chip-remove]:hover {
480
+ opacity: 1 !important;
481
+ color: ${theme.colors.white} !important;
482
+ }
483
+ `;
484
+ document.head.appendChild(style);
485
+ }, []);
486
+ }
487
+ var buttonStyle = {
488
+ background: theme.colors.surfaceHover,
489
+ border: `1px solid ${theme.colors.border}`,
490
+ borderRadius: theme.radius.md,
491
+ color: theme.colors.textPrimary,
492
+ cursor: "pointer",
493
+ fontFamily: theme.fontFamily.sans,
494
+ fontSize: theme.fontSize.md,
495
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
496
+ transition: "filter 0.1s, background 0.1s, border-color 0.1s",
497
+ width: "100%"
498
+ };
499
+ var inputStyle = {
500
+ background: theme.colors.surface,
501
+ border: `1px solid ${theme.colors.borderInput}`,
502
+ borderRadius: theme.radius.md,
503
+ color: theme.colors.textPrimary,
504
+ fontFamily: theme.fontFamily.sans,
505
+ fontSize: theme.fontSize.md,
506
+ outline: "none",
507
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
508
+ transition: "border-color 0.15s",
509
+ width: "100%"
510
+ };
511
+ var sectionLabelStyle = {
512
+ color: theme.colors.textMuted,
513
+ fontSize: "9px",
514
+ fontWeight: 600,
515
+ letterSpacing: "0.05em",
516
+ textTransform: "uppercase"
517
+ };
518
+ var LevelToggle = ({
519
+ level,
520
+ active,
521
+ onToggle
522
+ }) => {
523
+ const colors = theme.colors.levels[level];
524
+ const handleClick = useCallback2(() => onToggle(level), [level, onToggle]);
525
+ return /* @__PURE__ */ jsx6(
526
+ "button",
527
+ {
528
+ "aria-pressed": active,
529
+ "data-lt-interactive": "",
530
+ "data-testid": `level-toggle-${level}`,
531
+ onClick: handleClick,
532
+ style: {
533
+ background: active ? colors.badge : "transparent",
534
+ border: `1px solid ${active ? colors.badge : theme.colors.border}`,
535
+ borderRadius: theme.radius.sm,
536
+ color: active ? theme.colors.white : colors.color,
537
+ cursor: "pointer",
538
+ fontFamily: theme.fontFamily.mono,
539
+ fontSize: theme.fontSize.sm,
540
+ fontWeight: 600,
541
+ lineHeight: 1,
542
+ minWidth: "30px",
543
+ opacity: active ? 1 : 0.5,
544
+ padding: "3px 0",
545
+ textAlign: "center",
546
+ transition: "all 0.15s"
547
+ },
548
+ title: `Filter ${level} logs`,
549
+ type: "button",
550
+ children: level.slice(0, 3).toUpperCase()
551
+ }
552
+ );
553
+ };
554
+ var Toolbar = ({
555
+ paused,
556
+ onPause,
557
+ onResume,
558
+ onClear,
559
+ levelFilter,
560
+ onLevelFilterChange,
561
+ categories,
562
+ categoryFilter,
563
+ onCategoryFilterChange,
564
+ searchText,
565
+ onSearchTextChange,
566
+ totalCount,
567
+ filteredCount
568
+ }) => {
569
+ useHoverStyles();
570
+ const [localSearch, setLocalSearch] = useState(searchText);
571
+ const debouncedSearch = useMemo(
572
+ () => new Debouncer(onSearchTextChange, { wait: 200 }),
573
+ [onSearchTextChange]
574
+ );
575
+ const handleSearchChange = useCallback2(
576
+ (e) => {
577
+ const value = e.target.value;
578
+ setLocalSearch(value);
579
+ debouncedSearch.maybeExecute(value);
580
+ },
581
+ [debouncedSearch]
582
+ );
583
+ useEffect2(() => {
584
+ if (searchText === "" && localSearch !== "") {
585
+ setLocalSearch("");
586
+ }
587
+ }, [searchText, localSearch]);
588
+ const handleLevelToggle = useCallback2(
589
+ (level) => {
590
+ const next = new Set(levelFilter);
591
+ if (next.has(level)) {
592
+ next.delete(level);
593
+ } else {
594
+ next.add(level);
595
+ }
596
+ onLevelFilterChange(next);
597
+ },
598
+ [levelFilter, onLevelFilterChange]
599
+ );
600
+ return /* @__PURE__ */ jsxs4(
601
+ "div",
602
+ {
603
+ "data-testid": "toolbar",
604
+ style: {
605
+ display: "flex",
606
+ flexDirection: "column",
607
+ flexShrink: 0,
608
+ gap: theme.spacing.xl,
609
+ height: "100%",
610
+ minWidth: "200px",
611
+ overflow: "auto",
612
+ padding: `${theme.spacing.lg} ${theme.spacing.xl}`
613
+ },
614
+ children: [
615
+ /* @__PURE__ */ jsxs4("div", { style: { textAlign: "center" }, children: [
616
+ /* @__PURE__ */ jsx6(
617
+ "span",
618
+ {
619
+ style: {
620
+ color: theme.colors.textPrimary,
621
+ fontFamily: theme.fontFamily.mono,
622
+ fontSize: theme.fontSize.lg,
623
+ fontWeight: 600
624
+ },
625
+ children: filteredCount === totalCount ? `${totalCount}` : `${filteredCount} / ${totalCount}`
626
+ }
627
+ ),
628
+ /* @__PURE__ */ jsx6("div", { style: { color: theme.colors.textMuted, fontSize: theme.fontSize.sm }, children: "logs" })
629
+ ] }),
630
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: theme.spacing.sm }, children: [
631
+ /* @__PURE__ */ jsx6("div", { style: sectionLabelStyle, children: "Actions" }),
632
+ /* @__PURE__ */ jsx6(
633
+ "button",
634
+ {
635
+ "data-lt-interactive": "",
636
+ onClick: paused ? onResume : onPause,
637
+ style: {
638
+ ...buttonStyle,
639
+ background: paused ? theme.colors.levels.warning.badge : buttonStyle.background,
640
+ color: paused ? theme.colors.white : buttonStyle.color
641
+ },
642
+ title: paused ? "Resume live updates" : "Pause live updates",
643
+ type: "button",
644
+ children: /* @__PURE__ */ jsxs4("span", { style: { alignItems: "center", display: "inline-flex", gap: "6px" }, children: [
645
+ paused ? /* @__PURE__ */ jsx6(PlayIcon, { size: 12 }) : /* @__PURE__ */ jsx6(PauseIcon, { size: 12 }),
646
+ paused ? "Resume" : "Pause"
647
+ ] })
648
+ }
649
+ ),
650
+ /* @__PURE__ */ jsx6(
651
+ "button",
652
+ {
653
+ "data-lt-interactive": "",
654
+ onClick: onClear,
655
+ style: buttonStyle,
656
+ title: "Clear all logs",
657
+ type: "button",
658
+ children: /* @__PURE__ */ jsxs4("span", { style: { alignItems: "center", display: "inline-flex", gap: "6px" }, children: [
659
+ /* @__PURE__ */ jsx6(Trash2Icon, { size: 12 }),
660
+ "Clear"
661
+ ] })
662
+ }
663
+ )
664
+ ] }),
665
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: theme.spacing.sm }, children: [
666
+ /* @__PURE__ */ jsxs4("div", { style: { alignItems: "center", display: "flex", justifyContent: "space-between" }, children: [
667
+ /* @__PURE__ */ jsx6("div", { style: sectionLabelStyle, children: "Levels" }),
668
+ levelFilter.size > 0 && /* @__PURE__ */ jsx6(
669
+ "button",
670
+ {
671
+ "data-lt-clear-btn": "",
672
+ onClick: () => onLevelFilterChange(/* @__PURE__ */ new Set()),
673
+ style: {
674
+ background: "none",
675
+ border: "none",
676
+ color: theme.colors.textMuted,
677
+ cursor: "pointer",
678
+ fontSize: "9px",
679
+ padding: 0,
680
+ transition: "color 0.1s"
681
+ },
682
+ title: "Show all levels",
683
+ type: "button",
684
+ children: "clear"
685
+ }
686
+ )
687
+ ] }),
688
+ /* @__PURE__ */ jsx6(
689
+ "div",
690
+ {
691
+ style: {
692
+ display: "grid",
693
+ gap: theme.spacing.xs,
694
+ gridTemplateColumns: "repeat(3, 1fr)"
695
+ },
696
+ children: LOG_LEVELS.map((level) => /* @__PURE__ */ jsx6(
697
+ LevelToggle,
698
+ {
699
+ active: levelFilter.size === 0 || levelFilter.has(level),
700
+ level,
701
+ onToggle: handleLevelToggle
702
+ },
703
+ level
704
+ ))
705
+ }
706
+ )
707
+ ] }),
708
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: theme.spacing.sm }, children: [
709
+ /* @__PURE__ */ jsxs4("div", { style: { alignItems: "center", display: "flex", justifyContent: "space-between" }, children: [
710
+ /* @__PURE__ */ jsx6("div", { style: sectionLabelStyle, children: "Category" }),
711
+ categoryFilter.length > 0 && /* @__PURE__ */ jsx6(
712
+ "button",
713
+ {
714
+ "data-lt-clear-btn": "",
715
+ onClick: () => onCategoryFilterChange([]),
716
+ style: {
717
+ background: "none",
718
+ border: "none",
719
+ color: theme.colors.textMuted,
720
+ cursor: "pointer",
721
+ fontSize: "9px",
722
+ padding: 0,
723
+ transition: "color 0.1s"
724
+ },
725
+ title: "Clear all categories",
726
+ type: "button",
727
+ children: "clear"
728
+ }
729
+ )
730
+ ] }),
731
+ categories.length === 0 ? /* @__PURE__ */ jsxs4(Popover.Root, { children: [
732
+ /* @__PURE__ */ jsx6(
733
+ Popover.Trigger,
734
+ {
735
+ delay: 300,
736
+ nativeButton: false,
737
+ openOnHover: true,
738
+ render: /* @__PURE__ */ jsx6(
739
+ "div",
740
+ {
741
+ "data-testid": "category-input",
742
+ style: {
743
+ ...inputStyle,
744
+ color: theme.colors.textMuted,
745
+ cursor: "not-allowed",
746
+ opacity: 0.5
747
+ },
748
+ children: "Category\u2026"
749
+ }
750
+ )
751
+ }
752
+ ),
753
+ /* @__PURE__ */ jsx6(Popover.Portal, { children: /* @__PURE__ */ jsx6(Popover.Positioner, { sideOffset: 6, style: { zIndex: 999999 }, children: /* @__PURE__ */ jsx6(
754
+ Popover.Popup,
755
+ {
756
+ style: {
757
+ background: theme.colors.surface,
758
+ border: `1px solid ${theme.colors.border}`,
759
+ borderRadius: theme.radius.md,
760
+ boxShadow: "0 8px 24px rgba(0,0,0,0.5)",
761
+ fontSize: theme.fontSize.sm,
762
+ maxWidth: "200px",
763
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`
764
+ },
765
+ children: /* @__PURE__ */ jsx6("p", { style: { color: theme.colors.textMuted, lineHeight: 1.4, margin: 0 }, children: "Waiting for logs to populate available categories." })
766
+ }
767
+ ) }) })
768
+ ] }) : /* @__PURE__ */ jsxs4(
769
+ Combobox.Root,
770
+ {
771
+ items: categories,
772
+ multiple: true,
773
+ onValueChange: onCategoryFilterChange,
774
+ value: categoryFilter,
775
+ children: [
776
+ /* @__PURE__ */ jsxs4(
777
+ "div",
778
+ {
779
+ style: {
780
+ background: theme.colors.surface,
781
+ border: `1px solid ${theme.colors.borderInput}`,
782
+ borderRadius: theme.radius.md,
783
+ display: "flex",
784
+ flexWrap: "wrap",
785
+ gap: "3px",
786
+ minHeight: "28px",
787
+ padding: "3px",
788
+ transition: "border-color 0.15s"
789
+ },
790
+ children: [
791
+ categoryFilter.map((cat) => /* @__PURE__ */ jsxs4(
792
+ "span",
793
+ {
794
+ style: {
795
+ alignItems: "center",
796
+ background: "rgba(99, 102, 241, 0.15)",
797
+ border: "1px solid rgba(99, 102, 241, 0.3)",
798
+ borderRadius: theme.radius.sm,
799
+ color: theme.colors.accentHover,
800
+ display: "inline-flex",
801
+ fontFamily: theme.fontFamily.mono,
802
+ fontSize: "9px",
803
+ gap: "3px",
804
+ lineHeight: 1,
805
+ padding: "1px 4px"
806
+ },
807
+ children: [
808
+ cat,
809
+ /* @__PURE__ */ jsx6(
810
+ "button",
811
+ {
812
+ "data-lt-chip-remove": "",
813
+ onClick: () => onCategoryFilterChange(categoryFilter.filter((c) => c !== cat)),
814
+ style: {
815
+ background: "none",
816
+ border: "none",
817
+ color: theme.colors.textMuted,
818
+ cursor: "pointer",
819
+ fontSize: "7px",
820
+ lineHeight: 1,
821
+ opacity: 0.7,
822
+ padding: 0,
823
+ transition: "color 0.1s, opacity 0.1s"
824
+ },
825
+ title: `Remove ${cat}`,
826
+ type: "button",
827
+ children: /* @__PURE__ */ jsx6(XIcon, { size: 7 })
828
+ }
829
+ )
830
+ ]
831
+ },
832
+ cat
833
+ )),
834
+ /* @__PURE__ */ jsx6(
835
+ Combobox.Input,
836
+ {
837
+ "data-testid": "category-input",
838
+ placeholder: categoryFilter.length === 0 ? "Category\u2026" : "",
839
+ style: {
840
+ background: "transparent",
841
+ border: "none",
842
+ color: theme.colors.textPrimary,
843
+ flex: 1,
844
+ fontFamily: theme.fontFamily.sans,
845
+ fontSize: theme.fontSize.md,
846
+ minWidth: "40px",
847
+ outline: "none",
848
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`
849
+ }
850
+ }
851
+ )
852
+ ]
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsx6(Combobox.Portal, { children: /* @__PURE__ */ jsx6(Combobox.Positioner, { style: { zIndex: 999999 }, children: /* @__PURE__ */ jsx6(
856
+ Combobox.Popup,
857
+ {
858
+ style: {
859
+ background: theme.colors.surface,
860
+ border: `1px solid ${theme.colors.border}`,
861
+ borderRadius: theme.radius.md,
862
+ boxShadow: "0 8px 24px rgba(0,0,0,0.5)",
863
+ maxHeight: "200px",
864
+ overflow: "auto",
865
+ padding: theme.spacing.xs
866
+ },
867
+ children: /* @__PURE__ */ jsx6(Combobox.List, { children: (item, index) => /* @__PURE__ */ jsxs4(
868
+ Combobox.Item,
869
+ {
870
+ index,
871
+ style: {
872
+ alignItems: "center",
873
+ borderRadius: theme.radius.sm,
874
+ color: theme.colors.textPrimary,
875
+ cursor: "pointer",
876
+ display: "flex",
877
+ fontFamily: theme.fontFamily.mono,
878
+ fontSize: theme.fontSize.md,
879
+ gap: theme.spacing.sm,
880
+ justifyContent: "space-between",
881
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
882
+ transition: "background 0.1s"
883
+ },
884
+ value: item,
885
+ children: [
886
+ /* @__PURE__ */ jsx6("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis" }, children: String(item) }),
887
+ /* @__PURE__ */ jsx6(Combobox.ItemIndicator, { children: /* @__PURE__ */ jsx6(CheckIcon, { color: theme.colors.accent, size: 14 }) })
888
+ ]
889
+ },
890
+ String(item)
891
+ ) })
892
+ }
893
+ ) }) })
894
+ ]
895
+ }
896
+ )
897
+ ] }),
898
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: theme.spacing.sm }, children: [
899
+ /* @__PURE__ */ jsx6("div", { style: sectionLabelStyle, children: "Search" }),
900
+ /* @__PURE__ */ jsx6(
901
+ "input",
902
+ {
903
+ "data-testid": "search-input",
904
+ onChange: handleSearchChange,
905
+ placeholder: "Search\u2026",
906
+ style: inputStyle,
907
+ type: "text",
908
+ value: localSearch
909
+ }
910
+ )
911
+ ] })
912
+ ]
913
+ }
914
+ );
915
+ };
916
+
917
+ // src/plugin/logtape-devtools-plugin.tsx
918
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
919
+ var LogTapeDevtoolsPlugin = ({ store }) => {
920
+ const allRecords = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
921
+ const [paused, setPaused] = useState2(false);
922
+ const [pausedRecords, setPausedRecords] = useState2([]);
923
+ const [levelFilter, setLevelFilter] = useState2(/* @__PURE__ */ new Set());
924
+ const [categoryFilter, setCategoryFilter] = useState2([]);
925
+ const [searchText, setSearchText] = useState2("");
926
+ const [expandedId, setExpandedId] = useState2(null);
927
+ const [sidebarOpen, setSidebarOpen] = useState2(true);
928
+ const handlePause = useCallback3(() => {
929
+ setPaused(true);
930
+ setPausedRecords(store.getSnapshot());
931
+ }, [store]);
932
+ const handleResume = useCallback3(() => {
933
+ setPaused(false);
934
+ setPausedRecords([]);
935
+ }, []);
936
+ const handleClear = useCallback3(() => {
937
+ store.clear();
938
+ setPausedRecords([]);
939
+ setExpandedId(null);
940
+ }, [store]);
941
+ const records = paused ? pausedRecords : allRecords;
942
+ const uniqueCategories = useMemo2(() => {
943
+ const seen = /* @__PURE__ */ new Set();
944
+ for (const r of records) {
945
+ seen.add(r.category.join("."));
946
+ }
947
+ return Array.from(seen).sort();
948
+ }, [records]);
949
+ const filteredRecords = useMemo2(() => {
950
+ return records.filter((r) => {
951
+ if (levelFilter.size > 0 && !levelFilter.has(r.level)) {
952
+ return false;
953
+ }
954
+ if (categoryFilter.length > 0 && !categoryFilter.includes(r.category.join("."))) {
955
+ return false;
956
+ }
957
+ if (searchText) {
958
+ const term = searchText.toLowerCase();
959
+ if (!(r.messageText.toLowerCase().includes(term) || r.category.join(".").toLowerCase().includes(term))) {
960
+ return false;
961
+ }
962
+ }
963
+ return true;
964
+ });
965
+ }, [records, levelFilter, categoryFilter, searchText]);
966
+ return /* @__PURE__ */ jsxs5(
967
+ "div",
968
+ {
969
+ style: {
970
+ background: theme.colors.background,
971
+ color: theme.colors.textPrimary,
972
+ display: "flex",
973
+ flexDirection: "row",
974
+ fontFamily: theme.fontFamily.sans,
975
+ height: "100%",
976
+ overflow: "hidden"
977
+ },
978
+ children: [
979
+ /* @__PURE__ */ jsx7(
980
+ "div",
981
+ {
982
+ style: {
983
+ borderRight: sidebarOpen ? `1px solid ${theme.colors.border}` : "none",
984
+ flexShrink: 0,
985
+ opacity: sidebarOpen ? 1 : 0,
986
+ overflow: "hidden",
987
+ transition: "width 0.2s ease, opacity 0.15s ease",
988
+ width: sidebarOpen ? "200px" : "0px"
989
+ },
990
+ children: /* @__PURE__ */ jsx7(
991
+ Toolbar,
992
+ {
993
+ categories: uniqueCategories,
994
+ categoryFilter,
995
+ filteredCount: filteredRecords.length,
996
+ levelFilter,
997
+ onCategoryFilterChange: setCategoryFilter,
998
+ onClear: handleClear,
999
+ onLevelFilterChange: setLevelFilter,
1000
+ onPause: handlePause,
1001
+ onResume: handleResume,
1002
+ onSearchTextChange: setSearchText,
1003
+ paused,
1004
+ searchText,
1005
+ totalCount: records.length
1006
+ }
1007
+ )
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flex: 1, flexDirection: "column", overflow: "hidden" }, children: [
1011
+ /* @__PURE__ */ jsxs5(
1012
+ "div",
1013
+ {
1014
+ style: {
1015
+ alignItems: "center",
1016
+ borderBottom: `1px solid ${theme.colors.border}`,
1017
+ display: "flex",
1018
+ flexShrink: 0,
1019
+ gap: theme.spacing.lg,
1020
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`
1021
+ },
1022
+ children: [
1023
+ /* @__PURE__ */ jsx7(
1024
+ "button",
1025
+ {
1026
+ "aria-expanded": sidebarOpen,
1027
+ "data-lt-clear-btn": "",
1028
+ onClick: () => setSidebarOpen((v) => !v),
1029
+ style: {
1030
+ background: "none",
1031
+ border: "none",
1032
+ color: theme.colors.textMuted,
1033
+ cursor: "pointer",
1034
+ fontFamily: theme.fontFamily.sans,
1035
+ fontSize: theme.fontSize.md,
1036
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
1037
+ transition: "color 0.1s"
1038
+ },
1039
+ title: sidebarOpen ? "Collapse sidebar" : "Expand sidebar",
1040
+ type: "button",
1041
+ children: /* @__PURE__ */ jsxs5("span", { style: { alignItems: "center", display: "inline-flex", gap: "6px" }, children: [
1042
+ sidebarOpen ? /* @__PURE__ */ jsx7(PanelLeftCloseIcon, { size: 14 }) : /* @__PURE__ */ jsx7(PanelLeftOpenIcon, { size: 14 }),
1043
+ sidebarOpen ? "Hide controls" : "Show controls"
1044
+ ] })
1045
+ }
1046
+ ),
1047
+ !sidebarOpen && /* @__PURE__ */ jsxs5(Fragment, { children: [
1048
+ /* @__PURE__ */ jsx7(
1049
+ "span",
1050
+ {
1051
+ style: {
1052
+ color: theme.colors.textMuted,
1053
+ fontFamily: theme.fontFamily.mono,
1054
+ fontSize: theme.fontSize.sm
1055
+ },
1056
+ children: filteredRecords.length === records.length ? `${records.length} logs` : `${filteredRecords.length} / ${records.length} logs`
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsx7(
1060
+ "button",
1061
+ {
1062
+ "data-lt-interactive": "",
1063
+ onClick: paused ? handleResume : handlePause,
1064
+ style: {
1065
+ background: paused ? theme.colors.levels.warning.badge : theme.colors.surfaceHover,
1066
+ border: `1px solid ${theme.colors.border}`,
1067
+ borderRadius: theme.radius.sm,
1068
+ color: paused ? theme.colors.white : theme.colors.textPrimary,
1069
+ cursor: "pointer",
1070
+ fontFamily: theme.fontFamily.sans,
1071
+ fontSize: theme.fontSize.sm,
1072
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
1073
+ transition: "filter 0.1s"
1074
+ },
1075
+ title: paused ? "Resume" : "Pause",
1076
+ type: "button",
1077
+ children: paused ? /* @__PURE__ */ jsx7(PlayIcon, { size: 12 }) : /* @__PURE__ */ jsx7(PauseIcon, { size: 12 })
1078
+ }
1079
+ ),
1080
+ /* @__PURE__ */ jsx7(
1081
+ "button",
1082
+ {
1083
+ "data-lt-interactive": "",
1084
+ onClick: handleClear,
1085
+ style: {
1086
+ background: theme.colors.surfaceHover,
1087
+ border: `1px solid ${theme.colors.border}`,
1088
+ borderRadius: theme.radius.sm,
1089
+ color: theme.colors.textPrimary,
1090
+ cursor: "pointer",
1091
+ fontFamily: theme.fontFamily.sans,
1092
+ fontSize: theme.fontSize.sm,
1093
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
1094
+ transition: "filter 0.1s"
1095
+ },
1096
+ title: "Clear",
1097
+ type: "button",
1098
+ children: /* @__PURE__ */ jsx7(Trash2Icon, { size: 12 })
1099
+ }
1100
+ )
1101
+ ] })
1102
+ ]
1103
+ }
1104
+ ),
1105
+ /* @__PURE__ */ jsx7(
1106
+ LogList,
1107
+ {
1108
+ autoScroll: !paused,
1109
+ expandedId,
1110
+ onToggle: setExpandedId,
1111
+ records: filteredRecords
1112
+ }
1113
+ )
1114
+ ] })
1115
+ ]
1116
+ }
1117
+ );
1118
+ };
1119
+
1120
+ // src/plugin/create-logtape-devtools-plugin.tsx
1121
+ import { jsx as jsx8 } from "react/jsx-runtime";
1122
+ var createLogTapeDevtoolsPlugin = (options) => ({
1123
+ defaultOpen: options?.defaultOpen ?? true,
1124
+ id: "logtape-devtools-plugin",
1125
+ name: options?.name ?? "LogTape",
1126
+ render: /* @__PURE__ */ jsx8(LogTapeDevtoolsPlugin, { store: options?.store ?? defaultLogStore })
1127
+ });
1128
+
1129
+ // src/sink.ts
1130
+ var counter = 0;
1131
+ function renderMessage(message) {
1132
+ return message.map((part) => {
1133
+ if (typeof part === "string") {
1134
+ return part;
1135
+ }
1136
+ if (part === null || part === void 0) {
1137
+ return String(part);
1138
+ }
1139
+ try {
1140
+ return JSON.stringify(part);
1141
+ } catch {
1142
+ return String(part);
1143
+ }
1144
+ }).join("");
1145
+ }
1146
+ function safeCloneProperties(properties) {
1147
+ try {
1148
+ return JSON.parse(JSON.stringify(properties));
1149
+ } catch {
1150
+ const result = {};
1151
+ for (const [key, value] of Object.entries(properties)) {
1152
+ try {
1153
+ result[key] = JSON.parse(JSON.stringify(value));
1154
+ } catch {
1155
+ result[key] = String(value);
1156
+ }
1157
+ }
1158
+ return result;
1159
+ }
1160
+ }
1161
+ var v8FrameRe = /at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/;
1162
+ var smFrameRe = /(?:^|@)(.+?):(\d+):(\d+)$/;
1163
+ function captureCallerInfo() {
1164
+ const stack = new Error().stack;
1165
+ if (!stack) {
1166
+ return void 0;
1167
+ }
1168
+ const lines = stack.split("\n");
1169
+ for (const line of lines) {
1170
+ const trimmed = line.trim();
1171
+ if (!trimmed || trimmed === "Error") {
1172
+ continue;
1173
+ }
1174
+ if (/logtape/i.test(trimmed)) {
1175
+ continue;
1176
+ }
1177
+ const match = v8FrameRe.exec(trimmed) ?? smFrameRe.exec(trimmed);
1178
+ if (!match) {
1179
+ continue;
1180
+ }
1181
+ const [, filePath, lineNo, colNo] = match;
1182
+ const fileName = filePath.replace(/[?#].*$/, "").split("/").pop() ?? filePath;
1183
+ return `${fileName}:${lineNo}:${colNo}`;
1184
+ }
1185
+ return void 0;
1186
+ }
1187
+ function normalizeRecord(record, captureStack) {
1188
+ const normalized = {
1189
+ id: `log-${++counter}-${record.timestamp}`,
1190
+ timestamp: record.timestamp,
1191
+ level: record.level,
1192
+ category: [...record.category],
1193
+ message: [...record.message],
1194
+ messageText: renderMessage(record.message),
1195
+ properties: safeCloneProperties(record.properties)
1196
+ };
1197
+ if (captureStack) {
1198
+ const caller = captureCallerInfo();
1199
+ if (caller) {
1200
+ normalized.caller = caller;
1201
+ }
1202
+ }
1203
+ return normalized;
1204
+ }
1205
+ function isProductionEnv() {
1206
+ try {
1207
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
1208
+ return true;
1209
+ }
1210
+ } catch {
1211
+ }
1212
+ return false;
1213
+ }
1214
+ function createDevtoolsSink(options) {
1215
+ const store = options?.store ?? defaultLogStore;
1216
+ const wantsStack = options?.experimentalCaptureStackTrace ?? true;
1217
+ const captureStack = wantsStack && (options?.forceStackTrace || !isProductionEnv());
1218
+ return (record) => {
1219
+ try {
1220
+ const normalized = normalizeRecord(record, captureStack);
1221
+ store.addRecord(normalized);
1222
+ } catch {
1223
+ }
1224
+ };
1225
+ }
1226
+ export {
1227
+ LOG_LEVELS,
1228
+ LogTapeDevtoolsPlugin,
1229
+ createDevtoolsSink,
1230
+ createLogStore,
1231
+ createLogTapeDevtoolsPlugin,
1232
+ defaultLogStore
1233
+ };
1234
+ //# sourceMappingURL=index.mjs.map