@sweidos/eidos 1.0.24 → 1.0.30

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.
@@ -0,0 +1,597 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useSyncExternalStore, useState, useCallback } from "react";
4
+ let _state;
5
+ const _listeners = /* @__PURE__ */ new Set();
6
+ function _notify() {
7
+ _listeners.forEach((fn) => fn());
8
+ }
9
+ function _set(updater) {
10
+ _state = { ..._state, ...updater(_state) };
11
+ _notify();
12
+ }
13
+ _state = {
14
+ isOnline: typeof navigator !== "undefined" ? navigator.onLine : true,
15
+ swStatus: "idle",
16
+ swError: void 0,
17
+ resources: {},
18
+ queue: [],
19
+ setOnline: (isOnline) => _set(() => ({ isOnline })),
20
+ setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),
21
+ registerResource: (url, entry) => _set((s) => ({ resources: { ...s.resources, [url]: entry } })),
22
+ updateResource: (url, update) => _set((s) => ({
23
+ resources: {
24
+ ...s.resources,
25
+ [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url]
26
+ }
27
+ })),
28
+ unregisterResource: (url) => _set((s) => {
29
+ const { [url]: _removed, ...rest } = s.resources;
30
+ return { resources: rest };
31
+ }),
32
+ addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),
33
+ updateQueueItem: (id, update) => _set((s) => ({
34
+ queue: s.queue.map((item) => item.id === id ? { ...item, ...update } : item)
35
+ })),
36
+ batchUpdateQueueItems: (updates) => _set((s) => {
37
+ const map = new Map(updates.map((u) => [u.id, u.update]));
38
+ return {
39
+ queue: s.queue.map((item) => {
40
+ const u = map.get(item.id);
41
+ return u ? { ...item, ...u } : item;
42
+ })
43
+ };
44
+ }),
45
+ removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),
46
+ hydrateQueue: (items) => _set(() => ({ queue: items }))
47
+ };
48
+ function _getState() {
49
+ return _state;
50
+ }
51
+ function _subscribe(listener) {
52
+ _listeners.add(listener);
53
+ return () => {
54
+ _listeners.delete(listener);
55
+ };
56
+ }
57
+ const useEidosStore = {
58
+ getState: _getState,
59
+ subscribe: _subscribe,
60
+ // Test/devtools helper — merges partial state, preserves action methods.
61
+ setState: (partial) => {
62
+ const update = typeof partial === "function" ? partial(_state) : partial;
63
+ _state = { ..._state, ...update };
64
+ _notify();
65
+ }
66
+ };
67
+ function useStore(selector) {
68
+ const fn = selector ?? ((s) => s);
69
+ return useSyncExternalStore(useEidosStore.subscribe, () => fn(useEidosStore.getState()));
70
+ }
71
+ function useEidosResources() {
72
+ return useStore((s) => s.resources);
73
+ }
74
+ function useEidosQueue() {
75
+ return useStore((s) => s.queue);
76
+ }
77
+ function useEidosStatus() {
78
+ const isOnline = useStore((s) => s.isOnline);
79
+ const swStatus = useStore((s) => s.swStatus);
80
+ const swError = useStore((s) => s.swError);
81
+ return { isOnline, swStatus, swError };
82
+ }
83
+ function useEidosQueueStats() {
84
+ const encoded = useStore((s) => {
85
+ let pending = 0, failed = 0, replaying = 0;
86
+ for (const q of s.queue) {
87
+ if (q.status === "pending") pending++;
88
+ else if (q.status === "failed") failed++;
89
+ else if (q.status === "replaying") replaying++;
90
+ }
91
+ return `${pending},${failed},${replaying},${s.queue.length}`;
92
+ });
93
+ const [p, f, r, t] = encoded.split(",");
94
+ return { pending: +p, failed: +f, replaying: +r, total: +t };
95
+ }
96
+ function setOfflineSimulation(enabled) {
97
+ useEidosStore.getState().setOnline(!enabled);
98
+ }
99
+ const DB_NAME = "eidos";
100
+ const DB_VERSION = 1;
101
+ const QUEUE_STORE = "action-queue";
102
+ let _db = null;
103
+ function openDB() {
104
+ if (_db) return Promise.resolve(_db);
105
+ return new Promise((resolve, reject) => {
106
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
107
+ req.onupgradeneeded = (event) => {
108
+ const db = event.target.result;
109
+ if (!db.objectStoreNames.contains(QUEUE_STORE)) {
110
+ const store = db.createObjectStore(QUEUE_STORE, { keyPath: "id" });
111
+ store.createIndex("status", "status", { unique: false });
112
+ store.createIndex("actionId", "actionId", { unique: false });
113
+ }
114
+ };
115
+ req.onsuccess = () => {
116
+ _db = req.result;
117
+ resolve(req.result);
118
+ };
119
+ req.onerror = () => reject(req.error);
120
+ });
121
+ }
122
+ async function idbUpdateQueueItem(id, update) {
123
+ const db = await openDB();
124
+ return new Promise((resolve, reject) => {
125
+ const tx = db.transaction(QUEUE_STORE, "readwrite");
126
+ const store = tx.objectStore(QUEUE_STORE);
127
+ const get = store.get(id);
128
+ get.onsuccess = () => {
129
+ if (get.result) {
130
+ store.put({ ...get.result, ...update });
131
+ }
132
+ };
133
+ tx.oncomplete = () => resolve();
134
+ tx.onerror = () => reject(tx.error);
135
+ });
136
+ }
137
+ async function idbRemoveFromQueue(id) {
138
+ const db = await openDB();
139
+ return new Promise((resolve, reject) => {
140
+ const tx = db.transaction(QUEUE_STORE, "readwrite");
141
+ tx.objectStore(QUEUE_STORE).delete(id);
142
+ tx.oncomplete = () => resolve();
143
+ tx.onerror = () => reject(tx.error);
144
+ });
145
+ }
146
+ async function idbGetPendingItems() {
147
+ const db = await openDB();
148
+ return new Promise((resolve, reject) => {
149
+ const tx = db.transaction(QUEUE_STORE, "readonly");
150
+ const index = tx.objectStore(QUEUE_STORE).index("status");
151
+ const results = [];
152
+ let done = 0;
153
+ function finish(err) {
154
+ if (err) {
155
+ reject(err);
156
+ return;
157
+ }
158
+ if (++done === 2) resolve(results);
159
+ }
160
+ const pendingReq = index.openCursor(IDBKeyRange.only("pending"));
161
+ pendingReq.onsuccess = (e) => {
162
+ const cursor = e.target.result;
163
+ if (cursor) {
164
+ results.push(cursor.value);
165
+ cursor.continue();
166
+ } else finish();
167
+ };
168
+ pendingReq.onerror = () => finish(pendingReq.error);
169
+ const failedReq = index.openCursor(IDBKeyRange.only("failed"));
170
+ failedReq.onsuccess = (e) => {
171
+ const cursor = e.target.result;
172
+ if (cursor) {
173
+ results.push(cursor.value);
174
+ cursor.continue();
175
+ } else finish();
176
+ };
177
+ failedReq.onerror = () => finish(failedReq.error);
178
+ });
179
+ }
180
+ async function idbClearQueue() {
181
+ const db = await openDB();
182
+ return new Promise((resolve, reject) => {
183
+ const tx = db.transaction(QUEUE_STORE, "readwrite");
184
+ tx.objectStore(QUEUE_STORE).clear();
185
+ tx.oncomplete = () => resolve();
186
+ tx.onerror = () => reject(tx.error);
187
+ });
188
+ }
189
+ const _actionRegistry = /* @__PURE__ */ new Map();
190
+ const _rollbackRegistry = /* @__PURE__ */ new Map();
191
+ const _conflictRegistry = /* @__PURE__ */ new Map();
192
+ function isClientError(err) {
193
+ if (err instanceof Response) return err.status >= 400 && err.status < 500;
194
+ if (typeof err === "object" && err !== null) {
195
+ const s = err.status;
196
+ if (typeof s === "number") return s >= 400 && s < 500;
197
+ }
198
+ return false;
199
+ }
200
+ function backoffMs(retryCount) {
201
+ const base = Math.min(2e3 * 2 ** retryCount, 3e5);
202
+ return base * (0.8 + Math.random() * 0.4);
203
+ }
204
+ let _replaying = false;
205
+ async function replayQueue() {
206
+ const store = useEidosStore.getState();
207
+ if (!store.isOnline || _replaying) {
208
+ return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 };
209
+ }
210
+ _replaying = true;
211
+ try {
212
+ return await _doReplayQueue(store);
213
+ } finally {
214
+ _replaying = false;
215
+ }
216
+ }
217
+ async function _replayItem(item, store) {
218
+ var _a;
219
+ const fn = _actionRegistry.get(item.actionId);
220
+ if (!fn) return "skipped";
221
+ try {
222
+ await fn(...item.args);
223
+ const completedAt = Date.now();
224
+ store.updateQueueItem(item.id, { status: "succeeded", completedAt });
225
+ await idbUpdateQueueItem(item.id, { status: "succeeded", completedAt });
226
+ setTimeout(() => {
227
+ store.removeQueueItem(item.id);
228
+ idbRemoveFromQueue(item.id);
229
+ }, 3e3);
230
+ return "succeeded";
231
+ } catch (err) {
232
+ if (isClientError(err)) {
233
+ const onConflict = _conflictRegistry.get(item.actionId);
234
+ if (onConflict) {
235
+ const resolution = onConflict(err, item.args);
236
+ if (resolution === "skip") {
237
+ store.removeQueueItem(item.id);
238
+ await idbRemoveFromQueue(item.id);
239
+ return "conflicted";
240
+ }
241
+ }
242
+ }
243
+ const retryCount = item.retryCount + 1;
244
+ if (retryCount >= item.maxRetries) {
245
+ store.updateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
246
+ await idbUpdateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
247
+ (_a = _rollbackRegistry.get(item.actionId)) == null ? void 0 : _a(...item.args);
248
+ return "failed";
249
+ } else {
250
+ const nextRetryAt = Date.now() + backoffMs(retryCount);
251
+ store.updateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
252
+ await idbUpdateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
253
+ return "retrying";
254
+ }
255
+ }
256
+ }
257
+ async function _replayTier(items, store, result) {
258
+ if (items.length === 0) return;
259
+ const replayable = items.filter((item) => _actionRegistry.has(item.actionId));
260
+ result.skipped += items.length - replayable.length;
261
+ if (replayable.length > 0) {
262
+ store.batchUpdateQueueItems(replayable.map((item) => ({ id: item.id, update: { status: "replaying" } })));
263
+ for (const item of replayable) {
264
+ idbUpdateQueueItem(item.id, { status: "replaying" });
265
+ }
266
+ }
267
+ const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)));
268
+ for (const o of outcomes) {
269
+ const outcome = o.status === "fulfilled" ? o.value : "failed";
270
+ if (outcome === "skipped") {
271
+ result.skipped++;
272
+ } else if (outcome === "conflicted") {
273
+ result.conflicted++;
274
+ } else {
275
+ result.attempted++;
276
+ result[outcome]++;
277
+ }
278
+ }
279
+ }
280
+ async function _doReplayQueue(store) {
281
+ const candidates = await idbGetPendingItems();
282
+ const now = Date.now();
283
+ const pending = candidates.filter((item) => !item.nextRetryAt || item.nextRetryAt <= now);
284
+ const result = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 };
285
+ for (const tier of ["high", "normal", "low"]) {
286
+ const tierItems = pending.filter((item) => (item.priority ?? "normal") === tier);
287
+ await _replayTier(tierItems, store, result);
288
+ }
289
+ return result;
290
+ }
291
+ async function clearQueue() {
292
+ await idbClearQueue();
293
+ useEidosStore.getState().hydrateQueue([]);
294
+ }
295
+ const C = {
296
+ bg: "#0f1117",
297
+ surface: "#1a1d27",
298
+ border: "#2a2d3a",
299
+ text: "#e2e8f0",
300
+ muted: "#64748b",
301
+ green: "#22c55e",
302
+ red: "#ef4444",
303
+ yellow: "#f59e0b",
304
+ blue: "#3b82f6",
305
+ purple: "#a855f7",
306
+ cyan: "#06b6d4"
307
+ };
308
+ function pill(color) {
309
+ return {
310
+ display: "inline-flex",
311
+ alignItems: "center",
312
+ padding: "1px 7px",
313
+ borderRadius: 9999,
314
+ fontSize: 10,
315
+ fontWeight: 600,
316
+ background: `${color}22`,
317
+ color,
318
+ border: `1px solid ${color}44`,
319
+ fontFamily: "inherit"
320
+ };
321
+ }
322
+ function btn(variant = "ghost") {
323
+ const base = {
324
+ cursor: "pointer",
325
+ border: "none",
326
+ borderRadius: 6,
327
+ padding: "3px 10px",
328
+ fontSize: 11,
329
+ fontWeight: 500,
330
+ fontFamily: "inherit",
331
+ transition: "opacity 0.15s"
332
+ };
333
+ if (variant === "danger") return { ...base, background: `${C.red}22`, color: C.red };
334
+ if (variant === "primary") return { ...base, background: `${C.blue}22`, color: C.blue };
335
+ return { ...base, background: C.surface, color: C.muted };
336
+ }
337
+ function swStatusColor(s) {
338
+ if (s === "active") return C.green;
339
+ if (s === "registering") return C.yellow;
340
+ if (s === "error" || s === "unsupported") return C.red;
341
+ return C.muted;
342
+ }
343
+ function queueStatusColor(s) {
344
+ if (s === "succeeded") return C.green;
345
+ if (s === "failed") return C.red;
346
+ if (s === "replaying") return C.yellow;
347
+ return C.blue;
348
+ }
349
+ function resourceStatusColor(s) {
350
+ if (s === "fresh") return C.green;
351
+ if (s === "stale" || s === "offline") return C.yellow;
352
+ if (s === "error") return C.red;
353
+ if (s === "fetching") return C.cyan;
354
+ return C.muted;
355
+ }
356
+ function positionStyle(p) {
357
+ const base = { position: "fixed", zIndex: 99999 };
358
+ if (p === "bottom-left") return { ...base, bottom: 16, left: 16 };
359
+ if (p === "top-right") return { ...base, top: 16, right: 16 };
360
+ if (p === "top-left") return { ...base, top: 16, left: 16 };
361
+ return { ...base, bottom: 16, right: 16 };
362
+ }
363
+ function EidosDevtools({ position = "bottom-right", defaultOpen = false }) {
364
+ const [open, setOpen] = useState(defaultOpen);
365
+ const [tab, setTab] = useState("queue");
366
+ const [simOffline, setSimOffline] = useState(false);
367
+ const { isOnline, swStatus, swError } = useEidosStatus();
368
+ const queue = useEidosQueue();
369
+ const { pending, failed, replaying } = useEidosQueueStats();
370
+ const resources = useEidosResources();
371
+ const resourceList = Object.values(resources);
372
+ const badgeCount = pending + failed + replaying;
373
+ const toggleOffline = useCallback(() => {
374
+ const next = !simOffline;
375
+ setSimOffline(next);
376
+ setOfflineSimulation(next);
377
+ }, [simOffline]);
378
+ const handleReplay = useCallback(() => {
379
+ void replayQueue();
380
+ }, []);
381
+ const handleClear = useCallback(() => {
382
+ void clearQueue();
383
+ }, []);
384
+ const toggleBtn = /* @__PURE__ */ jsxs(
385
+ "button",
386
+ {
387
+ onClick: () => setOpen((v) => !v),
388
+ title: "Eidos Devtools",
389
+ style: {
390
+ display: "flex",
391
+ alignItems: "center",
392
+ gap: 6,
393
+ padding: "5px 10px",
394
+ background: C.bg,
395
+ border: `1px solid ${C.border}`,
396
+ borderRadius: 9999,
397
+ cursor: "pointer",
398
+ color: C.text,
399
+ fontSize: 11,
400
+ fontWeight: 600,
401
+ fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
402
+ boxShadow: "0 2px 12px rgba(0,0,0,0.5)",
403
+ userSelect: "none"
404
+ },
405
+ children: [
406
+ /* @__PURE__ */ jsx("span", { style: { color: isOnline ? C.green : C.red, fontSize: 8 }, children: "●" }),
407
+ /* @__PURE__ */ jsx("span", { style: { color: C.cyan }, children: "⚡" }),
408
+ /* @__PURE__ */ jsx("span", { children: "eidos" }),
409
+ badgeCount > 0 && /* @__PURE__ */ jsx("span", { style: {
410
+ background: failed > 0 ? C.red : C.yellow,
411
+ color: "#fff",
412
+ borderRadius: 9999,
413
+ minWidth: 16,
414
+ height: 16,
415
+ display: "inline-flex",
416
+ alignItems: "center",
417
+ justifyContent: "center",
418
+ fontSize: 9,
419
+ fontWeight: 700,
420
+ padding: "0 4px"
421
+ }, children: badgeCount })
422
+ ]
423
+ }
424
+ );
425
+ if (!open) {
426
+ return /* @__PURE__ */ jsx("div", { style: positionStyle(position), children: toggleBtn });
427
+ }
428
+ return /* @__PURE__ */ jsxs("div", { style: { ...positionStyle(position), display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 }, children: [
429
+ /* @__PURE__ */ jsxs("div", { style: {
430
+ width: 340,
431
+ maxHeight: 480,
432
+ display: "flex",
433
+ flexDirection: "column",
434
+ background: C.bg,
435
+ border: `1px solid ${C.border}`,
436
+ borderRadius: 10,
437
+ overflow: "hidden",
438
+ boxShadow: "0 8px 32px rgba(0,0,0,0.6)",
439
+ fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
440
+ fontSize: 11,
441
+ color: C.text
442
+ }, children: [
443
+ /* @__PURE__ */ jsxs("div", { style: {
444
+ display: "flex",
445
+ alignItems: "center",
446
+ gap: 8,
447
+ padding: "8px 12px",
448
+ background: C.surface,
449
+ borderBottom: `1px solid ${C.border}`,
450
+ flexShrink: 0
451
+ }, children: [
452
+ /* @__PURE__ */ jsx("span", { style: { color: C.cyan, fontSize: 13 }, children: "⚡" }),
453
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 700, fontSize: 12, color: C.text, flex: 1 }, children: "Eidos Devtools" }),
454
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
455
+ /* @__PURE__ */ jsx("span", { style: pill(isOnline ? C.green : C.red), children: isOnline ? "online" : "offline" }),
456
+ /* @__PURE__ */ jsx(
457
+ "button",
458
+ {
459
+ onClick: toggleOffline,
460
+ title: simOffline ? "Disable offline simulation" : "Enable offline simulation",
461
+ style: {
462
+ ...btn("ghost"),
463
+ background: simOffline ? `${C.yellow}22` : C.surface,
464
+ color: simOffline ? C.yellow : C.muted,
465
+ border: `1px solid ${simOffline ? C.yellow + "44" : C.border}`,
466
+ fontSize: 10
467
+ },
468
+ children: simOffline ? "📡 simulating" : "✈ sim offline"
469
+ }
470
+ )
471
+ ] })
472
+ ] }),
473
+ /* @__PURE__ */ jsxs("div", { style: {
474
+ display: "flex",
475
+ alignItems: "center",
476
+ gap: 8,
477
+ padding: "6px 12px",
478
+ borderBottom: `1px solid ${C.border}`,
479
+ flexShrink: 0,
480
+ background: C.bg
481
+ }, children: [
482
+ /* @__PURE__ */ jsx("span", { style: { color: C.muted, fontSize: 10 }, children: "SW" }),
483
+ /* @__PURE__ */ jsx("span", { style: pill(swStatusColor(swStatus)), children: swStatus }),
484
+ swError && /* @__PURE__ */ jsx("span", { style: { color: C.red, fontSize: 10, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: swError })
485
+ ] }),
486
+ /* @__PURE__ */ jsx("div", { style: {
487
+ display: "flex",
488
+ borderBottom: `1px solid ${C.border}`,
489
+ flexShrink: 0,
490
+ background: C.surface
491
+ }, children: ["queue", "cache"].map((t) => /* @__PURE__ */ jsx(
492
+ "button",
493
+ {
494
+ onClick: () => setTab(t),
495
+ style: {
496
+ flex: 1,
497
+ padding: "6px 0",
498
+ background: "none",
499
+ border: "none",
500
+ borderBottom: tab === t ? `2px solid ${C.cyan}` : "2px solid transparent",
501
+ cursor: "pointer",
502
+ color: tab === t ? C.cyan : C.muted,
503
+ fontSize: 11,
504
+ fontWeight: tab === t ? 600 : 400,
505
+ fontFamily: "inherit",
506
+ textTransform: "uppercase",
507
+ letterSpacing: "0.05em"
508
+ },
509
+ children: t === "queue" ? `Queue (${queue.length})` : `Cache (${resourceList.length})`
510
+ },
511
+ t
512
+ )) }),
513
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 }, children: tab === "queue" ? /* @__PURE__ */ jsx(QueueTab, { queue, onReplay: handleReplay, onClear: handleClear }) : /* @__PURE__ */ jsx(CacheTab, { resources: resourceList }) })
514
+ ] }),
515
+ toggleBtn
516
+ ] });
517
+ }
518
+ function QueueTab({
519
+ queue,
520
+ onReplay,
521
+ onClear
522
+ }) {
523
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
524
+ /* @__PURE__ */ jsxs("div", { style: {
525
+ display: "flex",
526
+ gap: 6,
527
+ padding: "8px 12px",
528
+ borderBottom: `1px solid ${C.border}`,
529
+ flexShrink: 0
530
+ }, children: [
531
+ /* @__PURE__ */ jsx("button", { onClick: onReplay, style: btn("primary"), children: "▶ Replay queue" }),
532
+ /* @__PURE__ */ jsx("button", { onClick: onClear, style: btn("danger"), children: "✕ Clear queue" }),
533
+ /* @__PURE__ */ jsxs("span", { style: { marginLeft: "auto", color: C.muted, fontSize: 10, alignSelf: "center" }, children: [
534
+ queue.length,
535
+ " item",
536
+ queue.length !== 1 ? "s" : ""
537
+ ] })
538
+ ] }),
539
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflowY: "auto" }, children: queue.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "20px 12px", textAlign: "center", color: C.muted }, children: "Queue empty" }) : queue.map((item) => /* @__PURE__ */ jsxs("div", { style: {
540
+ display: "flex",
541
+ alignItems: "center",
542
+ gap: 8,
543
+ padding: "7px 12px",
544
+ borderBottom: `1px solid ${C.border}`
545
+ }, children: [
546
+ /* @__PURE__ */ jsx("span", { style: pill(queueStatusColor(item.status)), children: item.status }),
547
+ item.priority && item.priority !== "normal" && /* @__PURE__ */ jsx("span", { style: pill(item.priority === "high" ? C.purple : C.muted), children: item.priority }),
548
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: C.text }, children: item.actionName }),
549
+ item.retryCount > 0 && /* @__PURE__ */ jsxs("span", { style: { color: C.muted, fontSize: 10, flexShrink: 0 }, children: [
550
+ "×",
551
+ item.retryCount,
552
+ "/",
553
+ item.maxRetries
554
+ ] })
555
+ ] }, item.id)) })
556
+ ] });
557
+ }
558
+ function CacheTab({ resources }) {
559
+ return /* @__PURE__ */ jsx("div", { children: resources.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "20px 12px", textAlign: "center", color: C.muted }, children: "No resources registered" }) : resources.map((res) => /* @__PURE__ */ jsxs("div", { style: {
560
+ padding: "7px 12px",
561
+ borderBottom: `1px solid ${C.border}`
562
+ }, children: [
563
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }, children: [
564
+ /* @__PURE__ */ jsx("span", { style: pill(resourceStatusColor(res.status)), children: res.status }),
565
+ /* @__PURE__ */ jsx("span", { style: { color: C.muted, fontSize: 10 }, children: res.strategy.name })
566
+ ] }),
567
+ /* @__PURE__ */ jsx("div", { style: {
568
+ color: C.text,
569
+ overflow: "hidden",
570
+ textOverflow: "ellipsis",
571
+ whiteSpace: "nowrap",
572
+ fontSize: 10,
573
+ marginBottom: 2
574
+ }, children: res.url }),
575
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, color: C.muted, fontSize: 10 }, children: [
576
+ /* @__PURE__ */ jsxs("span", { title: "Cache hits", children: [
577
+ "↑",
578
+ res.cacheHits,
579
+ " hit",
580
+ res.cacheHits !== 1 ? "s" : ""
581
+ ] }),
582
+ /* @__PURE__ */ jsxs("span", { title: "Cache misses", children: [
583
+ "↓",
584
+ res.cacheMisses,
585
+ " miss",
586
+ res.cacheMisses !== 1 ? "es" : ""
587
+ ] }),
588
+ res.cachedAt && /* @__PURE__ */ jsxs("span", { title: "Cached at", children: [
589
+ "⏱ ",
590
+ new Date(res.cachedAt).toLocaleTimeString()
591
+ ] })
592
+ ] })
593
+ ] }, res.url)) });
594
+ }
595
+ export {
596
+ EidosDevtools
597
+ };