@kontsedal/olas-devtools 0.0.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1922 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let react = require("react");
3
+ let _kontsedal_olas_react = require("@kontsedal/olas-react");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let _kontsedal_olas_core = require("@kontsedal/olas-core");
6
+ //#region src/format.ts
7
+ /**
8
+ * Render a payload (props, vars, result, etc.) as a single-line string for
9
+ * the panel. Cuts at `maxLen` so a giant blob doesn't blow up the layout.
10
+ */
11
+ function formatPayload(value, maxLen = 200) {
12
+ if (value === void 0) return "undefined";
13
+ if (value === null) return "null";
14
+ if (typeof value === "function") return "[fn]";
15
+ let s;
16
+ try {
17
+ s = JSON.stringify(value, replaceUnserializable);
18
+ } catch {
19
+ s = String(value);
20
+ }
21
+ if (s === void 0) s = String(value);
22
+ return s.length > maxLen ? `${s.slice(0, maxLen)}…` : s;
23
+ }
24
+ function replaceUnserializable(_key, value) {
25
+ if (typeof value === "function") return "[fn]";
26
+ if (typeof value === "bigint") return value.toString();
27
+ if (value instanceof Error) return {
28
+ name: value.name,
29
+ message: value.message
30
+ };
31
+ return value;
32
+ }
33
+ /** Render an HH:MM:SS.mmm timestamp from epoch ms. */
34
+ function formatTime(t) {
35
+ const d = new Date(t);
36
+ const pad = (n, w = 2) => n.toString().padStart(w, "0");
37
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
38
+ }
39
+ /** Render a controller path / query key as a compact string. */
40
+ function formatPath(path) {
41
+ if (path.length === 0) return "∅";
42
+ return path.map((p) => String(p)).join(" › ");
43
+ }
44
+ //#endregion
45
+ //#region src/JsonView.tsx
46
+ function JsonView({ value, depth = 0 }) {
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Render, {
48
+ value,
49
+ depth,
50
+ initiallyOpen: depth === 0
51
+ });
52
+ }
53
+ function Render({ value, depth, initiallyOpen }) {
54
+ if (value === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
55
+ className: "olas-devtools-json-null",
56
+ children: "null"
57
+ });
58
+ if (value === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
59
+ className: "olas-devtools-json-null",
60
+ children: "undefined"
61
+ });
62
+ const t = typeof value;
63
+ if (t === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
64
+ className: "olas-devtools-json-string",
65
+ children: [
66
+ "\"",
67
+ value,
68
+ "\""
69
+ ]
70
+ });
71
+ if (t === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
72
+ className: "olas-devtools-json-number",
73
+ children: String(value)
74
+ });
75
+ if (t === "boolean") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
76
+ className: "olas-devtools-json-boolean",
77
+ children: String(value)
78
+ });
79
+ if (t === "bigint") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
80
+ className: "olas-devtools-json-number",
81
+ children: [String(value), "n"]
82
+ });
83
+ if (value instanceof Error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
84
+ className: "olas-devtools-json-error",
85
+ children: [
86
+ value.name,
87
+ "(",
88
+ JSON.stringify(value.message),
89
+ ")"
90
+ ]
91
+ });
92
+ if (Array.isArray(value)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleArray, {
93
+ value,
94
+ depth,
95
+ initiallyOpen
96
+ });
97
+ if (t === "object") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleObject, {
98
+ value,
99
+ depth,
100
+ initiallyOpen
101
+ });
102
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: String(value) });
103
+ }
104
+ function CollapsibleArray({ value, depth, initiallyOpen }) {
105
+ const [open, setOpen] = (0, react.useState)(initiallyOpen && value.length <= 12);
106
+ if (value.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
107
+ className: "olas-devtools-json-bracket",
108
+ children: "[]"
109
+ });
110
+ if (!open) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
111
+ type: "button",
112
+ className: "olas-devtools-json-toggle",
113
+ onClick: () => setOpen(true),
114
+ children: [
115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
116
+ className: "olas-devtools-json-bracket",
117
+ children: "["
118
+ }),
119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
120
+ className: "olas-devtools-json-summary",
121
+ children: [
122
+ value.length,
123
+ " item",
124
+ value.length === 1 ? "" : "s"
125
+ ]
126
+ }),
127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
128
+ className: "olas-devtools-json-bracket",
129
+ children: "]"
130
+ })
131
+ ]
132
+ });
133
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
134
+ className: "olas-devtools-json-block",
135
+ children: [
136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
137
+ type: "button",
138
+ className: "olas-devtools-json-toggle olas-devtools-json-toggle-open",
139
+ onClick: () => setOpen(false),
140
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
141
+ className: "olas-devtools-json-bracket",
142
+ children: "["
143
+ })
144
+ }),
145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
146
+ className: "olas-devtools-json-children",
147
+ children: value.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
148
+ className: "olas-devtools-json-row",
149
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
150
+ className: "olas-devtools-json-index",
151
+ children: [idx, ":"]
152
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Render, {
153
+ value: item,
154
+ depth: depth + 1,
155
+ initiallyOpen: false
156
+ })]
157
+ }, idx))
158
+ }),
159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
160
+ className: "olas-devtools-json-bracket",
161
+ children: "]"
162
+ })
163
+ ]
164
+ });
165
+ }
166
+ function CollapsibleObject({ value, depth, initiallyOpen }) {
167
+ const keys = Object.keys(value);
168
+ const [open, setOpen] = (0, react.useState)(initiallyOpen && keys.length <= 8);
169
+ if (keys.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
170
+ className: "olas-devtools-json-bracket",
171
+ children: "{}"
172
+ });
173
+ if (!open) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
174
+ type: "button",
175
+ className: "olas-devtools-json-toggle",
176
+ onClick: () => setOpen(true),
177
+ children: [
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
179
+ className: "olas-devtools-json-bracket",
180
+ children: "{"
181
+ }),
182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
183
+ className: "olas-devtools-json-summary",
184
+ children: [keys.slice(0, 3).join(", "), keys.length > 3 ? ` +${keys.length - 3}` : ""]
185
+ }),
186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
187
+ className: "olas-devtools-json-bracket",
188
+ children: "}"
189
+ })
190
+ ]
191
+ });
192
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
193
+ className: "olas-devtools-json-block",
194
+ children: [
195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
196
+ type: "button",
197
+ className: "olas-devtools-json-toggle olas-devtools-json-toggle-open",
198
+ onClick: () => setOpen(false),
199
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
200
+ className: "olas-devtools-json-bracket",
201
+ children: "{"
202
+ })
203
+ }),
204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
205
+ className: "olas-devtools-json-children",
206
+ children: keys.map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
207
+ className: "olas-devtools-json-row",
208
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
209
+ className: "olas-devtools-json-key",
210
+ children: [k, ":"]
211
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Render, {
212
+ value: value[k],
213
+ depth: depth + 1,
214
+ initiallyOpen: false
215
+ })]
216
+ }, k))
217
+ }),
218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
219
+ className: "olas-devtools-json-bracket",
220
+ children: "}"
221
+ })
222
+ ]
223
+ });
224
+ }
225
+ /**
226
+ * Subscribes to a root's `__debug` bus and maintains live state for the
227
+ * devtools panel. Exposes signals so the React layer can consume via
228
+ * `@kontsedal/olas-react`'s `use()`.
229
+ *
230
+ * Pure logic — no DOM, no React. Construct one per root.
231
+ */
232
+ var DevtoolsStore = class {
233
+ tree$ = (0, _kontsedal_olas_core.signal)(makeRoot());
234
+ cache$ = (0, _kontsedal_olas_core.signal)([]);
235
+ mutations$ = (0, _kontsedal_olas_core.signal)([]);
236
+ fields$ = (0, _kontsedal_olas_core.signal)([]);
237
+ maxEntries;
238
+ now;
239
+ nextId = 1;
240
+ /** Keyed by `path|name` so a mutation:run can be paired with its
241
+ * success/error to compute duration. Cleared after pairing. */
242
+ mutationStarts = /* @__PURE__ */ new Map();
243
+ constructor(options) {
244
+ this.maxEntries = options?.maxEntries ?? 100;
245
+ this.now = options?.now ?? (() => Date.now());
246
+ }
247
+ /**
248
+ * Subscribe to the given root's debug bus. Returns the unsubscribe. The
249
+ * caller (typically the React component) is responsible for invoking it
250
+ * on unmount.
251
+ */
252
+ attach(root) {
253
+ return root.__debug.subscribe((event) => this.handle(event));
254
+ }
255
+ /** Apply one event. Exposed for tests. */
256
+ handle(event) {
257
+ switch (event.type) {
258
+ case "controller:constructed":
259
+ this.tree$.set(insertNode(this.tree$.peek(), event.path, event.props));
260
+ return;
261
+ case "controller:suspended":
262
+ this.tree$.set(setNodeState(this.tree$.peek(), event.path, "suspended"));
263
+ return;
264
+ case "controller:resumed":
265
+ this.tree$.set(setNodeState(this.tree$.peek(), event.path, "active"));
266
+ return;
267
+ case "controller:disposed":
268
+ this.tree$.set(setNodeState(this.tree$.peek(), event.path, "disposed"));
269
+ return;
270
+ case "cache:subscribed":
271
+ this.pushCache({
272
+ kind: "subscribed",
273
+ queryKey: event.queryKey,
274
+ subscriberPath: event.subscriberPath
275
+ });
276
+ return;
277
+ case "cache:fetch-start":
278
+ this.pushCache({
279
+ kind: "fetch-start",
280
+ queryKey: event.queryKey
281
+ });
282
+ return;
283
+ case "cache:fetch-success":
284
+ this.pushCache({
285
+ kind: "fetch-success",
286
+ queryKey: event.queryKey,
287
+ durationMs: event.durationMs
288
+ });
289
+ return;
290
+ case "cache:fetch-error":
291
+ this.pushCache({
292
+ kind: "fetch-error",
293
+ queryKey: event.queryKey,
294
+ durationMs: event.durationMs,
295
+ error: event.error
296
+ });
297
+ return;
298
+ case "cache:invalidated":
299
+ this.pushCache({
300
+ kind: "invalidated",
301
+ queryKey: event.queryKey
302
+ });
303
+ return;
304
+ case "cache:gc":
305
+ this.pushCache({
306
+ kind: "gc",
307
+ queryKey: event.queryKey
308
+ });
309
+ return;
310
+ case "mutation:run":
311
+ this.mutationStarts.set(mutationKey(event.path, event.name), this.now());
312
+ this.pushMutation({
313
+ kind: "run",
314
+ path: event.path,
315
+ name: event.name,
316
+ vars: event.vars
317
+ });
318
+ return;
319
+ case "mutation:success": {
320
+ const durationMs = this.consumeStart(event.path, event.name);
321
+ this.pushMutation({
322
+ kind: "success",
323
+ path: event.path,
324
+ name: event.name,
325
+ result: event.result,
326
+ ...durationMs !== void 0 ? { durationMs } : {}
327
+ });
328
+ return;
329
+ }
330
+ case "mutation:error": {
331
+ const durationMs = this.consumeStart(event.path, event.name);
332
+ this.pushMutation({
333
+ kind: "error",
334
+ path: event.path,
335
+ name: event.name,
336
+ error: event.error,
337
+ ...durationMs !== void 0 ? { durationMs } : {}
338
+ });
339
+ return;
340
+ }
341
+ case "mutation:rollback":
342
+ this.pushMutation({
343
+ kind: "rollback",
344
+ path: event.path,
345
+ name: event.name
346
+ });
347
+ return;
348
+ case "field:validated":
349
+ this.pushField({
350
+ path: event.path,
351
+ field: event.field,
352
+ valid: event.valid,
353
+ errors: event.errors
354
+ });
355
+ return;
356
+ }
357
+ }
358
+ /** Clear every log. Tree state is preserved — the live tree is not a log. */
359
+ clearLogs() {
360
+ this.cache$.set([]);
361
+ this.mutations$.set([]);
362
+ this.fields$.set([]);
363
+ }
364
+ pushCache(entry) {
365
+ const full = {
366
+ id: this.nextId++,
367
+ t: this.now(),
368
+ ...entry
369
+ };
370
+ this.cache$.set(appendBounded(this.cache$.peek(), full, this.maxEntries));
371
+ }
372
+ pushMutation(entry) {
373
+ const full = {
374
+ id: this.nextId++,
375
+ t: this.now(),
376
+ ...entry
377
+ };
378
+ this.mutations$.set(appendBounded(this.mutations$.peek(), full, this.maxEntries));
379
+ }
380
+ pushField(entry) {
381
+ const full = {
382
+ id: this.nextId++,
383
+ t: this.now(),
384
+ ...entry
385
+ };
386
+ this.fields$.set(appendBounded(this.fields$.peek(), full, this.maxEntries));
387
+ }
388
+ consumeStart(path, name) {
389
+ const key = mutationKey(path, name);
390
+ const startedAt = this.mutationStarts.get(key);
391
+ if (startedAt === void 0) return void 0;
392
+ this.mutationStarts.delete(key);
393
+ return this.now() - startedAt;
394
+ }
395
+ };
396
+ function mutationKey(path, name) {
397
+ return `${path.join(">")}#${name ?? ""}`;
398
+ }
399
+ function makeRoot() {
400
+ return {
401
+ path: [],
402
+ state: "active",
403
+ props: void 0,
404
+ children: []
405
+ };
406
+ }
407
+ function appendBounded(arr, item, max) {
408
+ const next = arr.length >= max ? arr.slice(arr.length - max + 1) : arr.slice();
409
+ next.push(item);
410
+ return next;
411
+ }
412
+ /**
413
+ * Insert (or update) a node at `path` inside the tree. Auto-creates any
414
+ * missing intermediate ancestors as 'active' placeholders — needed if the
415
+ * subscriber attached after the root was constructed.
416
+ *
417
+ * Returns a NEW tree object (immutable update).
418
+ */
419
+ function insertNode(root, path, props) {
420
+ if (path.length === 0) return {
421
+ ...root,
422
+ state: "active",
423
+ props
424
+ };
425
+ return cloneWithUpsert(root, path, 0, props);
426
+ }
427
+ function cloneWithUpsert(node, path, depth, props) {
428
+ if (depth === path.length) return {
429
+ ...node,
430
+ state: "active",
431
+ props
432
+ };
433
+ const segment = path[depth];
434
+ const idx = node.children.findIndex((c) => c.path[c.path.length - 1] === segment);
435
+ const childPath = path.slice(0, depth + 1);
436
+ if (idx === -1) {
437
+ const newChild = cloneWithUpsert({
438
+ path: childPath,
439
+ state: "active",
440
+ props: void 0,
441
+ children: []
442
+ }, path, depth + 1, props);
443
+ return {
444
+ ...node,
445
+ children: [...node.children, newChild]
446
+ };
447
+ }
448
+ const existing = node.children[idx];
449
+ const updatedChild = cloneWithUpsert(existing, path, depth + 1, props);
450
+ const nextChildren = node.children.slice();
451
+ nextChildren[idx] = updatedChild;
452
+ return {
453
+ ...node,
454
+ children: nextChildren
455
+ };
456
+ }
457
+ /**
458
+ * Set `state` on the node at `path`. If the node doesn't exist (out-of-order
459
+ * event delivery), the tree is returned unchanged.
460
+ */
461
+ function setNodeState(root, path, state) {
462
+ if (path.length === 0) return {
463
+ ...root,
464
+ state
465
+ };
466
+ return setStateAt(root, path, 0, state) ?? root;
467
+ }
468
+ function setStateAt(node, path, depth, state) {
469
+ if (depth === path.length) return {
470
+ ...node,
471
+ state
472
+ };
473
+ const segment = path[depth];
474
+ const idx = node.children.findIndex((c) => c.path[c.path.length - 1] === segment);
475
+ if (idx === -1) return null;
476
+ const existing = node.children[idx];
477
+ const updatedChild = setStateAt(existing, path, depth + 1, state);
478
+ if (updatedChild === null) return null;
479
+ const nextChildren = node.children.slice();
480
+ nextChildren[idx] = updatedChild;
481
+ return {
482
+ ...node,
483
+ children: nextChildren
484
+ };
485
+ }
486
+ //#endregion
487
+ //#region src/styles.ts
488
+ /**
489
+ * Inline CSS for the devtools panel. Scoped to the `.olas-devtools-*` class
490
+ * prefix so it doesn't bleed into the host app. Honors `prefers-color-scheme`
491
+ * and accepts host palette overrides via `--olas-*` custom properties.
492
+ */
493
+ const DEVTOOLS_CSS = `
494
+ .olas-devtools {
495
+ container-type: inline-size;
496
+ --olas-bg: #ffffff;
497
+ --olas-fg: #1f2330;
498
+ --olas-muted: #6b7280;
499
+ --olas-soft: #f5f6f9;
500
+ --olas-soft-2: #eef0f4;
501
+ --olas-accent: #4f46e5;
502
+ --olas-accent-soft: rgba(79,70,229,0.10);
503
+ --olas-success: #1e8a3d;
504
+ --olas-success-soft: rgba(30,138,61,0.12);
505
+ --olas-warn: #ad6800;
506
+ --olas-warn-soft: rgba(173,104,0,0.12);
507
+ --olas-error: #b8361f;
508
+ --olas-error-soft: rgba(184,54,31,0.12);
509
+ --olas-border: #e6e6ea;
510
+ --olas-border-soft: #eeeef2;
511
+ --olas-row-alt: #fafafc;
512
+
513
+ /* JSON viewer colors */
514
+ --olas-json-key: #7a4ab8;
515
+ --olas-json-string: #1e8a3d;
516
+ --olas-json-number: #b25400;
517
+ --olas-json-boolean: #4f46e5;
518
+ --olas-json-null: #9aa0aa;
519
+ --olas-json-bracket: #6b7280;
520
+ --olas-json-summary: #6b7280;
521
+
522
+ font-family: -apple-system, BlinkMacSystemFont, "Inter var", "Inter", "Segoe UI", system-ui, sans-serif;
523
+ font-size: 12.5px;
524
+ line-height: 1.55;
525
+ color: var(--olas-fg);
526
+ background: var(--olas-bg);
527
+ border: 1px solid var(--olas-border);
528
+ border-radius: 10px;
529
+ display: flex;
530
+ flex-direction: column;
531
+ height: 100%;
532
+ min-height: 320px;
533
+ overflow: hidden;
534
+ box-sizing: border-box;
535
+ }
536
+ .olas-devtools *,
537
+ .olas-devtools *::before,
538
+ .olas-devtools *::after { box-sizing: border-box; }
539
+
540
+ @media (prefers-color-scheme: dark) {
541
+ .olas-devtools {
542
+ --olas-bg: #15171e;
543
+ --olas-fg: #e8ebf2;
544
+ --olas-muted: #97a0b3;
545
+ --olas-soft: #1d2029;
546
+ --olas-soft-2: #232733;
547
+ --olas-accent: #8b86f0;
548
+ --olas-accent-soft: rgba(139,134,240,0.18);
549
+ --olas-success: #4ade80;
550
+ --olas-success-soft: rgba(74,222,128,0.16);
551
+ --olas-warn: #f5b740;
552
+ --olas-warn-soft: rgba(245,183,64,0.16);
553
+ --olas-error: #ef6b53;
554
+ --olas-error-soft: rgba(239,107,83,0.16);
555
+ --olas-border: #2a2e3a;
556
+ --olas-border-soft: #20232c;
557
+ --olas-row-alt: #1a1d25;
558
+
559
+ --olas-json-key: #c39bff;
560
+ --olas-json-string: #7ee79d;
561
+ --olas-json-number: #f5b740;
562
+ --olas-json-boolean: #8b86f0;
563
+ --olas-json-null: #7c8090;
564
+ --olas-json-bracket: #97a0b3;
565
+ --olas-json-summary: #97a0b3;
566
+ }
567
+ }
568
+
569
+ /* ---- tabs ------------------------------------------------------------- */
570
+ .olas-devtools-tabs {
571
+ display: flex;
572
+ align-items: center;
573
+ gap: 1px;
574
+ border-bottom: 1px solid var(--olas-border);
575
+ background: var(--olas-soft);
576
+ padding: 0 8px;
577
+ flex-shrink: 0;
578
+ overflow-x: auto;
579
+ scrollbar-width: none;
580
+ }
581
+ .olas-devtools-tabs::-webkit-scrollbar { display: none; }
582
+ .olas-devtools-tab {
583
+ display: inline-flex;
584
+ align-items: center;
585
+ gap: 6px;
586
+ padding: 9px 10px 8px;
587
+ background: transparent;
588
+ color: var(--olas-muted);
589
+ border: 0;
590
+ border-bottom: 2px solid transparent;
591
+ margin-bottom: -1px;
592
+ cursor: pointer;
593
+ font: inherit;
594
+ font-weight: 500;
595
+ font-size: 12px;
596
+ white-space: nowrap;
597
+ flex-shrink: 0;
598
+ transition: color 80ms, border-color 80ms;
599
+ }
600
+ .olas-devtools-tab-label-full { display: inline; }
601
+ .olas-devtools-tab-label-short { display: none; }
602
+ @container (max-width: 480px) {
603
+ .olas-devtools-tab { padding: 9px 8px 8px; font-size: 11.5px; gap: 4px; }
604
+ .olas-devtools-tab-label-full { display: none; }
605
+ .olas-devtools-tab-label-short { display: inline; }
606
+ .olas-devtools-pause-text,
607
+ .olas-devtools-clear-text { display: none; }
608
+ }
609
+ .olas-devtools-tab:hover { color: var(--olas-fg); }
610
+ .olas-devtools-tab[aria-selected="true"] {
611
+ color: var(--olas-fg);
612
+ border-bottom-color: var(--olas-accent);
613
+ }
614
+ .olas-devtools-tab[aria-selected="true"] .olas-devtools-tab-count {
615
+ background: var(--olas-accent-soft);
616
+ color: var(--olas-accent);
617
+ }
618
+ .olas-devtools-tab-count {
619
+ min-width: 18px;
620
+ padding: 0 6px;
621
+ height: 16px;
622
+ border-radius: 999px;
623
+ background: color-mix(in oklch, var(--olas-fg) 8%, transparent);
624
+ color: var(--olas-muted);
625
+ font-size: 10.5px;
626
+ font-weight: 600;
627
+ display: inline-flex;
628
+ align-items: center;
629
+ justify-content: center;
630
+ font-variant-numeric: tabular-nums;
631
+ line-height: 1;
632
+ }
633
+ .olas-devtools-pause,
634
+ .olas-devtools-clear {
635
+ padding: 4px 10px;
636
+ background: transparent;
637
+ color: var(--olas-muted);
638
+ border: 0;
639
+ border-radius: 6px;
640
+ cursor: pointer;
641
+ font: inherit;
642
+ font-size: 11.5px;
643
+ align-self: center;
644
+ }
645
+ .olas-devtools-pause { margin-left: auto; }
646
+ .olas-devtools-pause:hover,
647
+ .olas-devtools-clear:hover {
648
+ color: var(--olas-fg);
649
+ background: color-mix(in oklch, var(--olas-bg) 60%, transparent);
650
+ }
651
+ .olas-devtools-pause-on { color: var(--olas-warn); background: var(--olas-warn-soft); }
652
+ .olas-devtools-pause-on:hover { color: var(--olas-warn); }
653
+ .olas-devtools-clear-icon { display: none; }
654
+ @container (max-width: 480px) {
655
+ .olas-devtools-clear-text { display: none; }
656
+ .olas-devtools-clear-icon { display: inline; }
657
+ .olas-devtools-pause, .olas-devtools-clear { padding: 4px 8px; }
658
+ }
659
+
660
+ /* ---- filter ---------------------------------------------------------- */
661
+ .olas-devtools-filter {
662
+ position: sticky;
663
+ top: 0;
664
+ z-index: 1;
665
+ display: flex;
666
+ align-items: center;
667
+ gap: 6px;
668
+ padding: 8px 10px;
669
+ border-bottom: 1px solid var(--olas-border-soft);
670
+ background: var(--olas-bg);
671
+ }
672
+ .olas-devtools-filter input {
673
+ flex: 1;
674
+ padding: 5px 9px;
675
+ border: 1px solid var(--olas-border);
676
+ border-radius: 6px;
677
+ background: var(--olas-soft);
678
+ color: var(--olas-fg);
679
+ font: inherit;
680
+ font-size: 12px;
681
+ outline: none;
682
+ transition: border-color 80ms, box-shadow 80ms;
683
+ }
684
+ .olas-devtools-filter input:focus {
685
+ border-color: var(--olas-accent);
686
+ box-shadow: 0 0 0 2px var(--olas-accent-soft);
687
+ }
688
+ .olas-devtools-filter input::placeholder { color: var(--olas-muted); }
689
+ .olas-devtools-filter button {
690
+ background: transparent;
691
+ border: 0;
692
+ color: var(--olas-muted);
693
+ cursor: pointer;
694
+ font: inherit;
695
+ font-size: 13px;
696
+ padding: 2px 6px;
697
+ }
698
+ .olas-devtools-filter button:hover { color: var(--olas-fg); }
699
+
700
+ /* ---- body ------------------------------------------------------------ */
701
+ .olas-devtools-body {
702
+ flex: 1;
703
+ overflow: auto;
704
+ }
705
+
706
+ /* ---- list rows ------------------------------------------------------- */
707
+ .olas-devtools-list {
708
+ margin: 0;
709
+ padding: 0;
710
+ list-style: none;
711
+ }
712
+ .olas-devtools-list li {
713
+ border-bottom: 1px solid var(--olas-border-soft);
714
+ display: flex;
715
+ flex-direction: column;
716
+ }
717
+ .olas-devtools-list li:last-child { border-bottom: none; }
718
+ .olas-devtools-list li:hover { background: var(--olas-row-alt); }
719
+
720
+ .olas-devtools-row-top {
721
+ display: flex;
722
+ align-items: center;
723
+ gap: 10px;
724
+ padding: 8px 12px;
725
+ min-height: 32px;
726
+ }
727
+ .olas-devtools-row-clickable .olas-devtools-row-top { cursor: pointer; user-select: none; }
728
+
729
+ .olas-devtools-target {
730
+ flex: 1;
731
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
732
+ font-size: 11.5px;
733
+ color: var(--olas-fg);
734
+ word-break: break-word;
735
+ min-width: 0;
736
+ }
737
+ .olas-devtools-target strong { font-weight: 600; color: var(--olas-accent); }
738
+ .olas-devtools-time {
739
+ color: var(--olas-muted);
740
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
741
+ font-size: 10.5px;
742
+ font-variant-numeric: tabular-nums;
743
+ white-space: nowrap;
744
+ }
745
+ .olas-devtools-chevron {
746
+ display: inline-flex;
747
+ align-items: center;
748
+ justify-content: center;
749
+ color: var(--olas-muted);
750
+ font-size: 12px;
751
+ width: 16px;
752
+ height: 16px;
753
+ transition: transform 100ms;
754
+ user-select: none;
755
+ }
756
+ .olas-devtools-chevron-open { transform: rotate(90deg); color: var(--olas-fg); }
757
+
758
+ .olas-devtools-kind {
759
+ color: var(--olas-accent);
760
+ background: var(--olas-accent-soft);
761
+ border-radius: 4px;
762
+ padding: 1px 7px;
763
+ font-size: 10.5px;
764
+ font-weight: 600;
765
+ letter-spacing: 0.02em;
766
+ white-space: nowrap;
767
+ line-height: 1.4;
768
+ font-variant-numeric: tabular-nums;
769
+ }
770
+ .olas-devtools-kind-success { color: var(--olas-success); background: var(--olas-success-soft); }
771
+ .olas-devtools-kind-error { color: var(--olas-error); background: var(--olas-error-soft); }
772
+ .olas-devtools-kind-warn,
773
+ .olas-devtools-kind-rollback { color: var(--olas-warn); background: var(--olas-warn-soft); }
774
+ .olas-devtools-duration {
775
+ color: var(--olas-muted);
776
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
777
+ font-size: 10.5px;
778
+ font-variant-numeric: tabular-nums;
779
+ background: var(--olas-soft);
780
+ border-radius: 4px;
781
+ padding: 1px 6px;
782
+ white-space: nowrap;
783
+ }
784
+
785
+ .olas-devtools-payload {
786
+ margin: 0 12px 10px;
787
+ padding: 8px 10px;
788
+ background: var(--olas-soft);
789
+ border: 1px solid var(--olas-border-soft);
790
+ border-radius: 6px;
791
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
792
+ font-size: 11.5px;
793
+ color: var(--olas-fg);
794
+ overflow-x: auto;
795
+ }
796
+ .olas-devtools-payload-inline {
797
+ margin-top: -4px;
798
+ padding: 4px 10px;
799
+ background: transparent;
800
+ border: 0;
801
+ color: var(--olas-muted);
802
+ font-size: 11px;
803
+ }
804
+ .olas-devtools-payload-json { line-height: 1.5; }
805
+
806
+ /* ---- JSON viewer ----------------------------------------------------- */
807
+ .olas-devtools-json-row {
808
+ display: block;
809
+ padding-left: 14px;
810
+ white-space: pre-wrap;
811
+ word-break: break-word;
812
+ }
813
+ .olas-devtools-json-children {
814
+ display: block;
815
+ border-left: 1px dashed var(--olas-border);
816
+ margin-left: 4px;
817
+ }
818
+ .olas-devtools-json-block {
819
+ display: inline-flex;
820
+ flex-direction: column;
821
+ vertical-align: top;
822
+ }
823
+ .olas-devtools-json-key {
824
+ color: var(--olas-json-key);
825
+ margin-right: 6px;
826
+ font-weight: 500;
827
+ }
828
+ .olas-devtools-json-index {
829
+ color: var(--olas-json-bracket);
830
+ margin-right: 6px;
831
+ }
832
+ .olas-devtools-json-string { color: var(--olas-json-string); }
833
+ .olas-devtools-json-number { color: var(--olas-json-number); }
834
+ .olas-devtools-json-boolean { color: var(--olas-json-boolean); }
835
+ .olas-devtools-json-null { color: var(--olas-json-null); font-style: italic; }
836
+ .olas-devtools-json-bracket { color: var(--olas-json-bracket); }
837
+ .olas-devtools-json-error { color: var(--olas-error); }
838
+ .olas-devtools-json-summary {
839
+ color: var(--olas-json-summary);
840
+ font-style: italic;
841
+ margin: 0 4px;
842
+ }
843
+ .olas-devtools-json-toggle {
844
+ display: inline-flex;
845
+ align-items: center;
846
+ gap: 0;
847
+ background: transparent;
848
+ border: 0;
849
+ padding: 0;
850
+ margin: 0;
851
+ cursor: pointer;
852
+ color: inherit;
853
+ font: inherit;
854
+ }
855
+ .olas-devtools-json-toggle:hover { background: var(--olas-accent-soft); border-radius: 3px; }
856
+
857
+ /* ---- empty state ---------------------------------------------------- */
858
+ .olas-devtools-empty {
859
+ padding: 36px 24px;
860
+ text-align: center;
861
+ color: var(--olas-muted);
862
+ }
863
+ .olas-devtools-empty-title {
864
+ color: var(--olas-fg);
865
+ font-weight: 600;
866
+ font-size: 13px;
867
+ margin-bottom: 4px;
868
+ }
869
+ .olas-devtools-empty-hint {
870
+ font-size: 12px;
871
+ max-width: 320px;
872
+ margin: 0 auto;
873
+ line-height: 1.55;
874
+ }
875
+
876
+ /* ---- tree ------------------------------------------------------------ */
877
+ .olas-devtools-tree { padding: 10px 12px; }
878
+ .olas-devtools-tree-node {
879
+ padding: 1px 0;
880
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
881
+ font-size: 12px;
882
+ }
883
+ .olas-devtools-tree-row {
884
+ display: inline-flex;
885
+ align-items: center;
886
+ gap: 8px;
887
+ padding: 1px 0;
888
+ }
889
+ .olas-devtools-tree-name { color: var(--olas-fg); font-weight: 500; }
890
+ .olas-devtools-tree-state-active,
891
+ .olas-devtools-tree-state-suspended,
892
+ .olas-devtools-tree-state-disposed {
893
+ border-radius: 4px;
894
+ padding: 0 6px;
895
+ font-size: 9.5px;
896
+ font-weight: 700;
897
+ text-transform: uppercase;
898
+ letter-spacing: 0.05em;
899
+ }
900
+ .olas-devtools-tree-state-active {
901
+ color: var(--olas-success);
902
+ background: var(--olas-success-soft);
903
+ }
904
+ .olas-devtools-tree-state-suspended {
905
+ color: var(--olas-warn);
906
+ background: var(--olas-warn-soft);
907
+ }
908
+ .olas-devtools-tree-state-disposed {
909
+ color: var(--olas-muted);
910
+ background: color-mix(in oklch, var(--olas-fg) 8%, transparent);
911
+ }
912
+ .olas-devtools-tree-pending {
913
+ color: var(--olas-warn);
914
+ background: var(--olas-warn-soft);
915
+ border-radius: 4px;
916
+ padding: 0 6px;
917
+ font-size: 10px;
918
+ font-weight: 600;
919
+ letter-spacing: 0.02em;
920
+ }
921
+ .olas-devtools-tree-props-toggle {
922
+ background: transparent;
923
+ border: 0;
924
+ cursor: pointer;
925
+ font: inherit;
926
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
927
+ font-size: 11px;
928
+ color: var(--olas-muted);
929
+ padding: 0 4px;
930
+ border-radius: 4px;
931
+ max-width: 280px;
932
+ overflow: hidden;
933
+ text-overflow: ellipsis;
934
+ white-space: nowrap;
935
+ text-align: left;
936
+ }
937
+ .olas-devtools-tree-props-toggle:hover {
938
+ color: var(--olas-fg);
939
+ background: var(--olas-soft);
940
+ }
941
+ .olas-devtools-tree-props {
942
+ margin: 4px 0 6px 8px;
943
+ padding: 6px 10px;
944
+ background: var(--olas-soft);
945
+ border: 1px solid var(--olas-border-soft);
946
+ border-radius: 6px;
947
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
948
+ font-size: 11px;
949
+ overflow-x: auto;
950
+ }
951
+ .olas-devtools-tree-children {
952
+ margin-left: 8px;
953
+ border-left: 1px dashed var(--olas-border);
954
+ padding-left: 10px;
955
+ margin-top: 2px;
956
+ }
957
+
958
+ /* ---- floating window + launcher ------------------------------------- */
959
+ .olas-devtools-launcher {
960
+ position: fixed;
961
+ right: 16px;
962
+ bottom: 16px;
963
+ z-index: 2147483645;
964
+ display: inline-flex;
965
+ align-items: center;
966
+ gap: 8px;
967
+ padding: 7px 12px 7px 11px;
968
+ background: #1f2330;
969
+ color: #e8ebf2;
970
+ border: 1px solid #2a2e3a;
971
+ border-radius: 999px;
972
+ box-shadow: 0 2px 6px rgba(0,0,0,0.15), 0 8px 24px rgba(0,0,0,0.18);
973
+ cursor: pointer;
974
+ font: inherit;
975
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
976
+ font-size: 12px;
977
+ font-weight: 500;
978
+ }
979
+ .olas-devtools-launcher:hover { filter: brightness(1.1); }
980
+ .olas-devtools-launcher-active { box-shadow: 0 0 0 2px rgba(139,134,240,0.5), 0 8px 24px rgba(0,0,0,0.18); }
981
+ .olas-devtools-launcher-dot {
982
+ width: 8px;
983
+ height: 8px;
984
+ border-radius: 50%;
985
+ background: #4ade80;
986
+ box-shadow: 0 0 6px rgba(74,222,128,0.7);
987
+ }
988
+ .olas-devtools-launcher-label { letter-spacing: 0.01em; }
989
+
990
+ .olas-devtools-floating {
991
+ position: fixed;
992
+ z-index: 2147483646;
993
+ display: flex;
994
+ flex-direction: column;
995
+ background: var(--olas-bg, #ffffff);
996
+ color: var(--olas-fg, #1f2330);
997
+ border: 1px solid var(--olas-border, #e6e6ea);
998
+ border-radius: 10px;
999
+ box-shadow: 0 8px 24px rgba(0,0,0,0.18), 0 24px 64px rgba(0,0,0,0.18);
1000
+ overflow: hidden;
1001
+ /* Inherit the panel's own CSS vars when DevtoolsPanel is mounted inside. */
1002
+ }
1003
+ @media (prefers-color-scheme: dark) {
1004
+ .olas-devtools-floating {
1005
+ background: #15171e;
1006
+ color: #e8ebf2;
1007
+ border-color: #2a2e3a;
1008
+ box-shadow: 0 8px 24px rgba(0,0,0,0.5), 0 24px 64px rgba(0,0,0,0.5);
1009
+ }
1010
+ }
1011
+ .olas-devtools-floating-header {
1012
+ display: flex;
1013
+ align-items: center;
1014
+ gap: 8px;
1015
+ height: 30px;
1016
+ padding: 0 8px 0 10px;
1017
+ background: var(--olas-soft, #f5f6f9);
1018
+ border-bottom: 1px solid var(--olas-border, #e6e6ea);
1019
+ cursor: grab;
1020
+ user-select: none;
1021
+ flex-shrink: 0;
1022
+ }
1023
+ .olas-devtools-floating-header:active { cursor: grabbing; }
1024
+ .olas-devtools-floating-grip {
1025
+ color: var(--olas-muted, #6b7280);
1026
+ font-size: 14px;
1027
+ line-height: 1;
1028
+ }
1029
+ .olas-devtools-floating-title {
1030
+ flex: 1;
1031
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
1032
+ font-size: 11.5px;
1033
+ font-weight: 600;
1034
+ color: var(--olas-fg, #1f2330);
1035
+ letter-spacing: 0.01em;
1036
+ }
1037
+ .olas-devtools-floating-actions { display: inline-flex; gap: 2px; }
1038
+ .olas-devtools-floating-action {
1039
+ width: 22px;
1040
+ height: 22px;
1041
+ display: inline-flex;
1042
+ align-items: center;
1043
+ justify-content: center;
1044
+ background: transparent;
1045
+ border: 0;
1046
+ border-radius: 4px;
1047
+ color: var(--olas-muted, #6b7280);
1048
+ cursor: pointer;
1049
+ font: inherit;
1050
+ font-size: 14px;
1051
+ line-height: 1;
1052
+ }
1053
+ .olas-devtools-floating-action:hover {
1054
+ color: var(--olas-fg, #1f2330);
1055
+ background: color-mix(in oklch, currentColor 14%, transparent);
1056
+ }
1057
+ .olas-devtools-floating-body {
1058
+ flex: 1;
1059
+ min-height: 0;
1060
+ display: flex;
1061
+ }
1062
+ .olas-devtools-floating-body > .olas-devtools {
1063
+ flex: 1;
1064
+ border: 0;
1065
+ border-radius: 0;
1066
+ min-height: 0;
1067
+ }
1068
+ .olas-devtools-floating-resize {
1069
+ position: absolute;
1070
+ right: 0;
1071
+ bottom: 0;
1072
+ width: 14px;
1073
+ height: 14px;
1074
+ cursor: nwse-resize;
1075
+ background:
1076
+ linear-gradient(135deg, transparent 0 7px, var(--olas-muted, #6b7280) 7px 8px, transparent 8px 10px,
1077
+ var(--olas-muted, #6b7280) 10px 11px, transparent 11px 100%);
1078
+ opacity: 0.6;
1079
+ }
1080
+ .olas-devtools-floating-resize:hover { opacity: 1; }
1081
+ `;
1082
+ //#endregion
1083
+ //#region src/DevtoolsPanel.tsx
1084
+ /**
1085
+ * Drop-in devtools panel for an Olas root.
1086
+ *
1087
+ * Features:
1088
+ * - **Tree** populated from the snapshot replay on mount (no lost events).
1089
+ * - **Cache / Mutations / Fields** event logs in reverse chronological order.
1090
+ * - **Filter** field per tab — text-matches kind, path, name, payload.
1091
+ * - **Pause** toggle freezes the log without stopping ingestion.
1092
+ * - **Click a row** to expand its payload from a truncated preview to the full
1093
+ * JSON.
1094
+ * - **Mutation durations** — `run → success/error` pairing surfaces elapsed ms.
1095
+ *
1096
+ * Styled inline (no CSS import needed) and scoped to the `.olas-devtools-*`
1097
+ * class prefix. Hosts override the palette via `--olas-*` custom properties.
1098
+ * Spec §13.
1099
+ */
1100
+ function DevtoolsPanel(props) {
1101
+ const { root, defaultTab = "tree", maxEntries, urlHashKey, inspectorPollMs = 800 } = props;
1102
+ const store = (0, react.useMemo)(() => new DevtoolsStore(maxEntries !== void 0 ? { maxEntries } : void 0), [maxEntries]);
1103
+ (0, react.useEffect)(() => store.attach(root), [root, store]);
1104
+ const initial = (0, react.useMemo)(() => readUrlHash(urlHashKey, defaultTab), [urlHashKey, defaultTab]);
1105
+ const [tab, setTab] = (0, react.useState)(initial.tab);
1106
+ const [paused, setPaused] = (0, react.useState)(false);
1107
+ const [filters, setFilters] = (0, react.useState)(initial.filters);
1108
+ const filter = filters[tab];
1109
+ const setFilter = (q) => setFilters((prev) => ({
1110
+ ...prev,
1111
+ [tab]: q
1112
+ }));
1113
+ (0, react.useEffect)(() => {
1114
+ if (urlHashKey === void 0) return;
1115
+ writeUrlHash(urlHashKey, {
1116
+ tab,
1117
+ filters
1118
+ });
1119
+ }, [
1120
+ urlHashKey,
1121
+ tab,
1122
+ filters
1123
+ ]);
1124
+ const [cacheEntries, setCacheEntries] = (0, react.useState)([]);
1125
+ const rootRef = (0, react.useRef)(root);
1126
+ rootRef.current = root;
1127
+ (0, react.useEffect)(() => {
1128
+ if (tab !== "inspector") return;
1129
+ const tick = () => setCacheEntries(rootRef.current.__debug.queryEntries());
1130
+ tick();
1131
+ const id = window.setInterval(tick, inspectorPollMs);
1132
+ return () => window.clearInterval(id);
1133
+ }, [tab, inspectorPollMs]);
1134
+ const liveTree = (0, _kontsedal_olas_react.use)(store.tree$);
1135
+ const liveCache = (0, _kontsedal_olas_react.use)(store.cache$);
1136
+ const liveMutations = (0, _kontsedal_olas_react.use)(store.mutations$);
1137
+ const liveFields = (0, _kontsedal_olas_react.use)(store.fields$);
1138
+ const [frozen, setFrozen] = (0, react.useState)(null);
1139
+ (0, react.useEffect)(() => {
1140
+ if (paused) setFrozen({
1141
+ tree: liveTree,
1142
+ cache: liveCache,
1143
+ mutations: liveMutations,
1144
+ fields: liveFields
1145
+ });
1146
+ else setFrozen(null);
1147
+ }, [paused]);
1148
+ const tree = frozen?.tree ?? liveTree;
1149
+ const cache = frozen?.cache ?? liveCache;
1150
+ const mutations = frozen?.mutations ?? liveMutations;
1151
+ const fields = frozen?.fields ?? liveFields;
1152
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1153
+ className: "olas-devtools",
1154
+ "data-testid": "olas-devtools",
1155
+ children: [
1156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: DEVTOOLS_CSS }),
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1158
+ className: "olas-devtools-tabs",
1159
+ role: "tablist",
1160
+ children: [
1161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
1162
+ name: "tree",
1163
+ current: tab,
1164
+ setTab,
1165
+ label: "Tree",
1166
+ short: "Tree",
1167
+ count: countLiveControllers(liveTree)
1168
+ }),
1169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
1170
+ name: "cache",
1171
+ current: tab,
1172
+ setTab,
1173
+ label: "Cache",
1174
+ short: "Cache",
1175
+ count: liveCache.length
1176
+ }),
1177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
1178
+ name: "inspector",
1179
+ current: tab,
1180
+ setTab,
1181
+ label: "Inspector",
1182
+ short: "Insp",
1183
+ count: cacheEntries.length
1184
+ }),
1185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
1186
+ name: "mutations",
1187
+ current: tab,
1188
+ setTab,
1189
+ label: "Mutations",
1190
+ short: "Mut",
1191
+ count: liveMutations.length
1192
+ }),
1193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
1194
+ name: "fields",
1195
+ current: tab,
1196
+ setTab,
1197
+ label: "Fields",
1198
+ short: "Fld",
1199
+ count: liveFields.length
1200
+ }),
1201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1202
+ type: "button",
1203
+ "aria-pressed": paused,
1204
+ className: paused ? "olas-devtools-pause olas-devtools-pause-on" : "olas-devtools-pause",
1205
+ onClick: () => setPaused(!paused),
1206
+ title: paused ? "Resume live updates" : "Pause live updates",
1207
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1208
+ "aria-hidden": "true",
1209
+ children: paused ? "▶" : "⏸"
1210
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1211
+ className: "olas-devtools-pause-text",
1212
+ children: paused ? " Resume" : " Pause"
1213
+ })]
1214
+ }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1216
+ className: "olas-devtools-clear",
1217
+ type: "button",
1218
+ onClick: () => store.clearLogs(),
1219
+ title: "Clear logs",
1220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1221
+ className: "olas-devtools-clear-text",
1222
+ children: "Clear"
1223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1224
+ className: "olas-devtools-clear-icon",
1225
+ "aria-hidden": "true",
1226
+ children: "✕"
1227
+ })]
1228
+ })
1229
+ ]
1230
+ }),
1231
+ (tab === "cache" || tab === "inspector" || tab === "mutations" || tab === "fields") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1232
+ className: "olas-devtools-filter",
1233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1234
+ type: "search",
1235
+ value: filter,
1236
+ placeholder: `Filter ${tab}…`,
1237
+ onChange: (e) => setFilter(e.target.value)
1238
+ }), filter !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1239
+ type: "button",
1240
+ onClick: () => setFilter(""),
1241
+ "aria-label": "Clear filter",
1242
+ children: "✕"
1243
+ })]
1244
+ }),
1245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1246
+ className: "olas-devtools-body",
1247
+ role: "tabpanel",
1248
+ children: [
1249
+ tab === "tree" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeView, {
1250
+ tree,
1251
+ mutations: liveMutations
1252
+ }),
1253
+ tab === "cache" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CacheView, {
1254
+ entries: cache,
1255
+ filter
1256
+ }),
1257
+ tab === "inspector" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorView, {
1258
+ entries: cacheEntries,
1259
+ filter
1260
+ }),
1261
+ tab === "mutations" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MutationsView, {
1262
+ entries: mutations,
1263
+ filter
1264
+ }),
1265
+ tab === "fields" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsView, {
1266
+ entries: fields,
1267
+ filter
1268
+ })
1269
+ ]
1270
+ })
1271
+ ]
1272
+ });
1273
+ }
1274
+ function Tab(props) {
1275
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1276
+ role: "tab",
1277
+ type: "button",
1278
+ "aria-selected": props.current === props.name,
1279
+ title: props.label,
1280
+ className: "olas-devtools-tab",
1281
+ onClick: () => props.setTab(props.name),
1282
+ children: [
1283
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1284
+ className: "olas-devtools-tab-label-full",
1285
+ children: props.label
1286
+ }),
1287
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1288
+ className: "olas-devtools-tab-label-short",
1289
+ "aria-hidden": "true",
1290
+ children: props.short
1291
+ }),
1292
+ props.count > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1293
+ className: "olas-devtools-tab-count",
1294
+ "aria-hidden": "true",
1295
+ children: props.count
1296
+ })
1297
+ ]
1298
+ });
1299
+ }
1300
+ function countLiveControllers(node) {
1301
+ let total = node.state !== "disposed" ? 1 : 0;
1302
+ for (const c of node.children) total += countLiveControllers(c);
1303
+ return Math.max(total - 1, 0);
1304
+ }
1305
+ function TreeView({ tree, mutations }) {
1306
+ if (tree.children.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1307
+ title: "No controllers yet",
1308
+ hint: "The root hasn't constructed any controllers."
1309
+ });
1310
+ const pending = (0, react.useMemo)(() => rollupPending(mutations), [mutations]);
1311
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1312
+ className: "olas-devtools-tree",
1313
+ children: tree.children.map((child) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNode, {
1314
+ node: child,
1315
+ pending
1316
+ }, child.path.join("/")))
1317
+ });
1318
+ }
1319
+ function rollupPending(entries) {
1320
+ const inFlight = /* @__PURE__ */ new Map();
1321
+ const out = /* @__PURE__ */ new Map();
1322
+ for (const e of entries) {
1323
+ const key = `${e.path.join(">")}#${e.name ?? ""}`;
1324
+ const pathKey = e.path.join(">");
1325
+ if (e.kind === "run") {
1326
+ inFlight.set(key, (inFlight.get(key) ?? 0) + 1);
1327
+ out.set(pathKey, (out.get(pathKey) ?? 0) + 1);
1328
+ } else if (e.kind === "success" || e.kind === "error") {
1329
+ const n = inFlight.get(key) ?? 0;
1330
+ if (n > 0) inFlight.set(key, n - 1);
1331
+ const p = out.get(pathKey) ?? 0;
1332
+ if (p > 0) out.set(pathKey, p - 1);
1333
+ }
1334
+ }
1335
+ return out;
1336
+ }
1337
+ function TreeNode({ node, pending }) {
1338
+ const name = node.path[node.path.length - 1] ?? "?";
1339
+ const stateClass = node.state === "suspended" ? "olas-devtools-tree-state-suspended" : node.state === "disposed" ? "olas-devtools-tree-state-disposed" : "olas-devtools-tree-state-active";
1340
+ const pendingCount = pending.get(node.path.join(">")) ?? 0;
1341
+ const propsPreview = (0, react.useMemo)(() => summarizeProps(node.props), [node.props]);
1342
+ const [propsOpen, setPropsOpen] = (0, react.useState)(false);
1343
+ const canExpandProps = node.props !== void 0 && node.props !== null;
1344
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1345
+ className: "olas-devtools-tree-node",
1346
+ children: [
1347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1348
+ className: "olas-devtools-tree-row",
1349
+ children: [
1350
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1351
+ className: "olas-devtools-tree-name",
1352
+ children: name
1353
+ }),
1354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1355
+ className: stateClass,
1356
+ children: node.state
1357
+ }),
1358
+ pendingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1359
+ className: "olas-devtools-tree-pending",
1360
+ title: "pending mutations on this controller",
1361
+ children: [pendingCount, " pending"]
1362
+ }),
1363
+ canExpandProps && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1364
+ type: "button",
1365
+ className: "olas-devtools-tree-props-toggle",
1366
+ "aria-expanded": propsOpen,
1367
+ onClick: () => setPropsOpen((v) => !v),
1368
+ title: propsOpen ? "Hide props" : "Show full props",
1369
+ children: propsPreview
1370
+ })
1371
+ ]
1372
+ }),
1373
+ propsOpen && canExpandProps && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1374
+ className: "olas-devtools-tree-props",
1375
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonView, { value: node.props })
1376
+ }),
1377
+ node.children.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1378
+ className: "olas-devtools-tree-children",
1379
+ children: node.children.map((child) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNode, {
1380
+ node: child,
1381
+ pending
1382
+ }, child.path.join("/")))
1383
+ })
1384
+ ]
1385
+ });
1386
+ }
1387
+ /** Build a one-line props summary for the tree row. */
1388
+ function summarizeProps(props) {
1389
+ if (props === null || props === void 0) return "";
1390
+ if (typeof props === "string") return `"${truncate(props, 24)}"`;
1391
+ if (typeof props === "number" || typeof props === "boolean") return String(props);
1392
+ if (Array.isArray(props)) return `[${props.length}]`;
1393
+ if (typeof props === "object") {
1394
+ const keys = Object.keys(props);
1395
+ if (keys.length === 0) return "{}";
1396
+ return `{ ${keys.slice(0, 2).map((k) => {
1397
+ const v = props[k];
1398
+ return `${k}: ${shortValue(v)}`;
1399
+ }).join(", ")}${keys.length > 2 ? `, +${keys.length - 2}` : ""} }`;
1400
+ }
1401
+ return String(props);
1402
+ }
1403
+ function shortValue(v) {
1404
+ if (v === null) return "null";
1405
+ if (v === void 0) return "undefined";
1406
+ if (typeof v === "string") return `"${truncate(v, 16)}"`;
1407
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
1408
+ if (Array.isArray(v)) return `[${v.length}]`;
1409
+ if (typeof v === "object") return `{${Object.keys(v).length}}`;
1410
+ return String(v);
1411
+ }
1412
+ function truncate(s, max) {
1413
+ return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
1414
+ }
1415
+ function InspectorView({ entries, filter }) {
1416
+ const filtered = useFiltered(entries, filter, inspectorHaystack);
1417
+ if (entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1418
+ title: "No cache entries",
1419
+ hint: "Subscribe to a query somewhere in the tree to see its data."
1420
+ });
1421
+ if (filtered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1422
+ title: "No matches",
1423
+ hint: `Nothing matches “${filter}”.`
1424
+ });
1425
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1426
+ className: "olas-devtools-list",
1427
+ children: filtered.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorRow, { entry }, entry.key.join("|")))
1428
+ });
1429
+ }
1430
+ function inspectorHaystack(e) {
1431
+ return [
1432
+ ...e.key.map(String),
1433
+ e.status,
1434
+ safeStringify(e.data)
1435
+ ].join(" ");
1436
+ }
1437
+ function InspectorRow({ entry }) {
1438
+ const kindClass = entry.status === "error" ? "olas-devtools-kind-error" : entry.status === "success" ? "olas-devtools-kind-success" : entry.status === "pending" ? "olas-devtools-kind-warn" : "";
1439
+ const ageMs = entry.lastUpdatedAt != null ? Date.now() - entry.lastUpdatedAt : null;
1440
+ const tags = [];
1441
+ if (entry.isStale) tags.push("stale");
1442
+ if (entry.isFetching) tags.push("fetching");
1443
+ if (entry.hasPendingMutations) tags.push("optimistic");
1444
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1445
+ kind: entry.status,
1446
+ kindClass,
1447
+ target: formatPath(entry.key),
1448
+ t: entry.lastUpdatedAt ?? Date.now(),
1449
+ payload: entry.error ?? entry.data,
1450
+ suffix: [ageMs != null ? `${formatAge(ageMs)} ago` : "—", ...tags].join(" · ")
1451
+ });
1452
+ }
1453
+ function safeStringify(v) {
1454
+ try {
1455
+ return JSON.stringify(v) ?? "";
1456
+ } catch {
1457
+ return String(v);
1458
+ }
1459
+ }
1460
+ function formatAge(ms) {
1461
+ if (ms < 1e3) return `${ms}ms`;
1462
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
1463
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
1464
+ return `${Math.round(ms / 36e5)}h`;
1465
+ }
1466
+ function readUrlHash(key, defaultTab) {
1467
+ const empty = {
1468
+ tree: "",
1469
+ cache: "",
1470
+ inspector: "",
1471
+ mutations: "",
1472
+ fields: ""
1473
+ };
1474
+ if (key === void 0) return {
1475
+ tab: defaultTab,
1476
+ filters: empty
1477
+ };
1478
+ if (typeof window === "undefined") return {
1479
+ tab: defaultTab,
1480
+ filters: empty
1481
+ };
1482
+ try {
1483
+ const raw = new URLSearchParams(window.location.hash.replace(/^#/, "")).get(key);
1484
+ if (raw === null) return {
1485
+ tab: defaultTab,
1486
+ filters: empty
1487
+ };
1488
+ const parsed = JSON.parse(decodeURIComponent(raw));
1489
+ return {
1490
+ tab: parsed.tab ?? defaultTab,
1491
+ filters: {
1492
+ ...empty,
1493
+ ...parsed.filters ?? {}
1494
+ }
1495
+ };
1496
+ } catch {
1497
+ return {
1498
+ tab: defaultTab,
1499
+ filters: empty
1500
+ };
1501
+ }
1502
+ }
1503
+ function writeUrlHash(key, state) {
1504
+ if (typeof window === "undefined") return;
1505
+ const params = new URLSearchParams(window.location.hash.replace(/^#/, ""));
1506
+ params.set(key, encodeURIComponent(JSON.stringify(state)));
1507
+ const next = `#${params.toString()}`;
1508
+ if (next !== window.location.hash) window.history.replaceState(null, "", next);
1509
+ }
1510
+ function CacheView({ entries, filter }) {
1511
+ const filtered = useFiltered(entries, filter, cacheHaystack);
1512
+ if (entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1513
+ title: "No cache events yet",
1514
+ hint: "Trigger a query subscription to see fetches here."
1515
+ });
1516
+ if (filtered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1517
+ title: "No matches",
1518
+ hint: `Nothing matches “${filter}”.`
1519
+ });
1520
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1521
+ className: "olas-devtools-list",
1522
+ children: [...filtered].reverse().map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CacheRow, { entry }, entry.id))
1523
+ });
1524
+ }
1525
+ function cacheHaystack(e) {
1526
+ const parts = [e.kind, ...e.queryKey.map((p) => String(p))];
1527
+ if (e.kind === "fetch-error") parts.push(safeStringify(e.error));
1528
+ if (e.kind === "subscribed") parts.push(...e.subscriberPath);
1529
+ return parts.join(" ");
1530
+ }
1531
+ function CacheRow({ entry }) {
1532
+ const kindClass = entry.kind === "fetch-error" ? "olas-devtools-kind-error" : entry.kind === "fetch-success" ? "olas-devtools-kind-success" : entry.kind === "invalidated" || entry.kind === "gc" ? "olas-devtools-kind-warn" : "";
1533
+ let inline = null;
1534
+ let payload;
1535
+ let suffix = null;
1536
+ if (entry.kind === "fetch-success") suffix = `${entry.durationMs}ms`;
1537
+ else if (entry.kind === "fetch-error") {
1538
+ suffix = `${entry.durationMs}ms`;
1539
+ payload = entry.error;
1540
+ } else if (entry.kind === "subscribed") inline = `from ${formatPath(entry.subscriberPath)}`;
1541
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1542
+ kind: entry.kind,
1543
+ kindClass,
1544
+ target: formatPath(entry.queryKey),
1545
+ t: entry.t,
1546
+ inline,
1547
+ payload,
1548
+ suffix
1549
+ });
1550
+ }
1551
+ function MutationsView({ entries, filter }) {
1552
+ const filtered = useFiltered(entries, filter, mutationHaystack);
1553
+ if (entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1554
+ title: "No mutations yet",
1555
+ hint: "Trigger a mutation to see the lifecycle here."
1556
+ });
1557
+ if (filtered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1558
+ title: "No matches",
1559
+ hint: `Nothing matches “${filter}”.`
1560
+ });
1561
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1562
+ className: "olas-devtools-list",
1563
+ children: [...filtered].reverse().map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MutationRow, { entry }, entry.id))
1564
+ });
1565
+ }
1566
+ function mutationHaystack(e) {
1567
+ const parts = [
1568
+ e.kind,
1569
+ ...e.path,
1570
+ e.name ?? ""
1571
+ ];
1572
+ if (e.kind === "run") parts.push(safeStringify(e.vars));
1573
+ if (e.kind === "success") parts.push(safeStringify(e.result));
1574
+ if (e.kind === "error") parts.push(safeStringify(e.error));
1575
+ return parts.join(" ");
1576
+ }
1577
+ function MutationRow({ entry }) {
1578
+ const kindClass = entry.kind === "error" ? "olas-devtools-kind-error" : entry.kind === "rollback" ? "olas-devtools-kind-rollback" : entry.kind === "success" ? "olas-devtools-kind-success" : "";
1579
+ const target = entry.name ? `${entry.name} · ${formatPath(entry.path)}` : formatPath(entry.path);
1580
+ let payload;
1581
+ let suffix = null;
1582
+ if (entry.kind === "run") payload = entry.vars;
1583
+ else if (entry.kind === "success") {
1584
+ payload = entry.result;
1585
+ if (entry.durationMs !== void 0) suffix = `${entry.durationMs}ms`;
1586
+ } else if (entry.kind === "error") {
1587
+ payload = entry.error;
1588
+ if (entry.durationMs !== void 0) suffix = `${entry.durationMs}ms`;
1589
+ }
1590
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1591
+ kind: entry.kind,
1592
+ kindClass,
1593
+ target,
1594
+ t: entry.t,
1595
+ payload,
1596
+ suffix
1597
+ });
1598
+ }
1599
+ function FieldsView({ entries, filter }) {
1600
+ const filtered = useFiltered(entries, filter, fieldHaystack);
1601
+ if (entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1602
+ title: "No field validations yet",
1603
+ hint: "Type into a form bound via ctx.form(...) or ctx.field(...) — each pass lands here."
1604
+ });
1605
+ if (filtered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
1606
+ title: "No matches",
1607
+ hint: `Nothing matches “${filter}”.`
1608
+ });
1609
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1610
+ className: "olas-devtools-list",
1611
+ children: [...filtered].reverse().map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, { entry }, entry.id))
1612
+ });
1613
+ }
1614
+ function fieldHaystack(e) {
1615
+ return [
1616
+ e.field,
1617
+ ...e.path,
1618
+ e.valid ? "valid" : "invalid",
1619
+ ...e.errors
1620
+ ].join(" ");
1621
+ }
1622
+ function FieldRow({ entry }) {
1623
+ const kindClass = entry.valid ? "olas-devtools-kind-success" : "olas-devtools-kind-error";
1624
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1625
+ kind: entry.valid ? "valid" : "invalid",
1626
+ kindClass,
1627
+ target: `${formatPath(entry.path)} · ${entry.field}`,
1628
+ t: entry.t,
1629
+ inline: entry.errors.length > 0 ? entry.errors.join(" · ") : null
1630
+ });
1631
+ }
1632
+ function Row(props) {
1633
+ const { kind, kindClass, target, t, inline, payload, suffix } = props;
1634
+ const hasPayload = payload !== void 0;
1635
+ const [expanded, setExpanded] = (0, react.useState)(false);
1636
+ const togglable = hasPayload;
1637
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1638
+ className: togglable ? "olas-devtools-row-clickable" : "",
1639
+ children: [
1640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1641
+ className: "olas-devtools-row-top",
1642
+ onClick: togglable ? () => setExpanded((v) => !v) : void 0,
1643
+ children: [
1644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1645
+ className: `olas-devtools-kind ${kindClass}`,
1646
+ children: kind
1647
+ }),
1648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1649
+ className: "olas-devtools-target",
1650
+ children: target
1651
+ }),
1652
+ suffix !== void 0 && suffix !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1653
+ className: "olas-devtools-duration",
1654
+ children: suffix
1655
+ }),
1656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1657
+ className: "olas-devtools-time",
1658
+ children: formatTime(t)
1659
+ }),
1660
+ togglable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1661
+ "aria-hidden": "true",
1662
+ className: `olas-devtools-chevron ${expanded ? "olas-devtools-chevron-open" : ""}`,
1663
+ children: "›"
1664
+ })
1665
+ ]
1666
+ }),
1667
+ inline != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1668
+ className: "olas-devtools-payload olas-devtools-payload-inline",
1669
+ children: inline
1670
+ }),
1671
+ hasPayload && expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1672
+ className: "olas-devtools-payload olas-devtools-payload-json",
1673
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonView, { value: payload })
1674
+ })
1675
+ ]
1676
+ });
1677
+ }
1678
+ function useFiltered(items, filter, haystack) {
1679
+ return (0, react.useMemo)(() => {
1680
+ if (filter.trim() === "") return [...items];
1681
+ const q = filter.toLowerCase();
1682
+ return items.filter((item) => haystack(item).toLowerCase().includes(q));
1683
+ }, [
1684
+ items,
1685
+ filter,
1686
+ haystack
1687
+ ]);
1688
+ }
1689
+ function Empty({ title, hint }) {
1690
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1691
+ className: "olas-devtools-empty",
1692
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1693
+ className: "olas-devtools-empty-title",
1694
+ children: title
1695
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1696
+ className: "olas-devtools-empty-hint",
1697
+ children: hint
1698
+ })]
1699
+ });
1700
+ }
1701
+ //#endregion
1702
+ //#region src/DevtoolsLauncher.tsx
1703
+ const MIN_W = 360;
1704
+ const MIN_H = 280;
1705
+ const HEADER_H = 30;
1706
+ const DEFAULT_W = 520;
1707
+ const DEFAULT_H = 520;
1708
+ const MARGIN = 16;
1709
+ function DevtoolsLauncher(props) {
1710
+ const storageKey = props.storageKey ?? "olas-devtools-window";
1711
+ const [state, setState] = (0, react.useState)(() => loadState(storageKey, props.initial));
1712
+ (0, react.useEffect)(() => {
1713
+ if (typeof localStorage === "undefined") return;
1714
+ try {
1715
+ localStorage.setItem(storageKey, JSON.stringify(state));
1716
+ } catch {}
1717
+ }, [state, storageKey]);
1718
+ (0, react.useEffect)(() => {
1719
+ const onResize = () => setState((s) => clampToViewport(s));
1720
+ if (typeof window === "undefined") return;
1721
+ window.addEventListener("resize", onResize);
1722
+ return () => window.removeEventListener("resize", onResize);
1723
+ }, []);
1724
+ const setOpen = (0, react.useCallback)((open) => setState((s) => ({
1725
+ ...s,
1726
+ open
1727
+ })), []);
1728
+ const setMin = (0, react.useCallback)((minimized) => setState((s) => ({
1729
+ ...s,
1730
+ minimized
1731
+ })), []);
1732
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: DEVTOOLS_CSS }),
1734
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LauncherButton, {
1735
+ open: state.open,
1736
+ onClick: () => setOpen(!state.open)
1737
+ }),
1738
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FloatingWindow, {
1739
+ state,
1740
+ setState,
1741
+ onClose: () => setOpen(false),
1742
+ onMinimize: () => setMin(!state.minimized),
1743
+ children: !state.minimized && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1744
+ className: "olas-devtools-floating-body",
1745
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevtoolsPanel, {
1746
+ root: props.root,
1747
+ defaultTab: props.defaultTab,
1748
+ maxEntries: props.maxEntries,
1749
+ urlHashKey: props.urlHashKey
1750
+ })
1751
+ })
1752
+ })
1753
+ ] });
1754
+ }
1755
+ function LauncherButton({ open, onClick }) {
1756
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1757
+ type: "button",
1758
+ "aria-label": open ? "Hide Olas devtools" : "Show Olas devtools",
1759
+ onClick,
1760
+ className: `olas-devtools-launcher ${open ? "olas-devtools-launcher-active" : ""}`,
1761
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1762
+ "aria-hidden": "true",
1763
+ className: "olas-devtools-launcher-dot"
1764
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1765
+ className: "olas-devtools-launcher-label",
1766
+ children: "Olas devtools"
1767
+ })]
1768
+ });
1769
+ }
1770
+ function FloatingWindow(props) {
1771
+ const { state, setState, onClose, onMinimize } = props;
1772
+ const dragState = (0, react.useRef)(null);
1773
+ const onPointerDown = (kind) => (e) => {
1774
+ e.preventDefault();
1775
+ e.target.setPointerCapture(e.pointerId);
1776
+ dragState.current = {
1777
+ kind,
1778
+ ox: e.clientX,
1779
+ oy: e.clientY,
1780
+ sx: state.x,
1781
+ sy: state.y,
1782
+ sw: state.w,
1783
+ sh: state.h
1784
+ };
1785
+ };
1786
+ const onPointerMove = (e) => {
1787
+ const d = dragState.current;
1788
+ if (d === null) return;
1789
+ const dx = e.clientX - d.ox;
1790
+ const dy = e.clientY - d.oy;
1791
+ if (d.kind === "move") setState((s) => clampToViewport({
1792
+ ...s,
1793
+ x: d.sx + dx,
1794
+ y: d.sy + dy
1795
+ }));
1796
+ else {
1797
+ const w = Math.max(MIN_W, d.sw + dx);
1798
+ const h = Math.max(MIN_H, d.sh + dy);
1799
+ setState((s) => clampToViewport({
1800
+ ...s,
1801
+ w,
1802
+ h
1803
+ }));
1804
+ }
1805
+ };
1806
+ const onPointerUp = () => {
1807
+ dragState.current = null;
1808
+ };
1809
+ const minimized = state.minimized;
1810
+ const height = minimized ? HEADER_H : state.h;
1811
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1812
+ className: "olas-devtools-floating",
1813
+ role: "dialog",
1814
+ "aria-label": "Olas devtools",
1815
+ style: {
1816
+ left: state.x,
1817
+ top: state.y,
1818
+ width: state.w,
1819
+ height
1820
+ },
1821
+ onPointerMove,
1822
+ onPointerUp,
1823
+ onPointerCancel: onPointerUp,
1824
+ children: [
1825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1826
+ className: "olas-devtools-floating-header",
1827
+ onPointerDown: onPointerDown("move"),
1828
+ children: [
1829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1830
+ className: "olas-devtools-floating-grip",
1831
+ "aria-hidden": "true",
1832
+ children: "⠿"
1833
+ }),
1834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1835
+ className: "olas-devtools-floating-title",
1836
+ children: "Olas devtools"
1837
+ }),
1838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1839
+ className: "olas-devtools-floating-actions",
1840
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1841
+ type: "button",
1842
+ "aria-label": minimized ? "Expand" : "Minimize",
1843
+ onClick: onMinimize,
1844
+ className: "olas-devtools-floating-action",
1845
+ children: minimized ? "▢" : "–"
1846
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1847
+ type: "button",
1848
+ "aria-label": "Close",
1849
+ onClick: onClose,
1850
+ className: "olas-devtools-floating-action",
1851
+ children: "×"
1852
+ })]
1853
+ })
1854
+ ]
1855
+ }),
1856
+ props.children,
1857
+ !minimized && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1858
+ className: "olas-devtools-floating-resize",
1859
+ onPointerDown: onPointerDown("resize"),
1860
+ "aria-label": "Resize",
1861
+ role: "separator"
1862
+ })
1863
+ ]
1864
+ });
1865
+ }
1866
+ function clampToViewport(s) {
1867
+ if (typeof window === "undefined") return s;
1868
+ const vw = window.innerWidth;
1869
+ const vh = window.innerHeight;
1870
+ const w = Math.min(s.w, vw - MARGIN * 2);
1871
+ const h = Math.min(s.h, vh - MARGIN * 2);
1872
+ const x = Math.max(MARGIN, Math.min(s.x, vw - w - MARGIN));
1873
+ const y = Math.max(MARGIN, Math.min(s.y, vh - HEADER_H - MARGIN));
1874
+ return {
1875
+ ...s,
1876
+ x,
1877
+ y,
1878
+ w,
1879
+ h
1880
+ };
1881
+ }
1882
+ function loadState(storageKey, initial) {
1883
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
1884
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
1885
+ const w = initial?.w ?? DEFAULT_W;
1886
+ const h = initial?.h ?? DEFAULT_H;
1887
+ const defaults = {
1888
+ x: initial?.x ?? Math.max(MARGIN, vw - w - MARGIN),
1889
+ y: initial?.y ?? Math.max(MARGIN, vh - h - MARGIN - 56),
1890
+ w,
1891
+ h,
1892
+ open: false,
1893
+ minimized: false
1894
+ };
1895
+ if (typeof localStorage === "undefined") return defaults;
1896
+ try {
1897
+ const raw = localStorage.getItem(storageKey);
1898
+ if (!raw) return defaults;
1899
+ const parsed = JSON.parse(raw);
1900
+ return clampToViewport({
1901
+ x: parsed.x ?? defaults.x,
1902
+ y: parsed.y ?? defaults.y,
1903
+ w: parsed.w ?? defaults.w,
1904
+ h: parsed.h ?? defaults.h,
1905
+ open: parsed.open ?? false,
1906
+ minimized: parsed.minimized ?? false
1907
+ });
1908
+ } catch {
1909
+ return defaults;
1910
+ }
1911
+ }
1912
+ //#endregion
1913
+ exports.DevtoolsLauncher = DevtoolsLauncher;
1914
+ exports.DevtoolsPanel = DevtoolsPanel;
1915
+ exports.DevtoolsStore = DevtoolsStore;
1916
+ exports.formatPath = formatPath;
1917
+ exports.formatPayload = formatPayload;
1918
+ exports.formatTime = formatTime;
1919
+ exports.insertNode = insertNode;
1920
+ exports.setNodeState = setNodeState;
1921
+
1922
+ //# sourceMappingURL=index.cjs.map