@fluixi/devtools 0.2.0-alpha.3 → 0.2.0-alpha.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.
Files changed (64) hide show
  1. package/dist/callsite.cjs +72 -0
  2. package/dist/callsite.mjs +51 -0
  3. package/dist/change-view.cjs +134 -0
  4. package/dist/change-view.mjs +111 -0
  5. package/dist/copy.cjs +63 -0
  6. package/dist/copy.mjs +42 -0
  7. package/dist/describe.cjs +55 -0
  8. package/dist/describe.mjs +34 -0
  9. package/dist/detail-panel.cjs +307 -0
  10. package/dist/detail-panel.mjs +284 -0
  11. package/dist/diff.cjs +225 -0
  12. package/dist/diff.mjs +202 -0
  13. package/dist/dom.cjs +204 -0
  14. package/dist/dom.mjs +183 -0
  15. package/dist/export.cjs +221 -0
  16. package/dist/export.mjs +209 -0
  17. package/dist/float.cjs +232 -0
  18. package/dist/float.mjs +211 -0
  19. package/dist/format.cjs +175 -0
  20. package/dist/format.mjs +154 -0
  21. package/dist/graph-view.cjs +893 -0
  22. package/dist/graph-view.mjs +870 -0
  23. package/dist/highlight.cjs +140 -0
  24. package/dist/highlight.mjs +119 -0
  25. package/dist/history.cjs +95 -0
  26. package/dist/history.mjs +74 -0
  27. package/dist/hook.cjs +1562 -0
  28. package/dist/hook.mjs +1553 -0
  29. package/dist/index.cjs +4552 -0
  30. package/dist/index.mjs +4544 -0
  31. package/dist/inspector.cjs +688 -0
  32. package/dist/inspector.mjs +676 -0
  33. package/dist/instrument.cjs +497 -0
  34. package/dist/instrument.mjs +487 -0
  35. package/dist/kind-filter.cjs +172 -0
  36. package/dist/kind-filter.mjs +151 -0
  37. package/dist/menu.cjs +141 -0
  38. package/dist/menu.mjs +120 -0
  39. package/dist/observe.cjs +1352 -0
  40. package/dist/observe.mjs +1345 -0
  41. package/dist/palette.cjs +52 -0
  42. package/dist/palette.mjs +31 -0
  43. package/dist/path.cjs +45 -0
  44. package/dist/path.mjs +24 -0
  45. package/dist/plugin.cjs +87 -0
  46. package/dist/plugin.mjs +66 -0
  47. package/dist/plugins/context.cjs +98 -0
  48. package/dist/plugins/context.mjs +75 -0
  49. package/dist/plugins/directives.cjs +98 -0
  50. package/dist/plugins/directives.mjs +75 -0
  51. package/dist/plugins/index.cjs +162 -0
  52. package/dist/plugins/index.mjs +140 -0
  53. package/dist/source.cjs +111 -0
  54. package/dist/source.mjs +90 -0
  55. package/dist/timeline.cjs +350 -0
  56. package/dist/timeline.mjs +327 -0
  57. package/dist/tree.cjs +574 -0
  58. package/dist/tree.mjs +551 -0
  59. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  60. package/dist/value-pane.cjs +223 -0
  61. package/dist/value-pane.mjs +200 -0
  62. package/dist/wire.cjs +109 -0
  63. package/dist/wire.mjs +89 -0
  64. package/package.json +13 -6
