@lombard.finance/sdk-devtools 0.1.0-canary.0.4

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