@fluixi/devtools 0.2.0-alpha.2 → 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 (158) hide show
  1. package/README.md +80 -2
  2. package/dist/callsite.cjs +72 -0
  3. package/dist/callsite.d.ts +22 -0
  4. package/dist/callsite.d.ts.map +1 -0
  5. package/dist/callsite.js +82 -0
  6. package/dist/callsite.mjs +51 -0
  7. package/dist/change-view.cjs +134 -0
  8. package/dist/change-view.d.ts +10 -0
  9. package/dist/change-view.d.ts.map +1 -0
  10. package/dist/change-view.js +102 -0
  11. package/dist/change-view.mjs +111 -0
  12. package/dist/copy.cjs +63 -0
  13. package/dist/copy.d.ts +9 -0
  14. package/dist/copy.d.ts.map +1 -0
  15. package/dist/copy.js +52 -0
  16. package/dist/copy.mjs +42 -0
  17. package/dist/describe.cjs +55 -0
  18. package/dist/describe.d.ts +3 -0
  19. package/dist/describe.d.ts.map +1 -0
  20. package/dist/describe.js +47 -0
  21. package/dist/describe.mjs +34 -0
  22. package/dist/detail-panel.cjs +307 -0
  23. package/dist/detail-panel.d.ts +17 -0
  24. package/dist/detail-panel.d.ts.map +1 -0
  25. package/dist/detail-panel.js +217 -0
  26. package/dist/detail-panel.mjs +284 -0
  27. package/dist/diff.cjs +225 -0
  28. package/dist/diff.d.ts +31 -0
  29. package/dist/diff.d.ts.map +1 -0
  30. package/dist/diff.js +132 -0
  31. package/dist/diff.mjs +202 -0
  32. package/dist/dom.cjs +204 -0
  33. package/dist/dom.d.ts +81 -0
  34. package/dist/dom.d.ts.map +1 -0
  35. package/dist/dom.js +269 -0
  36. package/dist/dom.mjs +183 -0
  37. package/dist/export.cjs +221 -0
  38. package/dist/export.d.ts +55 -0
  39. package/dist/export.d.ts.map +1 -0
  40. package/dist/export.js +90 -0
  41. package/dist/export.mjs +209 -0
  42. package/dist/float.cjs +232 -0
  43. package/dist/float.d.ts +54 -0
  44. package/dist/float.d.ts.map +1 -0
  45. package/dist/float.js +256 -0
  46. package/dist/float.mjs +211 -0
  47. package/dist/format.cjs +175 -0
  48. package/dist/format.d.ts +16 -0
  49. package/dist/format.d.ts.map +1 -0
  50. package/dist/format.js +188 -0
  51. package/dist/format.mjs +154 -0
  52. package/dist/graph-view.cjs +893 -0
  53. package/dist/graph-view.d.ts +79 -2
  54. package/dist/graph-view.d.ts.map +1 -1
  55. package/dist/graph-view.js +866 -50
  56. package/dist/graph-view.mjs +870 -0
  57. package/dist/highlight.cjs +140 -0
  58. package/dist/highlight.d.ts +56 -0
  59. package/dist/highlight.d.ts.map +1 -0
  60. package/dist/highlight.js +154 -0
  61. package/dist/highlight.mjs +119 -0
  62. package/dist/history.cjs +95 -0
  63. package/dist/history.d.ts +59 -0
  64. package/dist/history.d.ts.map +1 -0
  65. package/dist/history.js +85 -0
  66. package/dist/history.mjs +74 -0
  67. package/dist/hook.cjs +1562 -0
  68. package/dist/hook.d.ts +60 -0
  69. package/dist/hook.d.ts.map +1 -0
  70. package/dist/hook.js +142 -0
  71. package/dist/hook.mjs +1553 -0
  72. package/dist/index.cjs +4552 -0
  73. package/dist/index.d.ts +44 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +25 -1
  76. package/dist/index.mjs +4544 -0
  77. package/dist/inspector.cjs +688 -0
  78. package/dist/inspector.d.ts +60 -0
  79. package/dist/inspector.d.ts.map +1 -0
  80. package/dist/inspector.js +415 -0
  81. package/dist/inspector.mjs +676 -0
  82. package/dist/instrument.cjs +497 -0
  83. package/dist/instrument.d.ts +147 -5
  84. package/dist/instrument.d.ts.map +1 -1
  85. package/dist/instrument.js +164 -44
  86. package/dist/instrument.mjs +487 -0
  87. package/dist/kind-filter.cjs +172 -0
  88. package/dist/kind-filter.d.ts +39 -0
  89. package/dist/kind-filter.d.ts.map +1 -0
  90. package/dist/kind-filter.js +151 -0
  91. package/dist/kind-filter.mjs +151 -0
  92. package/dist/menu.cjs +141 -0
  93. package/dist/menu.d.ts +37 -0
  94. package/dist/menu.d.ts.map +1 -0
  95. package/dist/menu.js +125 -0
  96. package/dist/menu.mjs +120 -0
  97. package/dist/observe.cjs +1352 -0
  98. package/dist/observe.d.ts +80 -0
  99. package/dist/observe.d.ts.map +1 -0
  100. package/dist/observe.js +893 -0
  101. package/dist/observe.mjs +1345 -0
  102. package/dist/palette.cjs +52 -0
  103. package/dist/palette.d.ts +11 -0
  104. package/dist/palette.d.ts.map +1 -0
  105. package/dist/palette.js +27 -0
  106. package/dist/palette.mjs +31 -0
  107. package/dist/path.cjs +45 -0
  108. package/dist/path.d.ts +19 -0
  109. package/dist/path.d.ts.map +1 -0
  110. package/dist/path.js +49 -0
  111. package/dist/path.mjs +24 -0
  112. package/dist/plugin.cjs +87 -0
  113. package/dist/plugin.d.ts +121 -0
  114. package/dist/plugin.d.ts.map +1 -0
  115. package/dist/plugin.js +74 -0
  116. package/dist/plugin.mjs +66 -0
  117. package/dist/plugins/context.cjs +98 -0
  118. package/dist/plugins/context.d.ts +10 -0
  119. package/dist/plugins/context.d.ts.map +1 -0
  120. package/dist/plugins/context.js +80 -0
  121. package/dist/plugins/context.mjs +75 -0
  122. package/dist/plugins/directives.cjs +98 -0
  123. package/dist/plugins/directives.d.ts +25 -0
  124. package/dist/plugins/directives.d.ts.map +1 -0
  125. package/dist/plugins/directives.js +78 -0
  126. package/dist/plugins/directives.mjs +75 -0
  127. package/dist/plugins/index.cjs +162 -0
  128. package/dist/plugins/index.d.ts +7 -0
  129. package/dist/plugins/index.d.ts.map +1 -0
  130. package/dist/plugins/index.js +9 -0
  131. package/dist/plugins/index.mjs +140 -0
  132. package/dist/source.cjs +111 -0
  133. package/dist/source.d.ts +72 -0
  134. package/dist/source.d.ts.map +1 -0
  135. package/dist/source.js +153 -0
  136. package/dist/source.mjs +90 -0
  137. package/dist/timeline.cjs +350 -0
  138. package/dist/timeline.d.ts +30 -0
  139. package/dist/timeline.d.ts.map +1 -0
  140. package/dist/timeline.js +271 -0
  141. package/dist/timeline.mjs +327 -0
  142. package/dist/tree.cjs +574 -0
  143. package/dist/tree.d.ts +128 -0
  144. package/dist/tree.d.ts.map +1 -0
  145. package/dist/tree.js +645 -0
  146. package/dist/tree.mjs +551 -0
  147. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  148. package/dist/value-pane.cjs +223 -0
  149. package/dist/value-pane.d.ts +10 -0
  150. package/dist/value-pane.d.ts.map +1 -0
  151. package/dist/value-pane.js +61 -0
  152. package/dist/value-pane.mjs +200 -0
  153. package/dist/wire.cjs +109 -0
  154. package/dist/wire.d.ts +100 -0
  155. package/dist/wire.d.ts.map +1 -0
  156. package/dist/wire.js +93 -0
  157. package/dist/wire.mjs +89 -0
  158. package/package.json +17 -5