@@ -0,0 +1,676 @@
1
+ // src/instrument.ts
2
+ import {
3
+ createSignal,
4
+ createMemo,
5
+ createEffect,
6
+ createResource,
7
+ createRoot,
8
+ untrack,
9
+ onCleanup,
10
+ getOwner,
11
+ runWithOwner,
12
+ Signal
13
+ } from "@fluixi/reactive/signal";
14
+ import { createStore } from "@fluixi/reactive/store";
15
+
16
+ // src/source.ts
17
+ var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
18
+ var host = globalThis;
19
+ var shared = host[MARKS] ?? {
20
+ pending: null,
21
+ expiring: false,
22
+ components: /* @__PURE__ */ new Map()
23
+ };
24
+ host[MARKS] = shared;
25
+
26
+ // src/format.ts
27
+ var MAX_DEPTH = 3;
28
+ var MAX_ENTRIES = 8;
29
+ function domName(v) {
30
+ if (typeof v?.nodeType !== "number" || typeof v.nodeName !== "string") return null;
31
+ return v.tagName ? `<${v.tagName.toLowerCase()}>` : `#${v.nodeName.toLowerCase()}`;
32
+ }
33
+ function fnName(v) {
34
+ return v.name ? `ƒ ${v.name}()` : "ƒ";
35
+ }
36
+ function className(v) {
37
+ const name = v.constructor?.name;
38
+ return name && name !== "Object" ? `${name} ` : "";
39
+ }
40
+ function primitive(v) {
41
+ if (v === null) return "null";
42
+ switch (typeof v) {
43
+ case "undefined":
44
+ return "undefined";
45
+ case "string":
46
+ return JSON.stringify(v);
47
+ case "number":
48
+ case "boolean":
49
+ return String(v);
50
+ case "bigint":
51
+ return `${v}n`;
52
+ case "symbol":
53
+ return v.toString();
54
+ case "function":
55
+ return fnName(v);
56
+ default:
57
+ return null;
58
+ }
59
+ }
60
+ function walk(value, depth, seen) {
61
+ const simple = primitive(value);
62
+ if (simple !== null) return simple;
63
+ const object = value;
64
+ if (seen.has(object)) return "[circular]";
65
+ const dom = domName(object);
66
+ if (dom) return dom;
67
+ if (object instanceof Date) return Number.isNaN(object.getTime()) ? "Invalid Date" : object.toISOString();
68
+ if (object instanceof RegExp) return String(object);
69
+ if (object instanceof Error) return `${object.name}: ${object.message}`;
70
+ if (object instanceof Promise) return "Promise";
71
+ if (ArrayBuffer.isView(object)) {
72
+ return `${className(object).trim()}(${object.length ?? ""})`;
73
+ }
74
+ if (depth >= MAX_DEPTH) return Array.isArray(object) ? "[…]" : "{…}";
75
+ seen.add(object);
76
+ try {
77
+ if (Array.isArray(object)) {
78
+ const shown2 = object.slice(0, MAX_ENTRIES).map((item) => walk(item, depth + 1, seen));
79
+ if (object.length > MAX_ENTRIES) shown2.push("…");
80
+ return `[${shown2.join(", ")}]`;
81
+ }
82
+ if (object instanceof Map) {
83
+ const shown2 = [];
84
+ for (const [k, v] of object) {
85
+ if (shown2.length === MAX_ENTRIES) {
86
+ shown2.push("…");
87
+ break;
88
+ }
89
+ shown2.push(`${walk(k, depth + 1, seen)} => ${walk(v, depth + 1, seen)}`);
90
+ }
91
+ return `Map(${object.size}) {${shown2.join(", ")}}`;
92
+ }
93
+ if (object instanceof Set) {
94
+ const shown2 = [];
95
+ for (const item of object) {
96
+ if (shown2.length === MAX_ENTRIES) {
97
+ shown2.push("…");
98
+ break;
99
+ }
100
+ shown2.push(walk(item, depth + 1, seen));
101
+ }
102
+ return `Set(${object.size}) {${shown2.join(", ")}}`;
103
+ }
104
+ const keys = Object.keys(object);
105
+ const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
106
+ let read;
107
+ try {
108
+ read = walk(object[key], depth + 1, seen);
109
+ } catch {
110
+ read = "[unreadable]";
111
+ }
112
+ return `${key}: ${read}`;
113
+ });
114
+ if (keys.length > MAX_ENTRIES) shown.push("…");
115
+ return `${className(object)}{${shown.join(", ")}}`;
116
+ } finally {
117
+ seen.delete(object);
118
+ }
119
+ }
120
+ function formatValue(value, limit = 240) {
121
+ let text;
122
+ try {
123
+ text = walk(value, 0, /* @__PURE__ */ new Set());
124
+ } catch {
125
+ text = "[unreadable]";
126
+ }
127
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
128
+ }
129
+ function indentValue(text, pad = " ") {
130
+ const out = [];
131
+ let depth = 0;
132
+ let quote = null;
133
+ let line = "";
134
+ const flush = () => {
135
+ if (line.trim()) out.push(pad.repeat(depth) + line.trim());
136
+ line = "";
137
+ };
138
+ for (let i = 0; i < text.length; i++) {
139
+ const c = text[i];
140
+ if (quote) {
141
+ line += c;
142
+ if (c === quote && text[i - 1] !== "\\") quote = null;
143
+ continue;
144
+ }
145
+ if (c === '"' || c === "'") {
146
+ quote = c;
147
+ line += c;
148
+ continue;
149
+ }
150
+ if (c === "{" || c === "[") {
151
+ line += c;
152
+ flush();
153
+ depth++;
154
+ continue;
155
+ }
156
+ if (c === "}" || c === "]") {
157
+ flush();
158
+ depth = Math.max(0, depth - 1);
159
+ line = c;
160
+ continue;
161
+ }
162
+ if (c === ",") {
163
+ line += c;
164
+ flush();
165
+ continue;
166
+ }
167
+ line += c;
168
+ }
169
+ flush();
170
+ return out.join("\n");
171
+ }
172
+
173
+ // src/instrument.ts
174
+ var CORE_STATE = { 0: "clean", 1: "check", 2: "dirty" };
175
+ function nodeVersion(n) {
176
+ return n.core && typeof n.core.version === "number" ? n.core.version : n.runs;
177
+ }
178
+ function nodeState(n) {
179
+ return n.core && typeof n.core.state === "number" ? CORE_STATE[n.core.state] : void 0;
180
+ }
181
+
182
+ // src/path.ts
183
+ var normalize = (file) => file.replace(/\\/g, "/");
184
+ var ROOTED = /^\/|^(Users|home|var|opt|private|tmp|mnt|srv)\//;
185
+ var WORKSPACE_DIRS = /* @__PURE__ */ new Set(["packages", "apps", "libs", "examples"]);
186
+ function shortenPath(file, root) {
187
+ const path = normalize(file);
188
+ if (root) {
189
+ const base = normalize(root).replace(/\/+$/, "");
190
+ if (base && path.startsWith(`${base}/`)) return path.slice(base.length + 1);
191
+ if (base.startsWith("/") && path.startsWith(`${base.slice(1)}/`)) return path.slice(base.length);
192
+ }
193
+ const installed = path.lastIndexOf("node_modules/");
194
+ if (installed >= 0) return path.slice(installed + "node_modules/".length);
195
+ if (ROOTED.test(path)) {
196
+ const segments = path.split("/");
197
+ for (let i = segments.length - 2; i > 0; i--) {
198
+ if (WORKSPACE_DIRS.has(segments[i])) return segments.slice(i).join("/");
199
+ }
200
+ }
201
+ return path;
202
+ }
203
+
204
+ // src/plugin.ts
205
+ var plugins = [];
206
+ function pluginRows(node, graph) {
207
+ const out = [];
208
+ for (const plugin of plugins) {
209
+ if (!plugin.rows) continue;
210
+ try {
211
+ out.push(...plugin.rows(node, graph));
212
+ } catch {
213
+ }
214
+ }
215
+ return out;
216
+ }
217
+ function pluginElementRows(extra) {
218
+ if (!extra) return [];
219
+ const out = [];
220
+ for (const plugin of plugins) {
221
+ if (!plugin.elementRows) continue;
222
+ try {
223
+ out.push(...plugin.elementRows(extra));
224
+ } catch {
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+
230
+ // src/palette.ts
231
+ var KIND_COLOR = {
232
+ state: "#34d399",
233
+ memo: "#a78bfa",
234
+ effect: "#fbbf24",
235
+ store: "#38bdf8",
236
+ resource: "#fb7185",
237
+ component: "#f0abfc",
238
+ provider: "#fb923c",
239
+ control: "#22d3ee",
240
+ router: "#818cf8",
241
+ handler: "#f472b6",
242
+ context: "#a3e635",
243
+ directive: "#5eead4",
244
+ binding: "#5eead4",
245
+ setter: "#fb7185",
246
+ group: "#8a90a2",
247
+ element: "#8a90a2"
248
+ };
249
+ var kindVar = (kind) => `--fx-kind-${kind}`;
250
+ var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
251
+ var KINDS = Object.keys(KIND_COLOR);
252
+ function kindRules(selector, property) {
253
+ return KINDS.map((kind) => `${selector(kind)} { ${property}: ${kindColor(kind)}; }`).join("\n");
254
+ }
255
+
256
+ // src/float.ts
257
+ var MIN_W = 240;
258
+ var MIN_H = 160;
259
+ function draggable(handle, onMove, onStart) {
260
+ handle.addEventListener("pointerdown", (event) => {
261
+ if (event.button !== 0) return;
262
+ if (event.target.closest("button")) return;
263
+ event.preventDefault();
264
+ handle.setPointerCapture(event.pointerId);
265
+ const fromX = event.clientX;
266
+ const fromY = event.clientY;
267
+ onStart?.();
268
+ const move = (e) => onMove(e.clientX - fromX, e.clientY - fromY);
269
+ const stop = () => {
270
+ handle.removeEventListener("pointermove", move);
271
+ handle.removeEventListener("pointerup", stop);
272
+ handle.removeEventListener("pointercancel", stop);
273
+ handle.removeEventListener("lostpointercapture", stop);
274
+ if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId);
275
+ };
276
+ handle.addEventListener("pointermove", move);
277
+ handle.addEventListener("pointerup", stop);
278
+ handle.addEventListener("pointercancel", stop);
279
+ handle.addEventListener("lostpointercapture", stop);
280
+ });
281
+ }
282
+ var opened = 0;
283
+ function floatCard(doc, title, at2) {
284
+ const card = doc.createElement("div");
285
+ card.className = "fx-float";
286
+ const spot = at2 ?? {
287
+ x: 60 + opened % 4 * 28,
288
+ y: 60 + opened % 4 * 28,
289
+ width: 360,
290
+ height: 280
291
+ };
292
+ opened += 1;
293
+ const bar = doc.createElement("div");
294
+ bar.className = "fx-float-bar";
295
+ const name = doc.createElement("span");
296
+ name.textContent = title;
297
+ bar.append(name);
298
+ card.append(bar);
299
+ let x = spot.x;
300
+ let y = spot.y;
301
+ let w = spot.width;
302
+ let h = spot.height;
303
+ const apply = () => {
304
+ const view = doc.defaultView;
305
+ const vw = view?.innerWidth ?? w;
306
+ const vh = view?.innerHeight ?? h;
307
+ w = Math.min(Math.max(MIN_W, w), vw);
308
+ h = Math.min(Math.max(MIN_H, h), vh);
309
+ x = Math.min(Math.max(0, x), Math.max(0, vw - w));
310
+ y = Math.min(Math.max(0, y), Math.max(0, vh - h));
311
+ card.style.left = `${x}px`;
312
+ card.style.top = `${y}px`;
313
+ card.style.width = `${w}px`;
314
+ card.style.height = `${h}px`;
315
+ };
316
+ let from = { x, y, w, h };
317
+ const remember = () => {
318
+ from = { x, y, w, h };
319
+ };
320
+ for (const edge of ["n", "s", "e", "w", "ne", "nw", "se", "sw"]) {
321
+ const handle = doc.createElement("div");
322
+ handle.className = `fx-float-h fx-float-${edge}`;
323
+ card.append(handle);
324
+ draggable(
325
+ handle,
326
+ (dx, dy) => {
327
+ if (edge.includes("e")) w = from.w + dx;
328
+ if (edge.includes("s")) h = from.h + dy;
329
+ if (edge.includes("w")) {
330
+ w = Math.max(MIN_W, from.w - dx);
331
+ x = from.x + from.w - w;
332
+ }
333
+ if (edge.includes("n")) {
334
+ h = Math.max(MIN_H, from.h - dy);
335
+ y = from.y + from.h - h;
336
+ }
337
+ apply();
338
+ },
339
+ remember
340
+ );
341
+ }
342
+ draggable(
343
+ bar,
344
+ (dx, dy) => {
345
+ x = from.x + dx;
346
+ y = from.y + dy;
347
+ apply();
348
+ },
349
+ remember
350
+ );
351
+ apply();
352
+ return { card, bar, remove: () => card.remove() };
353
+ }
354
+
355
+ // src/value-pane.ts
356
+ var escape = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
357
+ function createValuePane(host2) {
358
+ const doc = host2.ownerDocument;
359
+ let built = null;
360
+ const close = () => {
361
+ built?.remove();
362
+ built = null;
363
+ };
364
+ const onKey = (event) => {
365
+ if (event.key === "Escape") close();
366
+ };
367
+ doc.addEventListener("keydown", onKey);
368
+ return {
369
+ open(title, text) {
370
+ close();
371
+ built = floatCard(doc, title, { x: 80, y: 80, width: 420, height: 320 });
372
+ const shut = doc.createElement("button");
373
+ shut.type = "button";
374
+ shut.className = "vp-close";
375
+ shut.textContent = "✕";
376
+ shut.title = "Close";
377
+ shut.addEventListener("click", close);
378
+ built.bar.append(shut);
379
+ const body = doc.createElement("pre");
380
+ body.className = "vp-body";
381
+ body.innerHTML = escape(indentValue(text));
382
+ built.card.append(body);
383
+ doc.body.appendChild(built.card);
384
+ },
385
+ close,
386
+ get isOpen() {
387
+ return built !== null;
388
+ },
389
+ destroy() {
390
+ doc.removeEventListener("keydown", onKey);
391
+ close();
392
+ }
393
+ };
394
+ }
395
+ var VALUE_PANE_CSS = `
396
+ .vp-close {
397
+ margin-left: auto; background: none; border: 0; cursor: pointer;
398
+ color: var(--fx-muted, #8a90a2); font-size: 12px; line-height: 1; padding: 2px 4px;
399
+ }
400
+ .vp-close:hover { color: var(--fx-label, #e7e9ee); }
401
+ .vp-body {
402
+ flex: 1; margin: 0; padding: 8px; overflow: auto; white-space: pre;
403
+ font-family: ui-monospace, monospace; font-size: 11.5px; line-height: 1.5;
404
+ color: var(--fx-label, #e7e9ee);
405
+ }
406
+ `;
407
+
408
+ // src/inspector.ts
409
+ var escape2 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
410
+ var clip = (text, n = 140) => text.length > n ? `${text.slice(0, n - 1)}…` : text;
411
+ var place = (text, source, live) => {
412
+ if (!source || !live || !source.file) return escape2(text);
413
+ return `<button type="button" class="ins-at" data-file="${escape2(source.file)}" data-line="${source.line ?? ""}" data-column="${source.column ?? ""}" title="Open ${escape2(source.file)}">${escape2(text)}</button>`;
414
+ };
415
+ var at = (source, root) => source ? `${source.file ? shortenPath(source.file, root) : ""}:${source.line ?? ""}:${source.column ?? ""}` : "";
416
+ var bothAt = (source, root) => {
417
+ if (!source) return "";
418
+ const out = source.compiled;
419
+ const same = out?.line === source.line && out?.column === source.column;
420
+ return out && !same ? `${at(source, root)} · out ${out.line ?? ""}:${out.column ?? ""}` : at(source, root);
421
+ };
422
+ function authoredPlace(node) {
423
+ for (const place2 of [node.source, node.declaredAt, node.usedAt]) {
424
+ if (place2 && !place2.external) return place2;
425
+ }
426
+ return node.declaredAt ?? node.source ?? node.usedAt;
427
+ }
428
+ function reaches(graph, id) {
429
+ const count = /* @__PURE__ */ new Map();
430
+ const writesHere = (nodes) => nodes.some((n) => n.writes?.includes(id) || writesHere(n.children));
431
+ for (const node of graph.values()) {
432
+ if (node.id === id || !node.dom?.length) continue;
433
+ if (writesHere(node.dom)) count.set(node.label, (count.get(node.label) ?? 0) + 1);
434
+ }
435
+ return [...count].map(([label, n]) => n > 1 ? `${label} ×${n}` : label);
436
+ }
437
+ function valueOf(node, limit) {
438
+ if (node.read) {
439
+ try {
440
+ return formatValue(node.read(), limit);
441
+ } catch {
442
+ }
443
+ }
444
+ return node.detail ?? node.value ?? "";
445
+ }
446
+ function createInspector(host2, options = {}) {
447
+ const empty = options.empty ?? "click a node to inspect";
448
+ const limit = options.limit ?? 600;
449
+ const reveals = typeof options.onReveal === "function";
450
+ const root = options.root;
451
+ const edits = typeof options.onEdit === "function";
452
+ let editing = null;
453
+ let field = null;
454
+ let internals = options.internals === true;
455
+ let pane = null;
456
+ const doc = host2.ownerDocument;
457
+ host2.addEventListener("click", (event) => {
458
+ const cell = event.target?.closest?.("[data-full]");
459
+ if (!cell) return;
460
+ pane ??= createValuePane(host2);
461
+ pane.open(cell.dataset.label || "value", cell.dataset.full || "");
462
+ });
463
+ const header = doc.createElement("div");
464
+ header.className = "ins-header";
465
+ const bar = doc.createElement("label");
466
+ bar.className = "ins-bar";
467
+ const box = doc.createElement("input");
468
+ box.type = "checkbox";
469
+ box.checked = internals;
470
+ bar.append(box, doc.createTextNode("framework"));
471
+ bar.title = "Also show where the framework created this, not only where it was written";
472
+ const body = doc.createElement("div");
473
+ body.className = "ins-body";
474
+ header.appendChild(bar);
475
+ host2.replaceChildren(header, body);
476
+ box.addEventListener("change", () => {
477
+ internals = box.checked;
478
+ signature = "";
479
+ });
480
+ if (edits) {
481
+ host2.addEventListener("click", (event) => {
482
+ const cell = event.target?.closest?.(".ins-edit");
483
+ if (!cell || editing !== null) return;
484
+ const id = Number(cell.dataset.id);
485
+ editing = id;
486
+ const input = host2.ownerDocument.createElement("input");
487
+ field = input;
488
+ input.className = "ins-input";
489
+ input.value = cell.dataset.raw ?? "";
490
+ cell.replaceWith(input);
491
+ input.focus();
492
+ input.select();
493
+ const done = (commit) => {
494
+ if (editing === null) return;
495
+ editing = null;
496
+ field = null;
497
+ signature = "";
498
+ if (commit) options.onEdit(id, input.value);
499
+ };
500
+ input.addEventListener("keydown", (e) => {
501
+ if (e.key === "Enter") done(true);
502
+ else if (e.key === "Escape") done(false);
503
+ });
504
+ input.addEventListener("blur", () => done(true));
505
+ });
506
+ }
507
+ if (reveals) {
508
+ host2.addEventListener("click", (event) => {
509
+ const button = event.target?.closest?.(".ins-at");
510
+ if (!button) return;
511
+ const line = Number(button.dataset.line);
512
+ const column = Number(button.dataset.column);
513
+ options.onReveal({
514
+ file: button.dataset.file,
515
+ ...Number.isFinite(line) && button.dataset.line ? { line } : {},
516
+ ...Number.isFinite(column) && button.dataset.column ? { column } : {}
517
+ });
518
+ });
519
+ }
520
+ let signature = "";
521
+ return {
522
+ show(graph, id) {
523
+ const node = id == null ? void 0 : graph.get(id);
524
+ if (!node) {
525
+ if (signature === "empty") return;
526
+ signature = "empty";
527
+ body.innerHTML = `<span class="ins-empty">${escape2(empty)}</span>`;
528
+ return;
529
+ }
530
+ const labelOf = (other) => graph.get(other)?.label ?? "?";
531
+ const reads = [...node.deps].map(labelOf);
532
+ const feeds = [...graph.values()].filter((n) => n.deps.has(node.id) && n.kind !== "binding").map((n) => n.label);
533
+ const writes = [...node.writes].map(labelOf);
534
+ const value = valueOf(node, limit);
535
+ const state = nodeState(node);
536
+ const synthetic = node.kind === "binding" || node.kind === "setter" || node.kind === "group";
537
+ const owner = node.createdIn !== void 0 ? graph.get(node.createdIn)?.label : void 0;
538
+ const reaching = reaches(graph, node.id);
539
+ const made = [...graph.values()].filter((n) => n.createdIn === node.id).map((n) => n.label);
540
+ const bound = node.targets?.length ? node.targets.map((t) => {
541
+ const where = bothAt(t.source, root);
542
+ const what = escape2(t.name ? `${t.name} of ${t.element}` : t.element);
543
+ return where ? `${what} (${place(where, t.source, reveals)})` : what;
544
+ }).join(", ") : node.bindings ? `${node.bindings} dom binding${node.bindings > 1 ? "s" : ""}` : "";
545
+ const relations = [
546
+ `<span>↤ reads ${reads.length ? `<b>${escape2(reads.join(", "))}</b>` : "<i>nothing</i>"}</span>`,
547
+ // `bound` already carries markup for the places a value reaches, so only the
548
+ // labels are escaped here.
549
+ `<span>↦ feeds ${feeds.length || bound ? `<b>${[...feeds.map(escape2), bound].filter(Boolean).join(", ")}</b>` : "<i>nothing yet</i>"}</span>`,
550
+ writes.length ? `<span>✎ writes <b>${escape2(writes.join(", "))}</b></span>` : "",
551
+ // Where the value is set from code the graph has no node for.
552
+ node.setAt?.length ? `<span>⌁ set at <b>${node.setAt.map((w) => place(bothAt(w, root), w, reveals)).join(", ")}</b></span>` : "",
553
+ owner ? `<span>◧ made in <b>${escape2(owner)}</b></span>` : "",
554
+ reaching.length ? `<span>◨ reaches <b>${escape2(reaching.join(", "))}</b></span>` : "",
555
+ ...pluginRows(node, graph).map((row) => {
556
+ const where = row.at && { ...row.at, file: row.at.file ?? node.source?.file };
557
+ const text = where?.file ? bothAt(where, root) : "";
558
+ const more = row.value !== void 0 && row.value.length > 140;
559
+ return `<span${more ? ` class="ins-more" data-full="${escape2(row.value)}" data-label="${escape2(row.label)}"` : ""} title="${escape2(row.value ?? row.label)}">⌘ ${escape2(row.label)}${row.value ? `=<b>${escape2(clip(row.value))}</b>` : ""}${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
560
+ }),
561
+ // What the tag was written with. A prop's position has no file of its own: it was
562
+ // written on the tag, so it belongs to whichever file that is.
563
+ ...(node.props ?? []).map((p) => {
564
+ const where = p.at && { ...p.at, file: p.at.file ?? node.source?.file };
565
+ const at2 = where?.file ? bothAt(where, root) : "";
566
+ return `<span>⇥ ${escape2(p.name)}=<b>${escape2(clip(p.value ?? "", 60))}</b>${at2 ? ` ${place(at2, where, reveals)}` : ""}</span>`;
567
+ }),
568
+ made.length ? `<span>▸ made <b>${escape2(made.join(", "))}</b></span>` : ""
569
+ ].filter(Boolean).join("");
570
+ const next = `${node.id}|${node.kind}|${value}|${node.runs}|${state ?? ""}|${relations}|${at(node.source, root)}|${at(node.usedAt, root)}|${bound}`;
571
+ if (editing !== null && field?.isConnected !== true) {
572
+ editing = null;
573
+ field = null;
574
+ }
575
+ if (editing !== null) return;
576
+ if (next === signature) return;
577
+ signature = next;
578
+ body.innerHTML = `<div class="ins-head"><span class="ins-kind n-${node.kind}">${node.kind}</span><b>${escape2(node.label)}</b>` + (synthetic ? '<span class="ins-meta">not a node</span></div>' : `<span class="ins-meta">v${nodeVersion(node)} · ${node.runs} runs${state ? ` · ${state}` : ""}</span></div>`) + (edits && node.kind === "state" ? `<div class="ins-val ins-edit" data-id="${node.id}" data-raw="${escape2(value)}" title="Click to set ${escape2(node.label)}">${escape2(clip(value))}</div>` : `<div class="ins-val${value.length > 140 ? " ins-more" : ""}"${value.length > 140 ? ` data-full="${escape2(value)}" data-label="${escape2(node.label)}"` : ""} title="${escape2(value)}">${escape2(clip(value))}</div>`) + `<div class="ins-rel">${relations}</div>` + // Only when the graph carries one. A snapshot from a build that was not marked has
579
+ // no locations at all, and an empty line there reads as missing data.
580
+ (node.source ? `<div class="ins-source">${node.source.external ? "📦" : "📍"} ${place(bothAt(node.source, root), node.source, reveals)}</div>` : "") + // A component is created where its tag is written; where it is declared is somewhere
581
+ // else, and only worth a line when it is.
582
+ (node.declaredAt && at(node.declaredAt, root) !== at(node.source, root) ? `<div class="ins-source">◧ declared ${place(at(node.declaredAt, root), node.declaredAt, reveals)}</div>` : "") + // A node a package made on the application's behalf: the package's line is where it
583
+ // was created, and this is the line that asked for it.
584
+ (node.usedAt ? `<div class="ins-source">↗ used at ${place(bothAt(node.usedAt, root), node.usedAt, reveals)}</div>` : "") + // Nothing said where it came from at all: the compiler did not mark this project and
585
+ // the stack had nothing to offer either. Saying so beats a card that just sits there.
586
+ (!node.source && !node.usedAt && !node.declaredAt ? '<div class="ins-source">— no source: build without sourceLocations</div>' : "");
587
+ },
588
+ showElement(graph, pick) {
589
+ if (!pick) {
590
+ if (signature === "empty") return;
591
+ signature = "empty";
592
+ body.innerHTML = `<span class="ins-empty">${escape2(empty)}</span>`;
593
+ return;
594
+ }
595
+ const owner = pick.owner === void 0 ? void 0 : graph.get(pick.owner);
596
+ const writers = (pick.writes ?? []).map((id) => graph.get(id)).filter((n) => n !== void 0);
597
+ const bindingAt = (node) => node.targets?.find((t) => t.element === pick.label || t.element.endsWith(` in ${pick.label}`))?.source;
598
+ const rows = writers.map((node) => {
599
+ const where = bindingAt(node);
600
+ const text = where ? bothAt(where, root) : "";
601
+ return `<span>⌁ <b>${escape2(node.label)}</b>${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
602
+ }).join("");
603
+ const relations = [
604
+ owner ? `<span>◧ rendered by <b>${escape2(owner.label)}</b></span>` : "<span>◧ rendered by <i>nothing in the graph</i></span>",
605
+ writers.length ? rows : "<span>⌁ <i>nothing writes here</i></span>",
606
+ ...pluginElementRows(pick.extra).map((row) => {
607
+ const where = row.at && { ...row.at, file: row.at.file ?? owner?.source?.file };
608
+ const text = where?.file ? bothAt(where, root) : "";
609
+ const more = row.value !== void 0 && row.value.length > 140;
610
+ return `<span${more ? ` class="ins-more" data-full="${escape2(row.value)}" data-label="${escape2(row.label)}"` : ""} title="${escape2(row.value ?? row.label)}">⌘ ${escape2(row.label)}${row.value ? `=<b>${escape2(clip(row.value))}</b>` : ""}${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
611
+ })
612
+ ].join("");
613
+ const source = pick.at ?? (owner && (owner.declaredAt ?? authoredPlace(owner)));
614
+ const madeIn = internals && owner?.source && owner.source !== source ? owner.source : void 0;
615
+ const next = `el|${pick.key}|${pick.label}|${relations}|${at(source, root)}|${at(madeIn, root)}`;
616
+ if (editing !== null && field?.isConnected !== true) {
617
+ editing = null;
618
+ field = null;
619
+ }
620
+ if (editing !== null) return;
621
+ if (next === signature) return;
622
+ signature = next;
623
+ body.innerHTML = `<div class="ins-head"><span class="ins-kind n-element">element</span><b>${escape2(pick.label)}</b><span class="ins-meta">not a node</span></div><div class="ins-rel">${relations}</div>` + // The tag's own line. An element has no position of its own in the graph — only the
624
+ // component that rendered it does — so this is as close as the source gets.
625
+ (source ? `<div class="ins-source">${source.external ? "📦" : "📍"} ${place(bothAt(source, root), source, reveals)}</div>` : "") + (madeIn ? `<div class="ins-source">📦 made in ${place(bothAt(madeIn, root), madeIn, reveals)}</div>` : "");
626
+ },
627
+ clear() {
628
+ signature = "";
629
+ body.innerHTML = `<span class="ins-empty">${escape2(empty)}</span>`;
630
+ }
631
+ };
632
+ }
633
+ var INSPECTOR_CSS = `
634
+ ${VALUE_PANE_CSS}
635
+ /* Anything cut to fit says so, and opens the whole of it. */
636
+ .ins-more { cursor: zoom-in; }
637
+ .ins-header{ display: flex; align-items: center; gap: 8px; padding: .3rem .5rem; }
638
+ .ins-body { padding: .5rem; }
639
+ .ins-more:hover { text-decoration: underline dotted; }
640
+ .ins-empty { color: var(--fx-muted, #8a90a2); font-size: 12px; }
641
+ .ins-bar {
642
+ display: flex; align-items: center; gap: 5px; margin-bottom: 6px;
643
+ font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
644
+ }
645
+ .ins-bar:hover { color: var(--fx-label, #e7e9ee); }
646
+ .ins-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
647
+ .ins-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
648
+ .ins-head b { font-size: 13px; color: var(--fx-label, #e7e9ee); }
649
+ .ins-kind { font-family: ui-monospace, monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: 1px; }
650
+ ${kindRules((k) => `.ins-kind.n-${k}`, "color")}
651
+ .ins-meta { margin-left: auto; color: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 10.5px; }
652
+ .ins-val { font-family: ui-monospace, monospace; font-size: 12px; color: var(--fx-label, #e7e9ee); word-break: break-word; }
653
+ .ins-rel { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; color: var(--fx-muted, #8a90a2); font-size: 11.5px; }
654
+ .ins-rel b { color: var(--fx-label, #e7e9ee); font-weight: 600; }
655
+ .ins-rel i { font-style: normal; opacity: .6; }
656
+ .ins-edit { cursor: text; border-bottom: 1px dashed var(--fx-line, #262a35); }
657
+ .ins-edit:hover { border-bottom-color: var(--fx-accent, #5eead4); }
658
+ .ins-input {
659
+ width: 100%; box-sizing: border-box; padding: 1px 4px;
660
+ font: inherit; font-family: ui-monospace, monospace;
661
+ color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
662
+ border: 1px solid var(--fx-accent, #5eead4); border-radius: 3px;
663
+ }
664
+ .ins-at {
665
+ font: inherit; color: inherit; background: none; border: 0; padding: 0;
666
+ cursor: pointer; text-decoration: underline; text-decoration-style: dotted;
667
+ text-underline-offset: 2px;
668
+ }
669
+ .ins-at:hover { color: var(--fx-accent, #5eead4); }
670
+ .ins-at:focus-visible { outline: 1px solid var(--fx-accent, #5eead4); outline-offset: 1px; }
671
+ .ins-source { margin-top: 6px; font-family: ui-monospace, monospace; font-size: 10.5px; color: var(--fx-muted, #8a90a2); }
672
+ `;
673
+ export {
674
+ INSPECTOR_CSS,
675
+ createInspector
676
+ };