@lombard.finance/sdk-devtools 0.1.0-canary-2.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.
package/dist/index.js ADDED
@@ -0,0 +1,1644 @@
1
+ // src/types.ts
2
+ var DEFAULT_DEVTOOLS_CONFIG = {
3
+ position: "bottom-right",
4
+ defaultOpen: false,
5
+ theme: "dark",
6
+ showEnvironment: true,
7
+ consoleLogging: false,
8
+ maxEvents: 100,
9
+ maxReducerLogs: 50
10
+ };
11
+
12
+ // src/provider/DevToolsProvider.tsx
13
+ import {
14
+ createContext,
15
+ useCallback,
16
+ useContext,
17
+ useEffect,
18
+ useMemo,
19
+ useState
20
+ } from "react";
21
+
22
+ // src/bridge/DevToolsBridge.ts
23
+ var DevToolsBridge = class {
24
+ constructor(config) {
25
+ this.events = [];
26
+ this.actions = /* @__PURE__ */ new Map();
27
+ this.unsubscribers = /* @__PURE__ */ new Map();
28
+ this.eventListeners = /* @__PURE__ */ new Set();
29
+ this.stateListeners = /* @__PURE__ */ new Set();
30
+ this.eventIdCounter = 0;
31
+ // Network logging
32
+ this.networkLog = /* @__PURE__ */ new Map();
33
+ this.networkListeners = /* @__PURE__ */ new Set();
34
+ this.requestIdCounter = 0;
35
+ this.config = { ...DEFAULT_DEVTOOLS_CONFIG, ...config };
36
+ }
37
+ // ─────────────────────────────────────────────────────────────────
38
+ // Action Registration
39
+ // ─────────────────────────────────────────────────────────────────
40
+ /**
41
+ * Register an SDK action for monitoring
42
+ *
43
+ * Automatically subscribes to all standard events:
44
+ * - status-change
45
+ * - error
46
+ * - completed
47
+ * - failed
48
+ * - loading
49
+ * - progress
50
+ */
51
+ registerAction(name, action, options) {
52
+ this.actions.set(name, {
53
+ name,
54
+ action,
55
+ registeredAt: Date.now(),
56
+ category: options?.category
57
+ });
58
+ const eventTypes = [
59
+ "status-change",
60
+ "error",
61
+ "completed",
62
+ "failed",
63
+ "loading",
64
+ "progress"
65
+ ];
66
+ const unsubscribers = [];
67
+ for (const eventType of eventTypes) {
68
+ try {
69
+ const unsubscribe = action.on(eventType, (...args) => {
70
+ this.handleEvent(name, eventType, args[0]);
71
+ });
72
+ if (typeof unsubscribe === "function") {
73
+ unsubscribers.push(unsubscribe);
74
+ }
75
+ } catch {
76
+ }
77
+ }
78
+ this.unsubscribers.set(name, unsubscribers);
79
+ this.notifyStateListeners();
80
+ this.handleEvent(name, "registered", { status: action.status });
81
+ return () => {
82
+ this.unregisterAction(name);
83
+ };
84
+ }
85
+ /**
86
+ * Unregister an action
87
+ */
88
+ unregisterAction(name) {
89
+ const unsubs = this.unsubscribers.get(name);
90
+ if (unsubs) {
91
+ unsubs.forEach((unsub) => {
92
+ unsub();
93
+ });
94
+ this.unsubscribers.delete(name);
95
+ }
96
+ this.actions.delete(name);
97
+ this.notifyStateListeners();
98
+ this.handleEvent(name, "unregistered", null);
99
+ }
100
+ // ─────────────────────────────────────────────────────────────────
101
+ // Event Handling
102
+ // ─────────────────────────────────────────────────────────────────
103
+ handleEvent(source, type, data) {
104
+ const event = {
105
+ id: `${++this.eventIdCounter}`,
106
+ type,
107
+ timestamp: Date.now(),
108
+ data,
109
+ source,
110
+ isSDKEvent: true
111
+ };
112
+ this.events.push(event);
113
+ if (this.events.length > this.config.maxEvents) {
114
+ this.events.shift();
115
+ }
116
+ if (this.config.consoleLogging) {
117
+ console.log(`[DevTools] ${source} \u2192 ${type}:`, data);
118
+ }
119
+ this.notifyEventListeners(event);
120
+ }
121
+ notifyEventListeners(event) {
122
+ this.eventListeners.forEach((listener) => {
123
+ try {
124
+ listener(event);
125
+ } catch (err) {
126
+ console.error("[DevTools] Event listener error:", err);
127
+ }
128
+ });
129
+ }
130
+ notifyStateListeners() {
131
+ this.stateListeners.forEach((listener) => {
132
+ try {
133
+ listener(new Map(this.actions));
134
+ } catch (err) {
135
+ console.error("[DevTools] State listener error:", err);
136
+ }
137
+ });
138
+ }
139
+ // ─────────────────────────────────────────────────────────────────
140
+ // Subscriptions
141
+ // ─────────────────────────────────────────────────────────────────
142
+ /**
143
+ * Subscribe to new events
144
+ */
145
+ onEvent(callback) {
146
+ this.eventListeners.add(callback);
147
+ return () => this.eventListeners.delete(callback);
148
+ }
149
+ /**
150
+ * Subscribe to action state changes
151
+ */
152
+ onStateChange(callback) {
153
+ this.stateListeners.add(callback);
154
+ return () => this.stateListeners.delete(callback);
155
+ }
156
+ // ─────────────────────────────────────────────────────────────────
157
+ // Getters
158
+ // ─────────────────────────────────────────────────────────────────
159
+ /**
160
+ * Get all collected events
161
+ */
162
+ getEvents() {
163
+ return [...this.events];
164
+ }
165
+ /**
166
+ * Get all registered actions
167
+ */
168
+ getActions() {
169
+ return new Map(this.actions);
170
+ }
171
+ /**
172
+ * Get current state summary for all actions
173
+ */
174
+ getStateSummary() {
175
+ const summary = {};
176
+ this.actions.forEach((reg, name) => {
177
+ summary[name] = {
178
+ status: reg.action.status,
179
+ isLoading: reg.action.isLoading,
180
+ hasError: reg.action.isFailed
181
+ };
182
+ });
183
+ return summary;
184
+ }
185
+ // ─────────────────────────────────────────────────────────────────
186
+ // Network Logging (API Requests/Responses)
187
+ // ─────────────────────────────────────────────────────────────────
188
+ /**
189
+ * Log an API request
190
+ *
191
+ * Call this when an HTTP request is initiated.
192
+ * Returns a request ID to correlate with the response.
193
+ */
194
+ logApiRequest(params) {
195
+ const requestId = `req-${++this.requestIdCounter}`;
196
+ const request = {
197
+ id: requestId,
198
+ type: "api-request",
199
+ method: params.method,
200
+ url: params.url,
201
+ payload: params.payload,
202
+ headers: params.headers,
203
+ timestamp: Date.now(),
204
+ source: params.source
205
+ };
206
+ const entry = {
207
+ request,
208
+ response: null,
209
+ isPending: true,
210
+ isFailed: false
211
+ };
212
+ this.networkLog.set(requestId, entry);
213
+ this.handleEvent(params.source || "api", "api-request", {
214
+ method: params.method,
215
+ url: params.url,
216
+ payload: params.payload
217
+ });
218
+ if (this.config.consoleLogging) {
219
+ console.log(
220
+ `[DevTools] API ${params.method} ${params.url}`,
221
+ params.payload
222
+ );
223
+ }
224
+ this.notifyNetworkListeners();
225
+ return requestId;
226
+ }
227
+ /**
228
+ * Log an API response
229
+ *
230
+ * Call this when an HTTP response is received.
231
+ * Pass the requestId returned from logApiRequest.
232
+ */
233
+ logApiResponse(params) {
234
+ const entry = this.networkLog.get(params.requestId);
235
+ if (!entry) {
236
+ console.warn(`[DevTools] No request found for ID: ${params.requestId}`);
237
+ return;
238
+ }
239
+ const response = {
240
+ id: `res-${params.requestId}`,
241
+ type: "api-response",
242
+ requestId: params.requestId,
243
+ status: params.status,
244
+ statusText: params.statusText,
245
+ data: params.data,
246
+ error: params.error,
247
+ duration: Date.now() - entry.request.timestamp,
248
+ timestamp: Date.now()
249
+ };
250
+ entry.response = response;
251
+ entry.isPending = false;
252
+ entry.isFailed = params.status >= 400 || !!params.error;
253
+ this.handleEvent(entry.request.source || "api", "api-response", {
254
+ status: params.status,
255
+ duration: response.duration,
256
+ url: entry.request.url,
257
+ error: params.error
258
+ });
259
+ if (this.config.consoleLogging) {
260
+ const logFn = entry.isFailed ? console.error : console.log;
261
+ logFn(
262
+ `[DevTools] API Response ${params.status} (${response.duration}ms) ${entry.request.url}`,
263
+ params.data || params.error
264
+ );
265
+ }
266
+ this.notifyNetworkListeners();
267
+ }
268
+ /**
269
+ * Subscribe to network log changes
270
+ */
271
+ onNetworkChange(callback) {
272
+ this.networkListeners.add(callback);
273
+ return () => this.networkListeners.delete(callback);
274
+ }
275
+ /**
276
+ * Get all network log entries
277
+ */
278
+ getNetworkLog() {
279
+ return Array.from(this.networkLog.values()).sort(
280
+ (a, b) => b.request.timestamp - a.request.timestamp
281
+ );
282
+ }
283
+ /**
284
+ * Clear network log
285
+ */
286
+ clearNetworkLog() {
287
+ this.networkLog.clear();
288
+ this.notifyNetworkListeners();
289
+ }
290
+ notifyNetworkListeners() {
291
+ const entries = this.getNetworkLog();
292
+ this.networkListeners.forEach((listener) => {
293
+ try {
294
+ listener(entries);
295
+ } catch (err) {
296
+ console.error("[DevTools] Network listener error:", err);
297
+ }
298
+ });
299
+ }
300
+ // ─────────────────────────────────────────────────────────────────
301
+ // Utilities
302
+ // ─────────────────────────────────────────────────────────────────
303
+ /**
304
+ * Clear all events
305
+ */
306
+ clearEvents() {
307
+ this.events = [];
308
+ }
309
+ /**
310
+ * Clear all registered actions
311
+ */
312
+ clearActions() {
313
+ this.actions.forEach((_, name) => {
314
+ this.unregisterAction(name);
315
+ });
316
+ }
317
+ /**
318
+ * Destroy the bridge
319
+ */
320
+ destroy() {
321
+ this.clearActions();
322
+ this.clearNetworkLog();
323
+ this.eventListeners.clear();
324
+ this.stateListeners.clear();
325
+ this.networkListeners.clear();
326
+ }
327
+ };
328
+ var globalBridge = null;
329
+ function getDevToolsBridge(config) {
330
+ if (!globalBridge) {
331
+ globalBridge = new DevToolsBridge(config);
332
+ }
333
+ return globalBridge;
334
+ }
335
+ function resetDevToolsBridge() {
336
+ if (globalBridge) {
337
+ globalBridge.destroy();
338
+ globalBridge = null;
339
+ }
340
+ }
341
+
342
+ // src/provider/DevToolsProvider.tsx
343
+ import { jsx } from "react/jsx-runtime";
344
+ var DevToolsContext = createContext(null);
345
+ function DevToolsProvider({
346
+ children,
347
+ config,
348
+ enabled = process.env.NODE_ENV !== "production"
349
+ }) {
350
+ const [bridge] = useState(() => new DevToolsBridge(config));
351
+ const [events, setEvents] = useState([]);
352
+ const [actions, setActions] = useState(
353
+ /* @__PURE__ */ new Map()
354
+ );
355
+ const [networkLog, setNetworkLog] = useState([]);
356
+ const [mountId, setMountId] = useState(0);
357
+ useEffect(() => {
358
+ setMountId((prev) => prev + 1);
359
+ }, []);
360
+ useEffect(() => {
361
+ if (!enabled || mountId === 0) return;
362
+ console.log(
363
+ "[DevToolsProvider] Setting up event subscriptions, mountId:",
364
+ mountId
365
+ );
366
+ const existingEvents = bridge.getEvents();
367
+ const existingActions = bridge.getActions();
368
+ if (existingEvents.length > 0) {
369
+ console.log(
370
+ "[DevToolsProvider] Syncing existing events:",
371
+ existingEvents.length
372
+ );
373
+ setEvents(existingEvents);
374
+ }
375
+ if (existingActions.size > 0) {
376
+ console.log(
377
+ "[DevToolsProvider] Syncing existing actions:",
378
+ existingActions.size
379
+ );
380
+ setActions(existingActions);
381
+ }
382
+ const unsubEvent = bridge.onEvent((event) => {
383
+ console.log("[DevToolsProvider] Event received:", event);
384
+ const newEvents = bridge.getEvents();
385
+ console.log("[DevToolsProvider] Setting events:", newEvents.length);
386
+ setEvents(newEvents);
387
+ });
388
+ const unsubState = bridge.onStateChange((newActions) => {
389
+ console.log(
390
+ "[DevToolsProvider] State changed:",
391
+ newActions.size,
392
+ "actions"
393
+ );
394
+ setActions(newActions);
395
+ });
396
+ const unsubNetwork = bridge.onNetworkChange((entries) => {
397
+ console.log(
398
+ "[DevToolsProvider] Network log updated:",
399
+ entries.length,
400
+ "entries"
401
+ );
402
+ setNetworkLog(entries);
403
+ });
404
+ const existingNetworkLog = bridge.getNetworkLog();
405
+ if (existingNetworkLog.length > 0) {
406
+ console.log(
407
+ "[DevToolsProvider] Syncing existing network log:",
408
+ existingNetworkLog.length
409
+ );
410
+ setNetworkLog(existingNetworkLog);
411
+ }
412
+ return () => {
413
+ console.log("[DevToolsProvider] Cleaning up subscriptions");
414
+ unsubEvent();
415
+ unsubState();
416
+ unsubNetwork();
417
+ };
418
+ }, [enabled, bridge, mountId]);
419
+ useEffect(() => {
420
+ return () => {
421
+ console.log("[DevToolsProvider] Destroying bridge");
422
+ bridge.destroy();
423
+ };
424
+ }, [bridge]);
425
+ const registerAction = useCallback(
426
+ (name, action, options) => {
427
+ if (!enabled) return () => {
428
+ };
429
+ return bridge.registerAction(name, action, options);
430
+ },
431
+ [bridge, enabled]
432
+ );
433
+ const unregisterAction = useCallback(
434
+ (name) => {
435
+ if (!enabled) return;
436
+ bridge.unregisterAction(name);
437
+ },
438
+ [bridge, enabled]
439
+ );
440
+ const clearEvents = useCallback(() => {
441
+ bridge.clearEvents();
442
+ setEvents([]);
443
+ }, [bridge]);
444
+ const clearNetworkLog = useCallback(() => {
445
+ bridge.clearNetworkLog();
446
+ setNetworkLog([]);
447
+ }, [bridge]);
448
+ const getStateSummary = useCallback(() => {
449
+ return bridge.getStateSummary();
450
+ }, [bridge]);
451
+ const value = useMemo(
452
+ () => ({
453
+ events,
454
+ actions,
455
+ networkLog,
456
+ registerAction,
457
+ unregisterAction,
458
+ clearEvents,
459
+ clearNetworkLog,
460
+ getStateSummary,
461
+ bridge,
462
+ isEnabled: enabled
463
+ }),
464
+ [
465
+ events,
466
+ actions,
467
+ networkLog,
468
+ registerAction,
469
+ unregisterAction,
470
+ clearEvents,
471
+ clearNetworkLog,
472
+ getStateSummary,
473
+ bridge,
474
+ enabled
475
+ ]
476
+ );
477
+ return /* @__PURE__ */ jsx(DevToolsContext.Provider, { value, children });
478
+ }
479
+ function useDevToolsContext() {
480
+ const context = useContext(DevToolsContext);
481
+ if (!context) {
482
+ throw new Error(
483
+ "useDevToolsContext must be used within a DevToolsProvider"
484
+ );
485
+ }
486
+ return context;
487
+ }
488
+ function useRegisterAction(name, action, category) {
489
+ const { registerAction } = useDevToolsContext();
490
+ useEffect(() => {
491
+ if (!action) return;
492
+ return registerAction(name, action, category ? { category } : void 0);
493
+ }, [name, action, registerAction]);
494
+ }
495
+
496
+ // src/components/DevToolsPanel.tsx
497
+ import {
498
+ Activity as Activity2,
499
+ Bug,
500
+ Database,
501
+ Maximize2,
502
+ Minimize2,
503
+ Terminal as Terminal2,
504
+ Wifi
505
+ } from "lucide-react";
506
+ import { useMemo as useMemo2, useState as useState5 } from "react";
507
+
508
+ // src/components/EventLog.tsx
509
+ import { Terminal, Trash2, Zap } from "lucide-react";
510
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
511
+ function EventLog({
512
+ events,
513
+ onClear,
514
+ title = "SDK Event Log",
515
+ showInfoBanner = true,
516
+ className = ""
517
+ }) {
518
+ const sdkEventCount = events.filter((e) => e.isSDKEvent).length;
519
+ return /* @__PURE__ */ jsxs("div", { className: `h-full flex flex-col ${className}`, children: [
520
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: [
521
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400", children: [
522
+ /* @__PURE__ */ jsx2(Terminal, { className: "w-3.5 h-3.5" }),
523
+ /* @__PURE__ */ jsx2("span", { children: title }),
524
+ events.length > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 px-1.5 py-0.5 bg-cyan-100 dark:bg-cyan-900/50 border border-cyan-200 dark:border-cyan-700/50 rounded text-[10px] text-cyan-600 dark:text-cyan-400", children: [
525
+ /* @__PURE__ */ jsx2(Zap, { className: "w-2.5 h-2.5" }),
526
+ sdkEventCount,
527
+ " SDK events"
528
+ ] })
529
+ ] }),
530
+ events.length > 0 && /* @__PURE__ */ jsxs(
531
+ "button",
532
+ {
533
+ onClick: onClear,
534
+ className: "flex items-center gap-1 text-[10px] text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors",
535
+ children: [
536
+ /* @__PURE__ */ jsx2(Trash2, { className: "w-3 h-3" }),
537
+ "Clear"
538
+ ]
539
+ }
540
+ )
541
+ ] }),
542
+ showInfoBanner && /* @__PURE__ */ jsxs("div", { className: "px-3 py-1 bg-gray-100 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700/50 text-[10px] text-gray-500", children: [
543
+ "\u{1F4A1} These events are emitted by the SDK's",
544
+ " ",
545
+ /* @__PURE__ */ jsx2("code", { className: "text-gray-900 dark:text-white font-semibold", children: "action.on()" }),
546
+ " ",
547
+ "method"
548
+ ] }),
549
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-y-auto p-2 font-mono text-[11px] leading-relaxed", children: events.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-gray-400 dark:text-gray-600 text-center py-4", children: /* @__PURE__ */ jsx2("p", { children: "SDK events will appear here..." }) }) : /* @__PURE__ */ jsx2("div", { className: "space-y-0.5", children: events.map((event) => /* @__PURE__ */ jsx2(EventRow, { event }, event.id)) }) })
550
+ ] });
551
+ }
552
+ function EventRow({ event }) {
553
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 hover:bg-gray-100 dark:hover:bg-gray-800/50 px-1 py-0.5 rounded", children: [
554
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-600 flex-shrink-0", children: formatTime(event.timestamp) }),
555
+ event.isSDKEvent && /* @__PURE__ */ jsx2("span", { className: "flex-shrink-0 text-[9px] px-1 py-0.5 bg-cyan-100 dark:bg-cyan-900/30 text-cyan-600 dark:text-cyan-500 rounded border border-cyan-200 dark:border-cyan-800/50", children: "SDK" }),
556
+ event.source && /* @__PURE__ */ jsx2("span", { className: "flex-shrink-0 text-[9px] px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded border border-purple-200 dark:border-purple-800/50", children: event.source }),
557
+ /* @__PURE__ */ jsx2("span", { className: `flex-shrink-0 ${getEventColor(event.type)}`, children: event.type }),
558
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\u2192" }),
559
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-600 dark:text-gray-400 truncate", children: formatEventData(event.data) })
560
+ ] });
561
+ }
562
+ function formatTime(timestamp) {
563
+ const date = new Date(timestamp);
564
+ const time = date.toLocaleTimeString("en-US", {
565
+ hour12: false,
566
+ hour: "2-digit",
567
+ minute: "2-digit",
568
+ second: "2-digit"
569
+ });
570
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
571
+ return `${time}.${ms}`;
572
+ }
573
+ function getEventColor(type) {
574
+ if (type === "error" || type === "failed") return "text-red-400";
575
+ if (type === "completed") return "text-green-400";
576
+ if (type === "status-change") return "text-cyan-400";
577
+ if (type === "progress") return "text-blue-400";
578
+ if (type === "loading") return "text-yellow-400";
579
+ return "text-gray-400";
580
+ }
581
+ function formatEventData(data) {
582
+ if (data === null || data === void 0) return "\u2014";
583
+ if (typeof data === "string") return data;
584
+ if (typeof data === "boolean") return data ? "true" : "false";
585
+ if (typeof data === "object") {
586
+ try {
587
+ return JSON.stringify(data);
588
+ } catch {
589
+ return String(data);
590
+ }
591
+ }
592
+ return String(data);
593
+ }
594
+
595
+ // src/components/NetworkLog.tsx
596
+ import {
597
+ AlertCircle,
598
+ CheckCircle,
599
+ ChevronDown,
600
+ ChevronRight,
601
+ Clock,
602
+ Loader2
603
+ } from "lucide-react";
604
+ import { useState as useState2 } from "react";
605
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
606
+ function NetworkLog({
607
+ entries,
608
+ onClear,
609
+ maxHeight = "400px"
610
+ }) {
611
+ const [expandedIds, setExpandedIds] = useState2(/* @__PURE__ */ new Set());
612
+ const toggleExpand = (id) => {
613
+ setExpandedIds((prev) => {
614
+ const next = new Set(prev);
615
+ if (next.has(id)) {
616
+ next.delete(id);
617
+ } else {
618
+ next.add(id);
619
+ }
620
+ return next;
621
+ });
622
+ };
623
+ if (entries.length === 0) {
624
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8 text-gray-500 dark:text-gray-400", children: [
625
+ /* @__PURE__ */ jsx3(Clock, { className: "w-8 h-8 mb-2 opacity-50" }),
626
+ /* @__PURE__ */ jsx3("p", { className: "text-sm", children: "No API requests yet" }),
627
+ /* @__PURE__ */ jsx3("p", { className: "text-xs opacity-75 mt-1", children: "Requests will appear here when you use SDK methods" })
628
+ ] });
629
+ }
630
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col h-full", children: [
631
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-gray-700", children: [
632
+ /* @__PURE__ */ jsxs2("span", { className: "text-xs font-medium text-gray-600 dark:text-gray-400", children: [
633
+ entries.length,
634
+ " request",
635
+ entries.length !== 1 ? "s" : ""
636
+ ] }),
637
+ /* @__PURE__ */ jsx3(
638
+ "button",
639
+ {
640
+ onClick: onClear,
641
+ className: "text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
642
+ children: "Clear"
643
+ }
644
+ )
645
+ ] }),
646
+ /* @__PURE__ */ jsx3("div", { className: "flex-1 overflow-y-auto", style: { maxHeight }, children: entries.map((entry) => /* @__PURE__ */ jsx3(
647
+ NetworkEntry,
648
+ {
649
+ entry,
650
+ isExpanded: expandedIds.has(entry.request.id),
651
+ onToggle: () => {
652
+ toggleExpand(entry.request.id);
653
+ }
654
+ },
655
+ entry.request.id
656
+ )) })
657
+ ] });
658
+ }
659
+ function NetworkEntry({ entry, isExpanded, onToggle }) {
660
+ const { request, response, isPending, isFailed } = entry;
661
+ const methodColor = {
662
+ GET: "text-emerald-600 dark:text-emerald-400",
663
+ POST: "text-blue-600 dark:text-blue-400",
664
+ PUT: "text-amber-600 dark:text-amber-400",
665
+ DELETE: "text-red-600 dark:text-red-400",
666
+ PATCH: "text-purple-600 dark:text-purple-400"
667
+ }[request.method] || "text-gray-600 dark:text-gray-400";
668
+ const StatusIcon = isPending ? () => /* @__PURE__ */ jsx3(Loader2, { className: "w-3.5 h-3.5 animate-spin text-blue-500" }) : isFailed ? () => /* @__PURE__ */ jsx3(AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }) : () => /* @__PURE__ */ jsx3(CheckCircle, { className: "w-3.5 h-3.5 text-emerald-500" });
669
+ const urlPath = (() => {
670
+ try {
671
+ const url = new URL(request.url, "http://localhost");
672
+ return url.pathname + url.search;
673
+ } catch {
674
+ return request.url;
675
+ }
676
+ })();
677
+ return /* @__PURE__ */ jsxs2(
678
+ "div",
679
+ {
680
+ className: `border-b border-gray-100 dark:border-gray-700/50 ${isFailed ? "bg-red-50/50 dark:bg-red-900/10" : ""}`,
681
+ children: [
682
+ /* @__PURE__ */ jsxs2(
683
+ "button",
684
+ {
685
+ onClick: onToggle,
686
+ className: "w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors",
687
+ children: [
688
+ isExpanded ? /* @__PURE__ */ jsx3(ChevronDown, { className: "w-3.5 h-3.5 text-gray-400 flex-shrink-0" }) : /* @__PURE__ */ jsx3(ChevronRight, { className: "w-3.5 h-3.5 text-gray-400 flex-shrink-0" }),
689
+ /* @__PURE__ */ jsx3(StatusIcon, {}),
690
+ /* @__PURE__ */ jsx3(
691
+ "span",
692
+ {
693
+ className: `text-xs font-mono font-bold ${methodColor} w-10 flex-shrink-0`,
694
+ children: request.method
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsx3("span", { className: "flex-1 text-xs font-mono text-gray-700 dark:text-gray-300 truncate", children: urlPath }),
698
+ response && /* @__PURE__ */ jsxs2(Fragment, { children: [
699
+ /* @__PURE__ */ jsx3(
700
+ "span",
701
+ {
702
+ className: `text-xs font-mono ${isFailed ? "text-red-600 dark:text-red-400" : "text-gray-500 dark:text-gray-400"}`,
703
+ children: response.status
704
+ }
705
+ ),
706
+ /* @__PURE__ */ jsxs2("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: [
707
+ response.duration,
708
+ "ms"
709
+ ] })
710
+ ] }),
711
+ request.source && /* @__PURE__ */ jsx3("span", { className: "text-[10px] px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 rounded", children: request.source })
712
+ ]
713
+ }
714
+ ),
715
+ isExpanded && /* @__PURE__ */ jsxs2("div", { className: "px-3 pb-3 space-y-2", children: [
716
+ /* @__PURE__ */ jsxs2("div", { children: [
717
+ /* @__PURE__ */ jsx3(
718
+ "h4",
719
+ {
720
+ style: { fontSize: "10px", lineHeight: "14px" },
721
+ className: "font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1",
722
+ children: "Request"
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsx3("div", { className: "bg-gray-50 dark:bg-gray-800 rounded p-2 overflow-x-auto max-w-full", children: request.payload ? /* @__PURE__ */ jsx3(
726
+ "pre",
727
+ {
728
+ style: { fontSize: "11px", lineHeight: "16px" },
729
+ className: "text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-all font-mono m-0",
730
+ children: JSON.stringify(request.payload, null, 2)
731
+ }
732
+ ) : /* @__PURE__ */ jsx3(
733
+ "span",
734
+ {
735
+ style: { fontSize: "11px" },
736
+ className: "text-gray-400 italic",
737
+ children: "No payload"
738
+ }
739
+ ) })
740
+ ] }),
741
+ response && /* @__PURE__ */ jsxs2("div", { children: [
742
+ /* @__PURE__ */ jsxs2(
743
+ "h4",
744
+ {
745
+ style: { fontSize: "10px", lineHeight: "14px" },
746
+ className: "font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1",
747
+ children: [
748
+ "Response",
749
+ response.error && /* @__PURE__ */ jsx3("span", { className: "text-red-500 ml-2", children: "Error" })
750
+ ]
751
+ }
752
+ ),
753
+ /* @__PURE__ */ jsx3(
754
+ "div",
755
+ {
756
+ className: `rounded p-2 overflow-x-auto max-w-full ${isFailed ? "bg-red-50 dark:bg-red-900/20" : "bg-gray-50 dark:bg-gray-800"}`,
757
+ children: response.error ? /* @__PURE__ */ jsx3(
758
+ "pre",
759
+ {
760
+ style: { fontSize: "11px", lineHeight: "16px" },
761
+ className: "text-red-600 dark:text-red-400 whitespace-pre-wrap break-all font-mono m-0",
762
+ children: response.error
763
+ }
764
+ ) : response.data ? /* @__PURE__ */ jsx3(
765
+ "pre",
766
+ {
767
+ style: { fontSize: "11px", lineHeight: "16px" },
768
+ className: "text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-all font-mono m-0",
769
+ children: JSON.stringify(response.data, null, 2)
770
+ }
771
+ ) : /* @__PURE__ */ jsx3(
772
+ "span",
773
+ {
774
+ style: { fontSize: "11px" },
775
+ className: "text-gray-400 italic",
776
+ children: "No data"
777
+ }
778
+ )
779
+ }
780
+ )
781
+ ] }),
782
+ isPending && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-xs text-blue-600 dark:text-blue-400", children: [
783
+ /* @__PURE__ */ jsx3(Loader2, { className: "w-3 h-3 animate-spin" }),
784
+ /* @__PURE__ */ jsx3("span", { children: "Waiting for response..." })
785
+ ] })
786
+ ] })
787
+ ]
788
+ }
789
+ );
790
+ }
791
+
792
+ // src/components/ReducerLog.tsx
793
+ import { Activity, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Trash2 as Trash22 } from "lucide-react";
794
+ import { useState as useState3 } from "react";
795
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
796
+ function ReducerLog({ logs, onClear, className = "" }) {
797
+ return /* @__PURE__ */ jsxs3("div", { className: `h-full flex flex-col ${className}`, children: [
798
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: [
799
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400", children: [
800
+ /* @__PURE__ */ jsx4(Activity, { className: "w-3.5 h-3.5" }),
801
+ /* @__PURE__ */ jsx4("span", { children: "Action Log" }),
802
+ logs.length > 0 && /* @__PURE__ */ jsxs3("span", { className: "px-1.5 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-[10px]", children: [
803
+ logs.length,
804
+ " actions"
805
+ ] })
806
+ ] }),
807
+ logs.length > 0 && /* @__PURE__ */ jsxs3(
808
+ "button",
809
+ {
810
+ onClick: onClear,
811
+ className: "flex items-center gap-1 text-[10px] text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors",
812
+ children: [
813
+ /* @__PURE__ */ jsx4(Trash22, { className: "w-3 h-3" }),
814
+ "Clear"
815
+ ]
816
+ }
817
+ )
818
+ ] }),
819
+ /* @__PURE__ */ jsx4("div", { className: "flex-1 overflow-y-auto", children: logs.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "text-gray-400 dark:text-gray-600 text-center py-4 text-xs", children: /* @__PURE__ */ jsx4("p", { children: "Reducer actions will appear here..." }) }) : /* @__PURE__ */ jsx4("div", { className: "divide-y divide-gray-200 dark:divide-gray-800", children: logs.map((log) => /* @__PURE__ */ jsx4(LogEntry, { log }, log.id)) }) })
820
+ ] });
821
+ }
822
+ function LogEntry({ log }) {
823
+ const [isExpanded, setIsExpanded] = useState3(false);
824
+ return /* @__PURE__ */ jsxs3("div", { className: "text-xs", children: [
825
+ /* @__PURE__ */ jsxs3(
826
+ "button",
827
+ {
828
+ onClick: () => {
829
+ setIsExpanded(!isExpanded);
830
+ },
831
+ className: "w-full flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors",
832
+ children: [
833
+ isExpanded ? /* @__PURE__ */ jsx4(ChevronDown2, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }) : /* @__PURE__ */ jsx4(ChevronRight2, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }),
834
+ /* @__PURE__ */ jsx4("span", { className: "text-gray-400 dark:text-gray-600 flex-shrink-0 font-mono", children: formatTime2(log.timestamp) }),
835
+ /* @__PURE__ */ jsx4("span", { className: "text-cyan-600 dark:text-cyan-400 font-medium", children: log.action.type }),
836
+ log.action.payload !== void 0 && /* @__PURE__ */ jsx4("span", { className: "text-gray-500 truncate", children: formatPayload(log.action.payload) })
837
+ ]
838
+ }
839
+ ),
840
+ isExpanded && /* @__PURE__ */ jsxs3("div", { className: "px-3 pb-3 space-y-2", children: [
841
+ log.action.payload !== void 0 && /* @__PURE__ */ jsxs3("div", { children: [
842
+ /* @__PURE__ */ jsx4("div", { className: "text-[10px] text-gray-500 mb-1", children: "Payload" }),
843
+ /* @__PURE__ */ jsx4("pre", { className: "p-2 bg-gray-100 dark:bg-gray-800 rounded text-[10px] font-mono text-amber-600 dark:text-yellow-300 overflow-x-auto", children: JSON.stringify(log.action.payload, null, 2) })
844
+ ] }),
845
+ /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-2", children: [
846
+ /* @__PURE__ */ jsxs3("div", { children: [
847
+ /* @__PURE__ */ jsx4("div", { className: "text-[10px] text-gray-500 mb-1", children: "Before" }),
848
+ /* @__PURE__ */ jsx4("pre", { className: "p-2 bg-gray-100 dark:bg-gray-800 rounded text-[10px] font-mono text-gray-600 dark:text-gray-400 overflow-x-auto max-h-32 overflow-y-auto", children: JSON.stringify(log.prevState, null, 2) })
849
+ ] }),
850
+ /* @__PURE__ */ jsxs3("div", { children: [
851
+ /* @__PURE__ */ jsx4("div", { className: "text-[10px] text-gray-500 mb-1", children: "After" }),
852
+ /* @__PURE__ */ jsx4("pre", { className: "p-2 bg-gray-100 dark:bg-gray-800 rounded text-[10px] font-mono text-green-600 dark:text-green-400 overflow-x-auto max-h-32 overflow-y-auto", children: JSON.stringify(log.nextState, null, 2) })
853
+ ] })
854
+ ] })
855
+ ] })
856
+ ] });
857
+ }
858
+ function formatTime2(timestamp) {
859
+ const date = new Date(timestamp);
860
+ return date.toLocaleTimeString("en-US", {
861
+ hour12: false,
862
+ hour: "2-digit",
863
+ minute: "2-digit",
864
+ second: "2-digit"
865
+ });
866
+ }
867
+ function formatPayload(payload) {
868
+ if (payload === null || payload === void 0) return "";
869
+ if (typeof payload === "string") return `"${payload}"`;
870
+ if (typeof payload === "object") {
871
+ try {
872
+ const str = JSON.stringify(payload);
873
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
874
+ } catch {
875
+ return String(payload);
876
+ }
877
+ }
878
+ return String(payload);
879
+ }
880
+
881
+ // src/components/StateInspector.tsx
882
+ import { Check, ChevronDown as ChevronDown3, ChevronRight as ChevronRight3, Copy } from "lucide-react";
883
+ import { useCallback as useCallback2, useState as useState4 } from "react";
884
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
885
+ function StateInspector({
886
+ state,
887
+ title = "State",
888
+ defaultExpandDepth = 2,
889
+ className = ""
890
+ }) {
891
+ const [isExpanded, setIsExpanded] = useState4(true);
892
+ const [copied, setCopied] = useState4(false);
893
+ const handleCopy = useCallback2(async () => {
894
+ try {
895
+ await navigator.clipboard.writeText(JSON.stringify(state, null, 2));
896
+ setCopied(true);
897
+ setTimeout(() => {
898
+ setCopied(false);
899
+ }, 2e3);
900
+ } catch (err) {
901
+ console.error("Failed to copy:", err);
902
+ }
903
+ }, [state]);
904
+ return /* @__PURE__ */ jsxs4(
905
+ "div",
906
+ {
907
+ className: `bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden h-full flex flex-col ${className}`,
908
+ children: [
909
+ /* @__PURE__ */ jsxs4("div", { className: "flex-shrink-0 flex items-center justify-between px-3 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700", children: [
910
+ /* @__PURE__ */ jsxs4(
911
+ "button",
912
+ {
913
+ onClick: () => {
914
+ setIsExpanded(!isExpanded);
915
+ },
916
+ className: "flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white",
917
+ children: [
918
+ isExpanded ? /* @__PURE__ */ jsx5(ChevronDown3, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx5(ChevronRight3, { className: "w-4 h-4" }),
919
+ title
920
+ ]
921
+ }
922
+ ),
923
+ /* @__PURE__ */ jsx5(
924
+ "button",
925
+ {
926
+ onClick: () => {
927
+ void handleCopy();
928
+ },
929
+ className: "flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors",
930
+ title: "Copy state as JSON",
931
+ children: copied ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
932
+ /* @__PURE__ */ jsx5(Check, { className: "w-3 h-3 text-green-500 dark:text-green-400" }),
933
+ /* @__PURE__ */ jsx5("span", { className: "text-green-500 dark:text-green-400", children: "Copied" })
934
+ ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
935
+ /* @__PURE__ */ jsx5(Copy, { className: "w-3 h-3" }),
936
+ "Copy"
937
+ ] })
938
+ }
939
+ )
940
+ ] }),
941
+ isExpanded && /* @__PURE__ */ jsx5("div", { className: "p-3 flex-1 overflow-auto", children: /* @__PURE__ */ jsx5(StateTree, { value: state, defaultExpandDepth }) })
942
+ ]
943
+ }
944
+ );
945
+ }
946
+ function StateTree({
947
+ value,
948
+ depth = 0,
949
+ keyName,
950
+ defaultExpandDepth = 2
951
+ }) {
952
+ const [isOpen, setIsOpen] = useState4(depth < defaultExpandDepth);
953
+ const indent = depth * 12;
954
+ const isExpandable = value !== null && typeof value === "object";
955
+ if (!isExpandable) {
956
+ return /* @__PURE__ */ jsxs4(
957
+ "div",
958
+ {
959
+ className: "flex items-baseline gap-1 font-mono text-xs",
960
+ style: { marginLeft: indent },
961
+ children: [
962
+ keyName && /* @__PURE__ */ jsxs4("span", { className: "text-purple-600 dark:text-purple-400", children: [
963
+ keyName,
964
+ ":"
965
+ ] }),
966
+ /* @__PURE__ */ jsx5(ValueDisplay, { value })
967
+ ]
968
+ }
969
+ );
970
+ }
971
+ if (Array.isArray(value)) {
972
+ if (value.length === 0) {
973
+ return /* @__PURE__ */ jsxs4("div", { className: "font-mono text-xs", style: { marginLeft: indent }, children: [
974
+ keyName && /* @__PURE__ */ jsxs4("span", { className: "text-purple-600 dark:text-purple-400", children: [
975
+ keyName,
976
+ ":",
977
+ " "
978
+ ] }),
979
+ /* @__PURE__ */ jsx5("span", { className: "text-gray-400 dark:text-gray-500", children: "[]" })
980
+ ] });
981
+ }
982
+ return /* @__PURE__ */ jsxs4("div", { style: { marginLeft: indent }, children: [
983
+ /* @__PURE__ */ jsxs4(
984
+ "button",
985
+ {
986
+ onClick: () => {
987
+ setIsOpen(!isOpen);
988
+ },
989
+ className: "flex items-center gap-1 font-mono text-xs hover:bg-gray-200 dark:hover:bg-gray-800 rounded px-1 -ml-1",
990
+ children: [
991
+ isOpen ? /* @__PURE__ */ jsx5(ChevronDown3, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }) : /* @__PURE__ */ jsx5(ChevronRight3, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }),
992
+ keyName && /* @__PURE__ */ jsxs4("span", { className: "text-purple-600 dark:text-purple-400", children: [
993
+ keyName,
994
+ ":"
995
+ ] }),
996
+ /* @__PURE__ */ jsxs4("span", { className: "text-gray-500", children: [
997
+ "[",
998
+ value.length,
999
+ "]"
1000
+ ] })
1001
+ ]
1002
+ }
1003
+ ),
1004
+ isOpen && /* @__PURE__ */ jsx5("div", { className: "ml-2", children: value.map((item, i) => /* @__PURE__ */ jsx5(
1005
+ StateTree,
1006
+ {
1007
+ value: item,
1008
+ depth: depth + 1,
1009
+ keyName: String(i),
1010
+ defaultExpandDepth
1011
+ },
1012
+ i
1013
+ )) })
1014
+ ] });
1015
+ }
1016
+ const entries = Object.entries(value);
1017
+ if (entries.length === 0) {
1018
+ return /* @__PURE__ */ jsxs4("div", { className: "font-mono text-xs", style: { marginLeft: indent }, children: [
1019
+ keyName && /* @__PURE__ */ jsxs4("span", { className: "text-purple-600 dark:text-purple-400", children: [
1020
+ keyName,
1021
+ ":",
1022
+ " "
1023
+ ] }),
1024
+ /* @__PURE__ */ jsx5("span", { className: "text-gray-400 dark:text-gray-500", children: "{}" })
1025
+ ] });
1026
+ }
1027
+ return /* @__PURE__ */ jsxs4("div", { style: { marginLeft: indent }, children: [
1028
+ /* @__PURE__ */ jsxs4(
1029
+ "button",
1030
+ {
1031
+ onClick: () => {
1032
+ setIsOpen(!isOpen);
1033
+ },
1034
+ className: "flex items-center gap-1 font-mono text-xs hover:bg-gray-200 dark:hover:bg-gray-800 rounded px-1 -ml-1",
1035
+ children: [
1036
+ isOpen ? /* @__PURE__ */ jsx5(ChevronDown3, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }) : /* @__PURE__ */ jsx5(ChevronRight3, { className: "w-3 h-3 text-gray-400 dark:text-gray-500" }),
1037
+ keyName && /* @__PURE__ */ jsxs4("span", { className: "text-purple-600 dark:text-purple-400", children: [
1038
+ keyName,
1039
+ ":"
1040
+ ] }),
1041
+ /* @__PURE__ */ jsx5("span", { className: "text-gray-500", children: `{${entries.length}}` })
1042
+ ]
1043
+ }
1044
+ ),
1045
+ isOpen && /* @__PURE__ */ jsx5("div", { className: "ml-2", children: entries.map(([key, val]) => /* @__PURE__ */ jsx5(
1046
+ StateTree,
1047
+ {
1048
+ value: val,
1049
+ depth: depth + 1,
1050
+ keyName: key,
1051
+ defaultExpandDepth
1052
+ },
1053
+ key
1054
+ )) })
1055
+ ] });
1056
+ }
1057
+ function ValueDisplay({ value }) {
1058
+ if (value === null) {
1059
+ return /* @__PURE__ */ jsx5("span", { className: "text-gray-400 dark:text-gray-500", children: "null" });
1060
+ }
1061
+ if (value === void 0) {
1062
+ return /* @__PURE__ */ jsx5("span", { className: "text-gray-400 dark:text-gray-500", children: "undefined" });
1063
+ }
1064
+ if (typeof value === "boolean") {
1065
+ return /* @__PURE__ */ jsx5("span", { className: "text-blue-600 dark:text-blue-400", children: String(value) });
1066
+ }
1067
+ if (typeof value === "number") {
1068
+ return /* @__PURE__ */ jsx5("span", { className: "text-green-600 dark:text-green-400", children: value });
1069
+ }
1070
+ if (typeof value === "string") {
1071
+ const display = value.length > 50 ? `${value.slice(0, 50)}...` : value;
1072
+ return /* @__PURE__ */ jsxs4("span", { className: "text-amber-600 dark:text-yellow-300", children: [
1073
+ '"',
1074
+ display,
1075
+ '"'
1076
+ ] });
1077
+ }
1078
+ return /* @__PURE__ */ jsx5("span", { className: "text-gray-600 dark:text-gray-400", children: String(value) });
1079
+ }
1080
+
1081
+ // src/components/DevToolsPanel.tsx
1082
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1083
+ function DevToolsPanel({
1084
+ events,
1085
+ onClearEvents,
1086
+ reducerLogs = [],
1087
+ onClearReducerLogs,
1088
+ state = {},
1089
+ networkLog = [],
1090
+ onClearNetworkLog,
1091
+ initialTab = "events",
1092
+ defaultMinimized = false,
1093
+ className = "",
1094
+ title = "DevTools",
1095
+ onMinimize
1096
+ }) {
1097
+ const [activeTab, setActiveTab] = useState5(initialTab);
1098
+ const [isMinimized, setIsMinimized] = useState5(defaultMinimized);
1099
+ const handleMinimize = () => {
1100
+ if (onMinimize) {
1101
+ onMinimize();
1102
+ } else {
1103
+ setIsMinimized(true);
1104
+ }
1105
+ };
1106
+ const tabs = useMemo2(
1107
+ () => [
1108
+ {
1109
+ id: "events",
1110
+ label: "Events",
1111
+ icon: Terminal2,
1112
+ count: events.length
1113
+ },
1114
+ {
1115
+ id: "network",
1116
+ label: "Network",
1117
+ icon: Wifi,
1118
+ count: networkLog.length
1119
+ },
1120
+ {
1121
+ id: "reducer",
1122
+ label: "Actions",
1123
+ icon: Activity2,
1124
+ count: reducerLogs.length
1125
+ },
1126
+ {
1127
+ id: "state",
1128
+ label: "State",
1129
+ icon: Database,
1130
+ count: null
1131
+ }
1132
+ ],
1133
+ [events.length, reducerLogs.length, networkLog.length]
1134
+ );
1135
+ if (isMinimized) {
1136
+ return /* @__PURE__ */ jsxs5(
1137
+ "button",
1138
+ {
1139
+ onClick: () => {
1140
+ setIsMinimized(false);
1141
+ },
1142
+ className: `
1143
+ flex items-center gap-2 px-3 py-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg
1144
+ text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors
1145
+ ${className}
1146
+ `,
1147
+ children: [
1148
+ /* @__PURE__ */ jsx6(Bug, { className: "w-4 h-4" }),
1149
+ /* @__PURE__ */ jsx6("span", { className: "text-xs font-medium", children: title }),
1150
+ events.length > 0 && /* @__PURE__ */ jsx6("span", { className: "text-[10px] px-1.5 py-0.5 bg-cyan-100 dark:bg-cyan-900/50 text-cyan-600 dark:text-cyan-400 rounded", children: events.length }),
1151
+ /* @__PURE__ */ jsx6(Maximize2, { className: "w-3 h-3 ml-1" })
1152
+ ]
1153
+ }
1154
+ );
1155
+ }
1156
+ return /* @__PURE__ */ jsxs5(
1157
+ "div",
1158
+ {
1159
+ className: `h-full flex flex-col bg-white dark:bg-gray-900 ${className}`,
1160
+ children: [
1161
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: [
1162
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 px-2", children: [
1163
+ /* @__PURE__ */ jsx6(Bug, { className: "w-3.5 h-3.5 text-gray-400 dark:text-gray-500" }),
1164
+ /* @__PURE__ */ jsx6("span", { className: "text-[10px] text-gray-500 font-medium uppercase tracking-wider", children: title })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 flex", children: tabs.map((tab) => /* @__PURE__ */ jsxs5(
1167
+ "button",
1168
+ {
1169
+ onClick: () => {
1170
+ setActiveTab(tab.id);
1171
+ },
1172
+ className: `
1173
+ flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors
1174
+ ${activeTab === tab.id ? "text-cyan-600 dark:text-cyan-400 border-cyan-500 dark:border-cyan-400 bg-white dark:bg-gray-900/50" : "text-gray-500 dark:text-gray-400 border-transparent hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700/30"}
1175
+ `,
1176
+ children: [
1177
+ /* @__PURE__ */ jsx6(tab.icon, { className: "w-3.5 h-3.5" }),
1178
+ tab.label,
1179
+ tab.count !== null && tab.count > 0 && /* @__PURE__ */ jsx6("span", { className: "ml-1 px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-[9px]", children: tab.count })
1180
+ ]
1181
+ },
1182
+ tab.id
1183
+ )) }),
1184
+ /* @__PURE__ */ jsx6(
1185
+ "button",
1186
+ {
1187
+ onClick: handleMinimize,
1188
+ className: "px-2 py-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors",
1189
+ title: "Minimize",
1190
+ children: /* @__PURE__ */ jsx6(Minimize2, { className: "w-3.5 h-3.5" })
1191
+ }
1192
+ )
1193
+ ] }),
1194
+ /* @__PURE__ */ jsxs5("div", { className: "flex-1 min-h-0 overflow-hidden", children: [
1195
+ activeTab === "events" && /* @__PURE__ */ jsx6(EventLog, { events, onClear: onClearEvents }),
1196
+ activeTab === "network" && /* @__PURE__ */ jsx6(
1197
+ NetworkLog,
1198
+ {
1199
+ entries: networkLog,
1200
+ onClear: onClearNetworkLog ?? (() => {
1201
+ })
1202
+ }
1203
+ ),
1204
+ activeTab === "reducer" && /* @__PURE__ */ jsx6(
1205
+ ReducerLog,
1206
+ {
1207
+ logs: reducerLogs,
1208
+ onClear: onClearReducerLogs ?? (() => {
1209
+ })
1210
+ }
1211
+ ),
1212
+ activeTab === "state" && /* @__PURE__ */ jsx6("div", { className: "h-full overflow-auto p-2", children: /* @__PURE__ */ jsx6(StateInspector, { state, title: "Current State" }) })
1213
+ ] })
1214
+ ]
1215
+ }
1216
+ );
1217
+ }
1218
+
1219
+ // src/components/StatusBadge.tsx
1220
+ import { jsx as jsx7 } from "react/jsx-runtime";
1221
+ function StatusBadge({ status, className = "" }) {
1222
+ const safeStatus = typeof status === "string" ? status : String(status ?? "idle");
1223
+ return /* @__PURE__ */ jsx7(
1224
+ "span",
1225
+ {
1226
+ className: `
1227
+ inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium
1228
+ ${getStatusColor(safeStatus)}
1229
+ ${className}
1230
+ `,
1231
+ children: formatStatus(safeStatus)
1232
+ }
1233
+ );
1234
+ }
1235
+ function getStatusColor(status) {
1236
+ const lower = status.toLowerCase();
1237
+ if (lower === "idle" || lower === "not_created") {
1238
+ return "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300";
1239
+ }
1240
+ if (lower.includes("address_ready") || lower === "completed") {
1241
+ return "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300";
1242
+ }
1243
+ if (lower.includes("needs") || lower.includes("authorization") || lower.includes("approval")) {
1244
+ return "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300";
1245
+ }
1246
+ if (lower === "confirming") {
1247
+ return "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300";
1248
+ }
1249
+ if (lower === "ready") {
1250
+ return "bg-teal-100 text-teal-700 dark:bg-teal-900/50 dark:text-teal-300";
1251
+ }
1252
+ return "bg-teal-100 text-teal-700 dark:bg-teal-900/50 dark:text-teal-300";
1253
+ }
1254
+ function formatStatus(status) {
1255
+ return status.replace(/[_-]/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1256
+ }
1257
+
1258
+ // src/components/StepIndicator.tsx
1259
+ import { Check as Check2 } from "lucide-react";
1260
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1261
+ function StepIndicator({
1262
+ steps,
1263
+ currentStep,
1264
+ className = ""
1265
+ }) {
1266
+ return /* @__PURE__ */ jsx8(
1267
+ "div",
1268
+ {
1269
+ className: `flex ${className}`,
1270
+ style: {
1271
+ width: "100%"
1272
+ },
1273
+ children: steps.map((step, index) => {
1274
+ const isCompleted = index < currentStep;
1275
+ const isCurrent = index === currentStep;
1276
+ const isLast = index === steps.length - 1;
1277
+ const circleStyles = {
1278
+ width: "28px",
1279
+ height: "28px",
1280
+ minWidth: "28px",
1281
+ minHeight: "28px",
1282
+ borderRadius: "6px"
1283
+ // Slightly rounded corners
1284
+ };
1285
+ if (isCompleted) {
1286
+ circleStyles.backgroundColor = "#22c55e";
1287
+ circleStyles.color = "white";
1288
+ } else if (isCurrent) {
1289
+ circleStyles.backgroundColor = "var(--lombard-green, #00E676)";
1290
+ circleStyles.color = "#111827";
1291
+ circleStyles.fontWeight = "bold";
1292
+ } else {
1293
+ circleStyles.backgroundColor = "#e5e7eb";
1294
+ circleStyles.color = "#6b7280";
1295
+ }
1296
+ return /* @__PURE__ */ jsxs6(
1297
+ "div",
1298
+ {
1299
+ style: {
1300
+ flex: 1,
1301
+ display: "flex",
1302
+ flexDirection: "column",
1303
+ alignItems: "center",
1304
+ position: "relative"
1305
+ },
1306
+ children: [
1307
+ !isLast && /* @__PURE__ */ jsx8(
1308
+ "div",
1309
+ {
1310
+ style: {
1311
+ position: "absolute",
1312
+ top: "14px",
1313
+ // Half of circle height (28/2)
1314
+ left: "50%",
1315
+ right: "-50%",
1316
+ height: "2px",
1317
+ backgroundColor: isCompleted ? "#22c55e" : "#e5e7eb",
1318
+ zIndex: 0
1319
+ }
1320
+ }
1321
+ ),
1322
+ /* @__PURE__ */ jsx8(
1323
+ "div",
1324
+ {
1325
+ className: "flex items-center justify-center text-sm font-medium transition-colors",
1326
+ style: {
1327
+ ...circleStyles,
1328
+ position: "relative",
1329
+ zIndex: 1
1330
+ },
1331
+ children: isCompleted ? /* @__PURE__ */ jsx8(Check2, { style: { width: "16px", height: "16px" } }) : index + 1
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ jsx8(
1335
+ "span",
1336
+ {
1337
+ className: "text-xs text-center",
1338
+ style: {
1339
+ marginTop: "6px",
1340
+ color: isCompleted ? "#16a34a" : isCurrent ? "#111827" : "#9ca3af",
1341
+ // gray-400
1342
+ fontWeight: isCompleted || isCurrent ? 500 : 400
1343
+ },
1344
+ children: step.label
1345
+ }
1346
+ )
1347
+ ]
1348
+ },
1349
+ step.id
1350
+ );
1351
+ })
1352
+ }
1353
+ );
1354
+ }
1355
+ function createSteps(configs, currentStep) {
1356
+ return configs.map((config, index) => ({
1357
+ ...config,
1358
+ status: index < currentStep ? "completed" : index === currentStep ? "current" : "pending"
1359
+ }));
1360
+ }
1361
+
1362
+ // src/hooks/useDevTools.ts
1363
+ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo3, useRef, useState as useState6 } from "react";
1364
+ function useDevTools(config) {
1365
+ const bridgeRef = useRef(null);
1366
+ if (!bridgeRef.current) {
1367
+ bridgeRef.current = config ? new DevToolsBridge(config) : getDevToolsBridge();
1368
+ }
1369
+ const bridge = bridgeRef.current;
1370
+ const [events, setEvents] = useState6(
1371
+ () => bridge.getEvents()
1372
+ );
1373
+ const [actions, setActions] = useState6(
1374
+ () => bridge.getActions()
1375
+ );
1376
+ useEffect2(() => {
1377
+ const unsubEvent = bridge.onEvent(() => {
1378
+ setEvents(bridge.getEvents());
1379
+ });
1380
+ const unsubState = bridge.onStateChange((newActions) => {
1381
+ setActions(newActions);
1382
+ });
1383
+ return () => {
1384
+ unsubEvent();
1385
+ unsubState();
1386
+ };
1387
+ }, [bridge]);
1388
+ const registerAction = useCallback3(
1389
+ (name, action, options) => {
1390
+ return bridge.registerAction(name, action, options);
1391
+ },
1392
+ [bridge]
1393
+ );
1394
+ const unregisterAction = useCallback3(
1395
+ (name) => {
1396
+ bridge.unregisterAction(name);
1397
+ },
1398
+ [bridge]
1399
+ );
1400
+ const clearEvents = useCallback3(() => {
1401
+ bridge.clearEvents();
1402
+ setEvents([]);
1403
+ }, [bridge]);
1404
+ const clearActions = useCallback3(() => {
1405
+ bridge.clearActions();
1406
+ setActions(/* @__PURE__ */ new Map());
1407
+ }, [bridge]);
1408
+ const stateSummary = useMemo3(() => bridge.getStateSummary(), [actions]);
1409
+ return {
1410
+ events,
1411
+ actions,
1412
+ stateSummary,
1413
+ registerAction,
1414
+ unregisterAction,
1415
+ clearEvents,
1416
+ clearActions,
1417
+ bridge
1418
+ };
1419
+ }
1420
+ function useMonitoredAction(name, createAction, deps) {
1421
+ const { registerAction } = useDevTools();
1422
+ const [action, setAction] = useState6(null);
1423
+ useEffect2(() => {
1424
+ const newAction = createAction();
1425
+ setAction(newAction);
1426
+ const unregister = registerAction(name, newAction);
1427
+ return () => {
1428
+ unregister();
1429
+ setAction(null);
1430
+ };
1431
+ }, deps);
1432
+ return action;
1433
+ }
1434
+ function useActionEvents(action) {
1435
+ const [events, setEvents] = useState6([]);
1436
+ const [status, setStatus] = useState6(action?.status ?? "idle");
1437
+ const [isLoading, setIsLoading] = useState6(action?.isLoading ?? false);
1438
+ const [error, setError] = useState6(action?.error ?? null);
1439
+ const eventIdRef = useRef(0);
1440
+ useEffect2(() => {
1441
+ if (!action) return;
1442
+ const unsubscribers = [];
1443
+ unsubscribers.push(
1444
+ action.on("status-change", (newStatus) => {
1445
+ setStatus(String(newStatus));
1446
+ setEvents((prev) => [
1447
+ ...prev,
1448
+ {
1449
+ id: String(++eventIdRef.current),
1450
+ type: "status-change",
1451
+ timestamp: Date.now(),
1452
+ data: newStatus,
1453
+ isSDKEvent: true
1454
+ }
1455
+ ]);
1456
+ })
1457
+ );
1458
+ unsubscribers.push(
1459
+ action.on("loading", (loading) => {
1460
+ setIsLoading(Boolean(loading));
1461
+ setEvents((prev) => [
1462
+ ...prev,
1463
+ {
1464
+ id: String(++eventIdRef.current),
1465
+ type: "loading",
1466
+ timestamp: Date.now(),
1467
+ data: loading,
1468
+ isSDKEvent: true
1469
+ }
1470
+ ]);
1471
+ })
1472
+ );
1473
+ unsubscribers.push(
1474
+ action.on("error", (err) => {
1475
+ setError(err);
1476
+ setEvents((prev) => [
1477
+ ...prev,
1478
+ {
1479
+ id: String(++eventIdRef.current),
1480
+ type: "error",
1481
+ timestamp: Date.now(),
1482
+ data: err,
1483
+ isSDKEvent: true
1484
+ }
1485
+ ]);
1486
+ })
1487
+ );
1488
+ unsubscribers.push(
1489
+ action.on("completed", () => {
1490
+ setEvents((prev) => [
1491
+ ...prev,
1492
+ {
1493
+ id: String(++eventIdRef.current),
1494
+ type: "completed",
1495
+ timestamp: Date.now(),
1496
+ data: null,
1497
+ isSDKEvent: true
1498
+ }
1499
+ ]);
1500
+ })
1501
+ );
1502
+ unsubscribers.push(
1503
+ action.on("failed", () => {
1504
+ setEvents((prev) => [
1505
+ ...prev,
1506
+ {
1507
+ id: String(++eventIdRef.current),
1508
+ type: "failed",
1509
+ timestamp: Date.now(),
1510
+ data: null,
1511
+ isSDKEvent: true
1512
+ }
1513
+ ]);
1514
+ })
1515
+ );
1516
+ return () => {
1517
+ unsubscribers.forEach((unsub) => {
1518
+ unsub();
1519
+ });
1520
+ };
1521
+ }, [action]);
1522
+ const clearEvents = useCallback3(() => {
1523
+ setEvents([]);
1524
+ }, []);
1525
+ return {
1526
+ events,
1527
+ status,
1528
+ isLoading,
1529
+ error,
1530
+ isFailed: error !== null,
1531
+ clearEvents
1532
+ };
1533
+ }
1534
+
1535
+ // src/hooks/useMockWallet.ts
1536
+ import { useCallback as useCallback4, useMemo as useMemo4, useState as useState7 } from "react";
1537
+ var MOCK_ADDRESSES = {
1538
+ // Vitalik's address (properly checksummed)
1539
+ evm: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
1540
+ // Valid mainnet bitcoin address
1541
+ bitcoin: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
1542
+ // Valid Solana address
1543
+ solana: "DRpbCBMxVnDK7maPMxTm9dRYNLGhPEYALmJY9VvUdWTm",
1544
+ // Valid Sui address
1545
+ sui: "0x7d20dcdb2bca4f508ea9613994683eb4e76e9cc555c8e2d4f8362e10e4eca31b",
1546
+ // Valid Starknet address
1547
+ starknet: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"
1548
+ };
1549
+ var MOCK_WALLET_LIMITATIONS = {
1550
+ /** Cannot sign transactions */
1551
+ cannotSign: true,
1552
+ /** Steps that work with mock wallet */
1553
+ supportedSteps: ["Create", "Prepare"],
1554
+ /** Steps that require real wallet */
1555
+ unsupportedSteps: ["Authorize", "Sign", "Execute"],
1556
+ /** User-friendly message */
1557
+ message: "Mock wallet cannot sign transactions. Connect a real wallet for full flow.",
1558
+ /** Short message for badges */
1559
+ shortMessage: "Cannot sign - connect real wallet"
1560
+ };
1561
+ var STORAGE_KEY = "lombard-devtools-mock-wallet";
1562
+ function useMockWallet() {
1563
+ const [isEnabled, setIsEnabled] = useState7(() => {
1564
+ if (typeof window === "undefined") return false;
1565
+ return localStorage.getItem(STORAGE_KEY) === "true";
1566
+ });
1567
+ const [chainId, setChainId] = useState7(1);
1568
+ const enable = useCallback4(() => {
1569
+ setIsEnabled(true);
1570
+ if (typeof window !== "undefined") {
1571
+ localStorage.setItem(STORAGE_KEY, "true");
1572
+ }
1573
+ }, []);
1574
+ const disable = useCallback4(() => {
1575
+ setIsEnabled(false);
1576
+ if (typeof window !== "undefined") {
1577
+ localStorage.setItem(STORAGE_KEY, "false");
1578
+ }
1579
+ }, []);
1580
+ const toggle = useCallback4(() => {
1581
+ setIsEnabled((prev) => {
1582
+ const newValue = !prev;
1583
+ if (typeof window !== "undefined") {
1584
+ localStorage.setItem(STORAGE_KEY, String(newValue));
1585
+ }
1586
+ return newValue;
1587
+ });
1588
+ }, []);
1589
+ const setChain = useCallback4((newChainId) => {
1590
+ setChainId(newChainId);
1591
+ }, []);
1592
+ const getAddress = useCallback4((chainType) => {
1593
+ return MOCK_ADDRESSES[chainType];
1594
+ }, []);
1595
+ return useMemo4(
1596
+ () => ({
1597
+ isEnabled,
1598
+ address: MOCK_ADDRESSES.evm,
1599
+ // Default to EVM
1600
+ chainId,
1601
+ canSign: false,
1602
+ // Mock wallet cannot sign - always false
1603
+ enable,
1604
+ disable,
1605
+ toggle,
1606
+ setChain,
1607
+ getAddress
1608
+ }),
1609
+ [isEnabled, chainId, enable, disable, toggle, setChain, getAddress]
1610
+ );
1611
+ }
1612
+ function getMockAddress(chainType) {
1613
+ return MOCK_ADDRESSES[chainType];
1614
+ }
1615
+ function requiresRealWallet(stepLabel) {
1616
+ const lower = stepLabel.toLowerCase();
1617
+ return lower.includes("authorize") || lower.includes("sign") || lower.includes("execute");
1618
+ }
1619
+ export {
1620
+ DEFAULT_DEVTOOLS_CONFIG,
1621
+ DevToolsBridge,
1622
+ DevToolsPanel,
1623
+ DevToolsProvider,
1624
+ EventLog,
1625
+ MOCK_ADDRESSES,
1626
+ MOCK_WALLET_LIMITATIONS,
1627
+ NetworkLog,
1628
+ ReducerLog,
1629
+ StateInspector,
1630
+ StatusBadge,
1631
+ StepIndicator,
1632
+ createSteps,
1633
+ getDevToolsBridge,
1634
+ getMockAddress,
1635
+ requiresRealWallet,
1636
+ resetDevToolsBridge,
1637
+ useActionEvents,
1638
+ useDevTools,
1639
+ useDevToolsContext,
1640
+ useMockWallet,
1641
+ useMonitoredAction,
1642
+ useRegisterAction
1643
+ };
1644
+ //# sourceMappingURL=index.js.map