@@ -0,0 +1,870 @@
1
+ // src/path.ts
2
+ var normalize = (file) => file.replace(/\\/g, "/");
3
+ var ROOTED = /^\/|^(Users|home|var|opt|private|tmp|mnt|srv)\//;
4
+ var WORKSPACE_DIRS = /* @__PURE__ */ new Set(["packages", "apps", "libs", "examples"]);
5
+ function shortenPath(file, root) {
6
+ const path = normalize(file);
7
+ if (root) {
8
+ const base = normalize(root).replace(/\/+$/, "");
9
+ if (base && path.startsWith(`${base}/`)) return path.slice(base.length + 1);
10
+ if (base.startsWith("/") && path.startsWith(`${base.slice(1)}/`)) return path.slice(base.length);
11
+ }
12
+ const installed = path.lastIndexOf("node_modules/");
13
+ if (installed >= 0) return path.slice(installed + "node_modules/".length);
14
+ if (ROOTED.test(path)) {
15
+ const segments = path.split("/");
16
+ for (let i = segments.length - 2; i > 0; i--) {
17
+ if (WORKSPACE_DIRS.has(segments[i])) return segments.slice(i).join("/");
18
+ }
19
+ }
20
+ return path;
21
+ }
22
+
23
+ // src/plugin.ts
24
+ var plugins = [];
25
+ function pluginBadge(node) {
26
+ for (const plugin of plugins) {
27
+ try {
28
+ const badge = plugin.badge?.(node);
29
+ if (badge) return badge;
30
+ } catch {
31
+ }
32
+ }
33
+ return void 0;
34
+ }
35
+
36
+ // src/palette.ts
37
+ var KIND_COLOR = {
38
+ state: "#34d399",
39
+ memo: "#a78bfa",
40
+ effect: "#fbbf24",
41
+ store: "#38bdf8",
42
+ resource: "#fb7185",
43
+ component: "#f0abfc",
44
+ provider: "#fb923c",
45
+ control: "#22d3ee",
46
+ router: "#818cf8",
47
+ handler: "#f472b6",
48
+ context: "#a3e635",
49
+ directive: "#5eead4",
50
+ binding: "#5eead4",
51
+ setter: "#fb7185",
52
+ group: "#8a90a2",
53
+ element: "#8a90a2"
54
+ };
55
+ var kindVar = (kind) => `--fx-kind-${kind}`;
56
+ var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
57
+ var KINDS = Object.keys(KIND_COLOR);
58
+ function kindRules(selector, property) {
59
+ return KINDS.map((kind) => `${selector(kind)} { ${property}: ${kindColor(kind)}; }`).join("\n");
60
+ }
61
+
62
+ // src/tree.ts
63
+ function inPageOrder(nodes) {
64
+ const slots = [];
65
+ const placed = [];
66
+ nodes.forEach((node, at) => {
67
+ if (node.order !== void 0) {
68
+ slots.push(at);
69
+ placed.push(node);
70
+ }
71
+ });
72
+ if (placed.length < 2) return nodes;
73
+ placed.sort((a, b) => a.order - b.order);
74
+ const out = nodes.slice();
75
+ slots.forEach((slot, i) => out[slot] = placed[i]);
76
+ return out;
77
+ }
78
+ var TREE_CSS = `
79
+ .fx-tree-row {
80
+ display: flex; align-items: center; gap: 4px;
81
+ padding: 1px 6px 1px calc(6px + var(--depth, 0) * 12px);
82
+ font-family: ui-monospace, monospace; font-size: 11.5px;
83
+ color: var(--fx-label, #e7e9ee); cursor: pointer; white-space: nowrap;
84
+ }
85
+ .fx-tree-row:hover { background: var(--fx-hover, #1b1e26); }
86
+ .fx-tree-row.on { background: var(--fx-sel, #232838); }
87
+ .fx-tree-twist {
88
+ width: 12px; padding: 0; border: 0; background: none; cursor: pointer;
89
+ color: var(--fx-muted, #8a90a2); font-size: 9px; line-height: 1;
90
+ }
91
+ .fx-tree-plugin {
92
+ font-size: 9px; color: var(--fx-accent, #5eead4);
93
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
94
+ max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
95
+ }
96
+ .fx-tree-lazy {
97
+ font-size: 9px; letter-spacing: .04em; color: var(--fx-router, #7dd3fc);
98
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
99
+ }
100
+ .fx-tree-count {
101
+ font-size: 9.5px; color: var(--fx-muted, #8a90a2);
102
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
103
+ }
104
+ ${kindRules((k) => `.fx-tree-row.n-${k} .fx-tree-tag`, "color")}
105
+ .fx-tree-bar {
106
+ display: flex; align-items: center; gap: 5px; padding: 2px 8px 5px;
107
+ font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
108
+ }
109
+ .fx-tree-bar:hover { color: var(--fx-label, #e7e9ee); }
110
+ .fx-tree-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
111
+ .fx-tree-row.n-element:hover .fx-tree-tag { color: var(--fx-label, #e7e9ee); }
112
+ /* A declared value is listed inside the tag that made it, so it reads quieter than a tag. */
113
+ .fx-tree-row.is-value .fx-tree-tag { font-style: italic; opacity: .85; }
114
+ .fx-tree-search {
115
+ width: calc(100% - 16px); margin: 0 8px 4px; padding: 2px 6px;
116
+ font: inherit; font-size: 10.5px;
117
+ color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
118
+ border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
119
+ }
120
+ .fx-tree-search:focus { outline: none; border-color: var(--fx-accent, #5eead4); }
121
+ .fx-tree-empty { display: block; padding: 8px; font-size: 11px; color: var(--fx-muted, #8a90a2); }
122
+ /* A prop reads as what was written on the tag, so it is quieter than the tag itself. */
123
+ /* The row that folds a tag's props away. Dashed, because it stands for something written
124
+ rather than something rendered. */
125
+ .fx-tree-row.is-props .fx-tree-tag {
126
+ display: inline-flex; align-items: center; gap: 5px;
127
+ padding: 0 5px; border: 1px dashed var(--fx-line, #262a35); border-radius: 3px;
128
+ color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
129
+ }
130
+ .fx-tree-row.is-props:hover .fx-tree-tag { border-color: var(--fx-accent, #5eead4); color: var(--fx-label, #e7e9ee); }
131
+ .fx-tree-props-count { opacity: .7; }
132
+
133
+ /* A prop is what was written on the tag above it, so it reads as an aside: the name in a
134
+ quiet chip, the value beside it, and no marker pretending it opens. */
135
+ .fx-tree-row.is-prop .fx-tree-tag { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
136
+ .fx-tree-prop-name {
137
+ flex: none; padding: 0 4px; border-radius: 3px;
138
+ background: var(--fx-chip, rgba(138, 144, 162, .14));
139
+ color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .02em;
140
+ }
141
+ .fx-tree-row.is-prop:hover .fx-tree-prop-name { color: var(--fx-label, #e7e9ee); }
142
+ .fx-tree-prop-value {
143
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
144
+ color: var(--fx-label, #e7e9ee); opacity: .75;
145
+ }
146
+ `;
147
+
148
+ // src/graph-view.ts
149
+ var SVGNS = "http://www.w3.org/2000/svg";
150
+ var COL = 200;
151
+ var ROW = 92;
152
+ var NW = 132;
153
+ var placeOf = (node) => node.source ?? node.usedAt ?? node.declaredAt;
154
+ var shortSource = (s) => {
155
+ const file = (s.file ?? "").split("/").pop() ?? "";
156
+ return s.line == null ? file : `${file}:${s.line}`;
157
+ };
158
+ var VALUE_CHARS = 7;
159
+ var fit = (v) => {
160
+ const text = v ?? "";
161
+ return text.length > VALUE_CHARS ? text.slice(0, VALUE_CHARS - 1) + "…" : text;
162
+ };
163
+ var NH = 62;
164
+ var PAD = 48;
165
+ var STEP = 110;
166
+ var FLASH = 560;
167
+ var EDGE_FLASH = 260;
168
+ var MIN_ZOOM = 0.2;
169
+ var OVER_NATURAL = 12;
170
+ var MIN_MAX_ZOOM = 8;
171
+ var ABSOLUTE_MAX_ZOOM = 4e3;
172
+ var DRAG_SLOP = 4;
173
+ var KIND_CLASS = { state: "n-state", memo: "n-memo", effect: "n-effect", store: "n-store", resource: "n-resource", component: "n-component", control: "n-control", router: "n-router", handler: "n-handler", group: "n-group", binding: "n-binding", setter: "n-setter", directive: "n-directive", context: "n-context", provider: "n-provider" };
174
+ var KIND_GLYPH = { state: "◆ state", memo: "ƒ memo", effect: "⚡ effect", store: "▤ store", resource: "⟳ resource", component: "◧ component", control: "⑂ control flow", router: "⤳ router", handler: "⇄ route", group: "▤ framework", binding: "⇥ dom", setter: "✎ sets", directive: "⌘ directive", context: "◈ context", provider: "◈ provider" };
175
+ var GROUP_ID = -1;
176
+ var bindingIds = /* @__PURE__ */ new Map();
177
+ var bindingId = (place) => {
178
+ const held = bindingIds.get(place);
179
+ if (held !== void 0) return held;
180
+ const id = -100 - bindingIds.size;
181
+ bindingIds.set(place, id);
182
+ return id;
183
+ };
184
+ var setterId = (owner, index) => -1e5 - owner * 32 - index;
185
+ function el(tag, attrs = {}) {
186
+ const n = document.createElementNS(SVGNS, tag);
187
+ for (const k in attrs) n.setAttribute(k, String(attrs[k]));
188
+ return n;
189
+ }
190
+ function computeDepths(graph, deps) {
191
+ const memo = /* @__PURE__ */ new Map();
192
+ const depth = (id, seen) => {
193
+ if (memo.has(id)) return memo.get(id);
194
+ if (seen.has(id)) return 0;
195
+ seen.add(id);
196
+ const node = graph.get(id);
197
+ let d = 0;
198
+ if (node) for (const dep of deps(node)) d = Math.max(d, depth(dep, seen) + 1);
199
+ seen.delete(id);
200
+ memo.set(id, d);
201
+ return d;
202
+ };
203
+ for (const node of graph.values()) node.depth = depth(node.id, /* @__PURE__ */ new Set());
204
+ }
205
+ function createGraphView(svg, options = {}) {
206
+ let signature = "";
207
+ let selected = null;
208
+ let focused = null;
209
+ const nodeEls = /* @__PURE__ */ new Map();
210
+ const edgeEls = [];
211
+ const staged = /* @__PURE__ */ new Map();
212
+ const edgeStaged = /* @__PURE__ */ new Map();
213
+ const stagedFor = /* @__PURE__ */ new Map();
214
+ let standIn = (id) => id;
215
+ const opened = /* @__PURE__ */ new Set();
216
+ const closed = /* @__PURE__ */ new Set();
217
+ const foldStores = options.collapseStores !== false;
218
+ let direction = options.direction ?? "horizontal";
219
+ let groupExternal = options.groupExternal !== false;
220
+ let showBindings = options.showBindings === true;
221
+ let kinds = options.kinds ? new Set(options.kinds) : null;
222
+ const uid = Math.random().toString(36).slice(2, 8);
223
+ const labelClip = `fx-label-clip-${uid}`;
224
+ const valueClip = `fx-value-clip-${uid}`;
225
+ const LABEL_W = 66;
226
+ const defs = el("defs");
227
+ const forLabel = el("clipPath", { id: labelClip });
228
+ forLabel.appendChild(el("rect", { x: 8, y: 0, width: LABEL_W, height: NH }));
229
+ const sourceClip = `fx-source-clip-${uid}`;
230
+ const forSource = el("clipPath", { id: sourceClip });
231
+ forSource.appendChild(el("rect", { x: 8, y: 0, width: NW - 16, height: NH }));
232
+ const forValue = el("clipPath", { id: valueClip });
233
+ forValue.appendChild(el("rect", { x: 8 + LABEL_W + 6, y: 0, width: NW - LABEL_W - 10, height: NH }));
234
+ defs.append(forLabel, forValue, forSource);
235
+ const viewport = el("g", { class: "fx-viewport" });
236
+ let edgeLayer = el("g", { class: "fx-edges" });
237
+ let nodeLayer = el("g", { class: "fx-nodes" });
238
+ viewport.append(edgeLayer, nodeLayer);
239
+ svg.append(defs, viewport);
240
+ const maxZoom = () => {
241
+ const box = svg.viewBox?.baseVal;
242
+ const rect = svg.getBoundingClientRect?.();
243
+ if (!box?.width || !rect?.width || !rect.height) return MIN_MAX_ZOOM;
244
+ const natural = Math.max(box.width / rect.width, (box.height || 0) / rect.height);
245
+ return Math.min(ABSOLUTE_MAX_ZOOM, Math.max(MIN_MAX_ZOOM, natural * OVER_NATURAL));
246
+ };
247
+ let scale = 1;
248
+ let panX = 0;
249
+ let panY = 0;
250
+ const applyView = () => viewport.setAttribute("transform", `translate(${panX} ${panY}) scale(${scale})`);
251
+ const toUserSpace = (clientX, clientY) => {
252
+ const ctm = svg.getScreenCTM?.();
253
+ if (!ctm) return { x: 0, y: 0 };
254
+ const point = Object.assign(svg.createSVGPoint(), { x: clientX, y: clientY }).matrixTransform(ctm.inverse());
255
+ return { x: point.x, y: point.y };
256
+ };
257
+ const zoomAt = (factor, at) => {
258
+ const next = Math.min(maxZoom(), Math.max(MIN_ZOOM, scale * factor));
259
+ if (next === scale) return;
260
+ panX = at.x - (at.x - panX) * next / scale;
261
+ panY = at.y - (at.y - panY) * next / scale;
262
+ scale = next;
263
+ applyView();
264
+ };
265
+ svg.addEventListener(
266
+ "wheel",
267
+ (event) => {
268
+ event.preventDefault();
269
+ zoomAt(Math.exp(-event.deltaY * 15e-4), toUserSpace(event.clientX, event.clientY));
270
+ },
271
+ { passive: false }
272
+ );
273
+ let dragging = null;
274
+ svg.addEventListener("pointerdown", (event) => {
275
+ if (event.button !== 0) return;
276
+ dragging = { x: event.clientX, y: event.clientY, panX, panY, moved: false };
277
+ });
278
+ svg.addEventListener("pointermove", (event) => {
279
+ if (!dragging) return;
280
+ const dx = event.clientX - dragging.x;
281
+ const dy = event.clientY - dragging.y;
282
+ if (!dragging.moved && Math.abs(dx) + Math.abs(dy) < DRAG_SLOP) return;
283
+ if (!dragging.moved) svg.setPointerCapture?.(event.pointerId);
284
+ dragging.moved = true;
285
+ const ratio = svg.getScreenCTM?.()?.a || 1;
286
+ panX = dragging.panX + dx / ratio;
287
+ panY = dragging.panY + dy / ratio;
288
+ applyView();
289
+ });
290
+ const endDrag = (event) => {
291
+ if (dragging?.moved) svg.releasePointerCapture?.(event.pointerId);
292
+ setTimeout(() => {
293
+ dragging = null;
294
+ }, 0);
295
+ };
296
+ svg.addEventListener("pointerup", endDrag);
297
+ svg.addEventListener("pointercancel", endDrag);
298
+ svg.addEventListener("dblclick", () => {
299
+ scale = 1;
300
+ panX = 0;
301
+ panY = 0;
302
+ applyView();
303
+ });
304
+ function stageWave(graph, now) {
305
+ const fresh = [];
306
+ for (const node of graph.values()) {
307
+ if (!node.lastPulse || stagedFor.get(node.id) === node.lastPulse) continue;
308
+ stagedFor.set(node.id, node.lastPulse);
309
+ if (nodeEls.has(node.id)) fresh.push(node.id);
310
+ }
311
+ if (!fresh.length) return;
312
+ const changed = new Set(fresh);
313
+ const after = /* @__PURE__ */ new Map();
314
+ for (const { a, b } of edgeEls) {
315
+ if (changed.has(a) && changed.has(b)) (after.get(a) ?? after.set(a, []).get(a)).push(b);
316
+ }
317
+ const fed = new Set([...after.values()].flat());
318
+ const step = /* @__PURE__ */ new Map();
319
+ const queue = fresh.filter((id) => !fed.has(id)).map((id) => ({ id, depth: 0 }));
320
+ if (!queue.length) for (const id of fresh) queue.push({ id, depth: 0 });
321
+ while (queue.length) {
322
+ const { id, depth } = queue.shift();
323
+ if (step.has(id)) continue;
324
+ step.set(id, depth);
325
+ for (const next of after.get(id) ?? []) if (!step.has(next)) queue.push({ id: next, depth: depth + 1 });
326
+ }
327
+ for (const id of fresh) staged.set(id, now + (step.get(id) ?? 0) * STEP);
328
+ for (const { a, b, el: el2 } of edgeEls) {
329
+ const from = step.get(a);
330
+ const to = step.get(b);
331
+ if (from === void 0 || to === void 0 || to <= from) continue;
332
+ edgeStaged.set(el2, now + from * STEP + STEP * 0.35);
333
+ }
334
+ }
335
+ function applySelected() {
336
+ for (const [id, { g }] of nodeEls) g.classList.toggle("selected", id === selected);
337
+ }
338
+ function related(graph, from) {
339
+ const feeds = /* @__PURE__ */ new Map();
340
+ const fedBy = /* @__PURE__ */ new Map();
341
+ const link = (source, target) => {
342
+ (feeds.get(source) ?? feeds.set(source, /* @__PURE__ */ new Set()).get(source)).add(target);
343
+ (fedBy.get(target) ?? fedBy.set(target, /* @__PURE__ */ new Set()).get(target)).add(source);
344
+ };
345
+ for (const node2 of graph.values()) {
346
+ for (const dep of node2.deps) link(dep, node2.id);
347
+ for (const written of node2.writes) link(node2.id, written);
348
+ }
349
+ const out = /* @__PURE__ */ new Set([from]);
350
+ const walk = (edges) => {
351
+ const queue = [from];
352
+ while (queue.length) {
353
+ for (const next of edges.get(queue.pop()) ?? []) {
354
+ if (!out.has(next)) {
355
+ out.add(next);
356
+ queue.push(next);
357
+ }
358
+ }
359
+ }
360
+ };
361
+ walk(feeds);
362
+ walk(fedBy);
363
+ const node = graph.get(from);
364
+ if (node?.createdIn !== void 0) out.add(node.createdIn);
365
+ for (const other of graph.values()) if (other.createdIn === from) out.add(other.id);
366
+ return out;
367
+ }
368
+ function light(set) {
369
+ if (!set) {
370
+ svg.classList.remove("fx-hovering");
371
+ for (const { g } of nodeEls.values()) g.classList.remove("hot");
372
+ for (const { el: path } of edgeEls) path.classList.remove("hot");
373
+ return;
374
+ }
375
+ svg.classList.add("fx-hovering");
376
+ for (const [nodeId, { g }] of nodeEls) g.classList.toggle("hot", set.has(nodeId));
377
+ for (const { a, b, el: path } of edgeEls) path.classList.toggle("hot", set.has(a) && set.has(b));
378
+ }
379
+ function hover(graph, id) {
380
+ if (id === null) return light(focused);
381
+ const set = new Set([...related(graph, id)].map(standIn));
382
+ light(set);
383
+ nodeEls.get(standIn(id))?.g.classList.add("hot");
384
+ }
385
+ function withEdges(graph) {
386
+ if (!showBindings) return graph;
387
+ const shown = new Map(graph);
388
+ let added = false;
389
+ for (const node of graph.values()) {
390
+ node.targets?.forEach((target) => {
391
+ const at = target.source;
392
+ const place = `${target.name ?? ""}|${target.element}|${at?.file ?? ""}:${at?.line ?? ""}:${at?.column ?? ""}`;
393
+ const id = bindingId(place);
394
+ const card = shown.get(id) ?? {
395
+ id,
396
+ label: target.name ? `${target.name} of ${target.element}` : target.element,
397
+ kind: "binding",
398
+ // The card reads the signal, so the arrow runs the way the value does.
399
+ deps: /* @__PURE__ */ new Set(),
400
+ writes: /* @__PURE__ */ new Set(),
401
+ runs: 0,
402
+ lastPulse: 0,
403
+ ...at ? { source: at } : {}
404
+ };
405
+ card.deps.add(node.id);
406
+ card.runs = Math.max(card.runs, node.runs);
407
+ card.lastPulse = Math.max(card.lastPulse, node.lastPulse);
408
+ shown.set(id, card);
409
+ added = true;
410
+ });
411
+ node.setAt?.forEach((at, i) => {
412
+ const id = setterId(node.id, i);
413
+ shown.set(id, {
414
+ id,
415
+ // Two handlers in one component can share an original position; the compiled line
416
+ // is what separates the cards.
417
+ label: `set at ${(at.file ?? "").split("/").pop() ?? "?"}:${at.line ?? ""}${at.compiled && at.compiled.line !== at.line ? `→${at.compiled.line}` : ""}`,
418
+ kind: "setter",
419
+ deps: /* @__PURE__ */ new Set(),
420
+ // A handler writes the signal, so it gets the write edge a computation would.
421
+ writes: /* @__PURE__ */ new Set([node.id]),
422
+ runs: node.runs,
423
+ // The write edge runs setter → signal, so the wave starts here.
424
+ lastPulse: node.lastPulse,
425
+ source: at
426
+ });
427
+ added = true;
428
+ });
429
+ }
430
+ return added ? shown : graph;
431
+ }
432
+ const inProject = (at) => !!at?.file && at.external !== true;
433
+ const machinery = (n) => {
434
+ if (n.authored !== false) return false;
435
+ if (n.kind === "state" || n.kind === "memo" || n.kind === "effect" || n.kind === "context") {
436
+ return true;
437
+ }
438
+ if (n.kind === "component" || n.kind === "provider") {
439
+ return !inProject(n.declaredAt) && !inProject(n.source) && !inProject(n.usedAt);
440
+ }
441
+ return false;
442
+ };
443
+ const VIEW_CARD = /* @__PURE__ */ new Set(["group", "binding", "setter"]);
444
+ const kindKey = () => kinds ? [...kinds].sort().join("+") : "*";
445
+ function onlyKinds(graph) {
446
+ if (!kinds) return graph;
447
+ const keep = (n) => VIEW_CARD.has(n.kind) || kinds.has(n.kind);
448
+ const out = /* @__PURE__ */ new Map();
449
+ for (const [id, node] of graph) {
450
+ if (!keep(node)) continue;
451
+ let owner = node.createdIn;
452
+ const seen = /* @__PURE__ */ new Set();
453
+ while (owner !== void 0 && !seen.has(owner)) {
454
+ seen.add(owner);
455
+ const up = graph.get(owner);
456
+ if (up && keep(up)) break;
457
+ owner = up?.createdIn;
458
+ }
459
+ out.set(id, owner === node.createdIn ? node : { ...node, createdIn: owner });
460
+ }
461
+ return out;
462
+ }
463
+ function grouped(graph) {
464
+ if (!groupExternal) return graph;
465
+ const outsiders = [...graph.values()].filter(
466
+ // A card carries the source of whatever made it, often a package. It already hangs off
467
+ // its signal and must not be moved under the group.
468
+ (n) => n.source?.external && n.createdIn === void 0 && n.kind !== "binding" && n.kind !== "setter" || machinery(n)
469
+ );
470
+ if (!outsiders.length) return graph;
471
+ const shown = new Map(graph);
472
+ shown.set(GROUP_ID, {
473
+ id: GROUP_ID,
474
+ label: "framework",
475
+ kind: "group",
476
+ deps: /* @__PURE__ */ new Set(),
477
+ writes: /* @__PURE__ */ new Set(),
478
+ runs: 0,
479
+ lastPulse: 0
480
+ });
481
+ const moved = new Set(outsiders.map((n) => n.id));
482
+ for (const node of outsiders) shown.set(node.id, { ...node, createdIn: GROUP_ID });
483
+ for (const [id, node] of graph) {
484
+ if (moved.has(id) || node.createdIn === void 0) continue;
485
+ let owner = node.createdIn;
486
+ const seen = /* @__PURE__ */ new Set();
487
+ while (owner !== void 0 && moved.has(owner) && !seen.has(owner)) {
488
+ seen.add(owner);
489
+ owner = graph.get(owner)?.createdIn;
490
+ }
491
+ if (owner !== node.createdIn) shown.set(id, { ...node, createdIn: owner });
492
+ }
493
+ return shown;
494
+ }
495
+ function rebuild(graph, folded) {
496
+ edgeLayer.remove();
497
+ nodeLayer.remove();
498
+ nodeEls.clear();
499
+ edgeEls.length = 0;
500
+ edgeStaged.clear();
501
+ edgeLayer = el("g", { class: "fx-edges" });
502
+ nodeLayer = el("g", { class: "fx-nodes" });
503
+ standIn = (id) => {
504
+ let at = id;
505
+ let outermost = id;
506
+ const seen = /* @__PURE__ */ new Set();
507
+ while (at !== void 0 && !seen.has(at)) {
508
+ seen.add(at);
509
+ if (folded.has(at) && at !== id) outermost = at;
510
+ at = graph.get(at)?.createdIn;
511
+ }
512
+ return outermost;
513
+ };
514
+ const visible = /* @__PURE__ */ new Map();
515
+ const hiddenCount = /* @__PURE__ */ new Map();
516
+ for (const node of graph.values()) {
517
+ const shown = standIn(node.id);
518
+ if (shown === node.id) visible.set(node.id, node);
519
+ else hiddenCount.set(shown, (hiddenCount.get(shown) ?? 0) + 1);
520
+ }
521
+ const readsOf = (node) => {
522
+ const out = /* @__PURE__ */ new Set();
523
+ for (const dep of node.deps) {
524
+ const from = standIn(dep);
525
+ if (from !== node.id && visible.has(from)) out.add(from);
526
+ }
527
+ if (node.createdIn !== void 0) {
528
+ const owner = standIn(node.createdIn);
529
+ if (owner !== node.id && visible.has(owner)) out.add(owner);
530
+ }
531
+ return out;
532
+ };
533
+ computeDepths(visible, readsOf);
534
+ const byDepth = /* @__PURE__ */ new Map();
535
+ let maxDepth = 0;
536
+ for (const node of visible.values()) {
537
+ const d = node.depth ?? 0;
538
+ (byDepth.get(d) ?? byDepth.set(d, []).get(d)).push(node);
539
+ maxDepth = Math.max(maxDepth, d);
540
+ }
541
+ for (const [d, nodes] of byDepth) byDepth.set(d, inPageOrder(nodes));
542
+ let maxRows = 0;
543
+ for (const nodes of byDepth.values()) maxRows = Math.max(maxRows, nodes.length);
544
+ const down = direction === "vertical";
545
+ const STEP_ACROSS = down ? COL + 24 : ROW;
546
+ const pos = /* @__PURE__ */ new Map();
547
+ for (const [d, nodes] of byDepth) {
548
+ const offset = PAD + (maxRows * STEP_ACROSS - nodes.length * STEP_ACROSS) / 2;
549
+ nodes.forEach(
550
+ (node, i) => pos.set(
551
+ node.id,
552
+ down ? { x: offset + i * STEP_ACROSS, y: PAD + d * (NH + 46) } : { x: PAD + d * COL, y: offset + i * STEP_ACROSS }
553
+ )
554
+ );
555
+ }
556
+ const curve = (from, to) => {
557
+ if (down) {
558
+ const x12 = from.x + NW / 2;
559
+ const y12 = from.y + NH;
560
+ const x22 = to.x + NW / 2;
561
+ const y22 = to.y;
562
+ const my = (y12 + y22) / 2;
563
+ return `M ${x12} ${y12} C ${x12} ${my}, ${x22} ${my}, ${x22} ${y22}`;
564
+ }
565
+ const x1 = from.x + NW;
566
+ const y1 = from.y + NH / 2;
567
+ const x2 = to.x;
568
+ const y2 = to.y + NH / 2;
569
+ const mx = (x1 + x2) / 2;
570
+ return `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
571
+ };
572
+ const addEdge = (a, b, kind, d) => {
573
+ const path = el("path", { class: kind, d });
574
+ edgeLayer.appendChild(path);
575
+ edgeEls.push({ a, b, el: path });
576
+ };
577
+ for (const node of visible.values()) {
578
+ const to = pos.get(node.id);
579
+ const owner = node.createdIn !== void 0 ? standIn(node.createdIn) : void 0;
580
+ for (const dep of readsOf(node)) {
581
+ const from = pos.get(dep);
582
+ const own = dep === owner && !node.deps.has(dep);
583
+ addEdge(dep, node.id, own ? "fx-oedge" : "fx-edge", curve(from, to));
584
+ }
585
+ for (const w of node.writes) {
586
+ const target = pos.get(standIn(w));
587
+ if (target && standIn(w) !== node.id) addEdge(node.id, standIn(w), "fx-wedge", curve(to, target));
588
+ }
589
+ }
590
+ for (const node of visible.values()) {
591
+ const p = pos.get(node.id);
592
+ const from = node.source?.external ? " n-external" : "";
593
+ const g = el("g", { class: `fx-node ${KIND_CLASS[node.kind]}${from}`, transform: `translate(${p.x} ${p.y})`, "data-id": node.id });
594
+ g.appendChild(el("rect", { class: "fx-box", width: NW, height: NH, rx: 12 }));
595
+ const kind = el("text", { class: "fx-kind", x: 12, y: 20 });
596
+ kind.textContent = KIND_GLYPH[node.kind];
597
+ const label = el("text", { class: "fx-label", x: 12, y: 38 });
598
+ label.setAttribute("clip-path", `url(#${labelClip})`);
599
+ label.textContent = node.label;
600
+ const value = el("text", { class: "fx-value", x: 8 + LABEL_W + 6, y: 38 });
601
+ value.setAttribute("clip-path", `url(#${valueClip})`);
602
+ value.textContent = fit(node.value);
603
+ const full = node.detail ?? node.value;
604
+ if (full) value.appendChild(el("title")).textContent = full;
605
+ g.append(kind, label, value);
606
+ if (node.lazy) {
607
+ const chunk = el("text", { class: "fx-lazy", x: 12, y: 52 });
608
+ chunk.textContent = "lazy";
609
+ chunk.appendChild(el("title")).textContent = "loaded as its own chunk";
610
+ g.appendChild(chunk);
611
+ }
612
+ const mark = pluginBadge(node);
613
+ if (mark) {
614
+ const badge = el("text", { class: "fx-plugin", x: NW - 12, y: 52, "text-anchor": "end" });
615
+ badge.textContent = fit(mark);
616
+ badge.appendChild(el("title")).textContent = mark;
617
+ g.appendChild(badge);
618
+ }
619
+ const summary = el("title");
620
+ summary.textContent = `${node.kind} ${node.label}${node.lazy ? " (lazy)" : ""}${full ? ` = ${full}` : ""}${placeOf(node) ? `
621
+ ${placeOf(node).file ?? ""}:${placeOf(node).line ?? ""}:${placeOf(node).column ?? ""}` : ""}`;
622
+ g.appendChild(summary);
623
+ const source = el("text", { class: "fx-source", x: 12, y: 52 });
624
+ source.setAttribute("clip-path", `url(#${sourceClip})`);
625
+ const sourceTitle = el("title");
626
+ source.appendChild(sourceTitle);
627
+ g.appendChild(source);
628
+ const inside = hiddenCount.get(node.id) ?? 0;
629
+ const owns = inside > 0 || [...graph.values()].some((n) => n.createdIn === node.id);
630
+ if (owns) {
631
+ const badge = el("text", { class: "fx-fold", x: NW - 12, y: 20, "text-anchor": "end" });
632
+ badge.textContent = inside > 0 ? `▸ ${inside}` : "▾";
633
+ badge.appendChild(el("title")).textContent = inside > 0 ? `show ${inside} inside` : "fold what is inside";
634
+ badge.addEventListener("click", (event) => {
635
+ event.stopPropagation();
636
+ toggleFold(node.id);
637
+ });
638
+ g.appendChild(badge);
639
+ }
640
+ g.addEventListener("click", () => {
641
+ if (dragging?.moved) return;
642
+ selected = node.id;
643
+ applySelected();
644
+ options.onSelect?.(node);
645
+ });
646
+ g.addEventListener("dblclick", (event) => {
647
+ if (!options.onReveal) return;
648
+ event.stopPropagation();
649
+ options.onReveal(node);
650
+ });
651
+ g.addEventListener("mouseenter", () => hover(graph, node.id));
652
+ g.addEventListener("mouseleave", () => hover(graph, null));
653
+ g.style.cursor = "pointer";
654
+ nodeLayer.appendChild(g);
655
+ nodeEls.set(node.id, { g, value, source, sourceTitle });
656
+ }
657
+ viewport.append(edgeLayer, nodeLayer);
658
+ const width = down ? PAD * 2 + maxRows * STEP_ACROSS + NW : PAD * 2 + maxDepth * COL + NW;
659
+ const height = down ? PAD * 2 + (maxDepth + 1) * (NH + 46) : PAD * 2 + maxRows * ROW;
660
+ svg.setAttribute("viewBox", `0 0 ${Math.max(width, 400)} ${Math.max(height, 240)}`);
661
+ applySelected();
662
+ }
663
+ let lastGraph = /* @__PURE__ */ new Map();
664
+ let lastShown = /* @__PURE__ */ new Map();
665
+ function foldedIn(graph) {
666
+ const hasChildren = /* @__PURE__ */ new Set();
667
+ for (const node of graph.values()) if (node.createdIn !== void 0) hasChildren.add(node.createdIn);
668
+ const inStore = (id) => {
669
+ const seen = /* @__PURE__ */ new Set();
670
+ let at = graph.get(id)?.createdIn;
671
+ while (at !== void 0 && !seen.has(at)) {
672
+ seen.add(at);
673
+ const up = graph.get(at);
674
+ if (up?.kind === "store") return true;
675
+ at = up?.createdIn;
676
+ }
677
+ return false;
678
+ };
679
+ const folded = /* @__PURE__ */ new Set();
680
+ for (const node of graph.values()) {
681
+ if (!hasChildren.has(node.id)) continue;
682
+ const byDefault = (node.kind === "group" || foldStores && (node.kind === "store" || inStore(node.id))) && !opened.has(node.id);
683
+ if (byDefault || closed.has(node.id)) folded.add(node.id);
684
+ }
685
+ return folded;
686
+ }
687
+ function toggleFold(id) {
688
+ const folded = foldedIn(grouped(lastGraph)).has(id);
689
+ if (folded) {
690
+ opened.add(id);
691
+ closed.delete(id);
692
+ } else {
693
+ closed.add(id);
694
+ opened.delete(id);
695
+ }
696
+ signature = "";
697
+ scale = 1;
698
+ panX = 0;
699
+ panY = 0;
700
+ applyView();
701
+ return !folded;
702
+ }
703
+ return {
704
+ // Date.now, matching what the reactor stamps on a node. See instrument.ts.
705
+ update(graph, now = Date.now()) {
706
+ lastGraph = graph;
707
+ const shown = grouped(withEdges(onlyKinds(graph)));
708
+ lastShown = shown;
709
+ const folded = foldedIn(shown);
710
+ let sig = `${[...folded].sort().join(",")}|${groupExternal}|${direction}|${kindKey()}|`;
711
+ for (const n of shown.values())
712
+ sig += `${n.id}:${n.kind}:${[...n.deps].sort().join(",")}>${[...n.writes].sort().join(",")}^${n.createdIn ?? ""};`;
713
+ if (sig !== signature) {
714
+ signature = sig;
715
+ rebuild(shown, folded);
716
+ if (focused) light(focused);
717
+ }
718
+ stageWave(shown, now);
719
+ for (const node of shown.values()) {
720
+ const eles = nodeEls.get(node.id);
721
+ if (!eles) continue;
722
+ const next = fit(node.value);
723
+ if (eles.value.textContent !== next) eles.value.textContent = next;
724
+ const place = placeOf(node);
725
+ const where = place ? shortSource(place) : "";
726
+ if (eles.source.textContent !== where) {
727
+ eles.source.textContent = where;
728
+ eles.sourceTitle.textContent = place ? `${place.file ? shortenPath(place.file, options.root) : ""}:${place.line ?? ""}:${place.column ?? ""}` : "";
729
+ }
730
+ const age = now - (staged.get(node.id) ?? node.lastPulse);
731
+ eles.g.style.setProperty("--pulse", (age >= 0 && age < FLASH ? 1 - age / FLASH : 0).toFixed(3));
732
+ }
733
+ for (const { el: el2 } of edgeEls) {
734
+ const at = edgeStaged.get(el2);
735
+ const age = at === void 0 ? Infinity : now - at;
736
+ el2.style.setProperty("--pulse", (age >= 0 && age < EDGE_FLASH ? 1 - age / EDGE_FLASH : 0).toFixed(3));
737
+ }
738
+ },
739
+ clear() {
740
+ signature = "";
741
+ selected = null;
742
+ focused = null;
743
+ svg.classList.remove("fx-hovering");
744
+ edgeLayer.replaceChildren();
745
+ nodeLayer.replaceChildren();
746
+ nodeEls.clear();
747
+ edgeEls.length = 0;
748
+ staged.clear();
749
+ stagedFor.clear();
750
+ edgeStaged.clear();
751
+ options.onSelect?.(null);
752
+ },
753
+ select(id) {
754
+ selected = id;
755
+ applySelected();
756
+ },
757
+ zoomBy(factor) {
758
+ const box = svg.viewBox?.baseVal;
759
+ zoomAt(factor, { x: (box?.width ?? 400) / 2, y: (box?.height ?? 240) / 2 });
760
+ },
761
+ resetView() {
762
+ scale = 1;
763
+ panX = 0;
764
+ panY = 0;
765
+ applyView();
766
+ },
767
+ toggle: toggleFold,
768
+ setGroupExternal(next) {
769
+ if (groupExternal === next) return;
770
+ groupExternal = next;
771
+ signature = "";
772
+ scale = 1;
773
+ panX = 0;
774
+ panY = 0;
775
+ applyView();
776
+ },
777
+ get groupExternal() {
778
+ return groupExternal;
779
+ },
780
+ setShowBindings(next) {
781
+ if (showBindings === next) return;
782
+ showBindings = next;
783
+ signature = "";
784
+ },
785
+ get showBindings() {
786
+ return showBindings;
787
+ },
788
+ setKinds(next) {
789
+ const key = next ? [...next].sort().join("+") : "*";
790
+ if (key === kindKey()) return;
791
+ kinds = next ? new Set(next) : null;
792
+ signature = "";
793
+ },
794
+ get kinds() {
795
+ return kinds;
796
+ },
797
+ get shown() {
798
+ return lastShown;
799
+ },
800
+ focus(ids) {
801
+ focused = ids?.length ? new Set(ids.map(standIn)) : null;
802
+ light(focused);
803
+ },
804
+ setDirection(next) {
805
+ if (direction === next) return;
806
+ direction = next;
807
+ signature = "";
808
+ scale = 1;
809
+ panX = 0;
810
+ panY = 0;
811
+ applyView();
812
+ },
813
+ get direction() {
814
+ return direction;
815
+ }
816
+ };
817
+ }
818
+ var DEVTOOLS_CSS = `
819
+ /* An edge carries a pulse while the change is crossing it, so a propagation reads as one
820
+ thing moving rather than everything lighting at once. */
821
+ .fx-edge {
822
+ fill: none;
823
+ stroke: var(--fx-edge, #38415a);
824
+ stroke-width: calc(1.6px + var(--pulse, 0) * 1.7px);
825
+ opacity: calc(.75 + var(--pulse, 0) * .25);
826
+ filter: drop-shadow(0 0 calc(var(--pulse, 0) * 5px) var(--fx-accent, #5eead4));
827
+ }
828
+ .fx-oedge { fill: none; stroke: var(--fx-own, #64748b); stroke-width: 1.2; stroke-dasharray: 1 4; stroke-linecap: round; opacity: .55; }
829
+ .fx-wedge {
830
+ fill: none;
831
+ stroke: var(--fx-write, #fb7185);
832
+ stroke-width: calc(1.4px + var(--pulse, 0) * 1.6px);
833
+ stroke-dasharray: 3 4;
834
+ opacity: calc(.65 + var(--pulse, 0) * .35);
835
+ }
836
+ .fx-node { color: var(--fx-accent, #5eead4); filter: drop-shadow(0 0 calc(var(--pulse,0) * 13px) var(--glow, #5eead4)); }
837
+ ${kindRules((k) => `.fx-node.n-${k}`, "--glow")}
838
+ .fx-node.n-group { opacity: .8; }
839
+ .fx-node.n-group .fx-box { stroke-dasharray: 4 3; }
840
+ /* Neither is a node: dashed and dimmer, so the reactive shape reads first. */
841
+ .fx-node.n-binding { opacity: .72; }
842
+ .fx-node.n-setter { opacity: .72; }
843
+ .fx-node.n-binding .fx-box, .fx-node.n-setter .fx-box { stroke-dasharray: 3 3; }
844
+ .fx-node.n-external { opacity: .55; }
845
+ .fx-node.n-external .fx-box { stroke-dasharray: 3 2; }
846
+ .fx-box { fill: var(--fx-node-bg, #131722); stroke: var(--glow, #5eead4); stroke-width: 1.4; stroke-opacity: calc(.5 + var(--pulse,0) * .5); }
847
+ .fx-node.selected .fx-box { fill: var(--fx-node-bg-selected, #1a2230); stroke-width: 2.4; }
848
+ .fx-kind { fill: var(--glow, #5eead4); font-family: ui-monospace, monospace; font-size: 9.5px; opacity: .85; }
849
+ .fx-label { fill: var(--fx-label, #e7e9ee); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; }
850
+ .fx-value { fill: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 12px; }
851
+ .fx-source { fill: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 9.5px; opacity: .75; }
852
+ .fx-plugin { fill: var(--fx-accent, #5eead4); font-family: ui-monospace, monospace; font-size: 9px; opacity: .9; }
853
+ .fx-lazy { fill: var(--fx-router, #7dd3fc); font-family: ui-monospace, monospace; font-size: 9px; letter-spacing: .04em; opacity: .9; }
854
+ .fx-fold { fill: var(--glow, #5eead4); font-family: ui-monospace, monospace; font-size: 10px; cursor: pointer; opacity: .8; }
855
+ .fx-fold:hover { opacity: 1; }
856
+ .fx-node.n-effect .fx-value { fill: var(--effect, #fbbf24); }
857
+ .fx-node.n-resource .fx-value { fill: var(--fx-write, #fb7185); }
858
+ /* Hovering one node is a question about that node: everything it cannot reach steps back. */
859
+ .fx-hovering .fx-node { opacity: .2; transition: opacity .12s ease; }
860
+ .fx-hovering .fx-node.hot { opacity: 1; }
861
+ .fx-hovering .fx-node.n-external.hot { opacity: .7; }
862
+ .fx-hovering .fx-edge, .fx-hovering .fx-oedge, .fx-hovering .fx-wedge { opacity: .08; }
863
+ .fx-hovering .fx-edge.hot { opacity: 1; stroke: var(--fx-accent, #5eead4); }
864
+ .fx-hovering .fx-oedge.hot { opacity: .8; }
865
+ .fx-hovering .fx-wedge.hot { opacity: .9; }
866
+ `;
867
+ export {
868
+ DEVTOOLS_CSS,
869
+ createGraphView
870
+ };