@fluixi/devtools 0.2.0-alpha.7 → 1.0.0-alpha.101

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Fluixi
3
+ Copyright (c) 2026 Ibrahima Touré
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @fluixi/devtools
2
2
 
3
3
  Live inspector for the Fluixi reactive graph. Instrument `signal` / `memo` / `effect`, then render
4
- the dependency graph and watch propagation animate — State → Memo → Effect, glitch-free, lazy.
4
+ the dependency graph and watch propagation animate: State → Memo → Effect, glitch-free, lazy.
5
5
 
6
6
  ```ts
7
7
  import { createReactor, createGraphView, DEVTOOLS_CSS } from '@fluixi/devtools';
@@ -23,13 +23,13 @@ tick();
23
23
 
24
24
  The instrumented `createSignal`/`createMemo`/`createEffect` wrap the real `@fluixi/reactive`
25
25
  primitives. A read is a
26
- subscription, so edges are recorded from actual reads — it's the genuine dependency graph, not a
26
+ subscription, so edges are recorded from actual reads: it's the genuine dependency graph, not a
27
27
  reconstruction. Values are re-read untracked each frame and effects count their own re-runs, so the
28
28
  inspector animates exactly as the core propagates.
29
29
 
30
30
  ## Watching an app you did not write
31
31
 
32
- createReactor hands out its own primitives, so it only sees code written against them. observeGraph() sees an ordinary app: the runtime reports each node as it is made, and the graph fills from plain `import { signal } from '@fluixi/core'`. Call it before the app's modules load — a node made earlier is not in the graph.
32
+ createReactor hands out its own primitives, so it only sees code written against them. observeGraph() sees an ordinary app: the runtime reports each node as it is made, and the graph fills from plain `import { signal } from '@fluixi/core'`. Call it before the app's modules load: a node made earlier is not in the graph.
33
33
 
34
34
  ```ts
35
35
  import { observeGraph, createGraphView } from '@fluixi/devtools';
@@ -42,13 +42,13 @@ tick();
42
42
 
43
43
  The same Map<number, RNode> the reactor produces, so createGraphView and anything else reading the graph does not care which made it. Dependencies are read off the core node, which already keeps its own sources and observers, so nothing is wrapped and no read is intercepted.
44
44
 
45
- No write edges, because nothing records which computation was running when a setter fired. Render effects are left out — the dom layer makes one per binding. An unwatched runtime pays one null check per node creation and per recompute.
45
+ No write edges, because nothing records which computation was running when a setter fired. Render effects are left out: the dom layer makes one per binding. An unwatched runtime pays one null check per node creation and per recompute.
46
46
 
47
47
  ## Getting the graph somewhere else
48
48
 
49
- serializeGraph flattens the graph to json — Sets become arrays, the live core node becomes version and state, the reader is dropped — and snapshotToRenderMap turns it back into something the view draws. sample() returns what moved, so a delta is the same call with those ids, and applySnapshot folds one into a graph already on screen.
49
+ serializeGraph flattens the graph to json, Sets become arrays, the live core node becomes version and state, the reader is dropped, and snapshotToRenderMap turns it back into something the view draws. sample() returns what moved, so a delta is the same call with those ids, and applySnapshot folds one into a graph already on screen.
50
50
 
51
- installDevtoolsHook() is for a tool that cannot be imported by the app. It leaves a global behind; when the application's runtime loads it finds it and hands over its own registration function, so the graph is built from the app's copy of the framework rather than the tool's. It has to be installed before the app runs — a browser agent at document_start — because the runtime offers once. It works against a production build too, without names or locations.
51
+ installDevtoolsHook() is for a tool that cannot be imported by the app. It leaves a global behind; when the application's runtime loads it finds it and hands over its own registration function, so the graph is built from the app's copy of the framework rather than the tool's. It has to be installed before the app runs, a browser agent at document_start, because the runtime offers once. It works against a production build too, without names or locations.
52
52
 
53
53
  ```js
54
54
  import { installDevtoolsHook } from '@fluixi/devtools';
@@ -72,7 +72,7 @@ globalThis.__fx_source?.("src/App.ts", 10, 15, "count");
72
72
  const count = signal(0);
73
73
  ```
74
74
 
75
- A renderer needs nothing else — no compiler, no source file, no ast — and file, line and column are enough for a host to decide how to open it. For example:
75
+ A renderer needs nothing else, no compiler, no source file, no ast, and file, line and column are enough for a host to decide how to open it. For example:
76
76
 
77
77
  ```ts
78
78
  for (const node of reactor.graph.values()) {
@@ -82,9 +82,9 @@ for (const node of reactor.graph.values()) {
82
82
  }
83
83
  ```
84
84
 
85
- The compiler wraps every component in a memo, so the graph sees the wrapper rather than the component. A node whose value carries a component's name reports its kind as `component` and takes that name as its label, so the graph reads `App` rather than `m5`. Nothing about the node changes — it is the same memo, with the same dependencies.
85
+ The compiler wraps every component in a memo, so the graph sees the wrapper rather than the component. A node whose value carries a component's name reports its kind as `component` and takes that name as its label, so the graph reads `App` rather than `m5`. Nothing about the node changes: it is the same memo, with the same dependencies.
86
86
 
87
- A component node's `source` is where its tag was written, which is where the node was made — the same meaning `source` has for every other kind. `declaredAt` is where the component itself was declared, which is usually another file. It stands in for `source` when nothing marked the tag, and two components sharing a name are reported without a location rather than with the wrong one.
87
+ A component node's `source` is where its tag was written, which is where the node was made: the same meaning `source` has for every other kind. `declaredAt` is where the component itself was declared, which is usually another file. It stands in for `source` when nothing marked the tag, and two components sharing a name are reported without a location rather than with the wrong one.
88
88
 
89
89
  ### Making a node the compiler cannot see
90
90
 
@@ -105,7 +105,7 @@ A production build should leave `sourceLocations` off and then carries none of i
105
105
  - `observeGraph()` → `{ graph, sample(), detach() }`
106
106
  - `installDevtoolsHook()` → the global a tool attaches through
107
107
  - `serializeGraph(graph, now?, { ids, removed }?)` / `snapshotToRenderMap(snapshot)`
108
- - `applySnapshot(graph, snapshot)` — fold a full snapshot or a delta into what is drawn
109
- - `formatValue(value, limit?)` — a printable rendering of any value; never throws
108
+ - `applySnapshot(graph, snapshot)`: fold a full snapshot or a delta into what is drawn
109
+ - `formatValue(value, limit?)`: a printable rendering of any value; never throws
110
110
  - `createGraphView(svg)` → `{ update(graph, now?), clear() }`
111
- - `DEVTOOLS_CSS` — default theme string (override the `fx-*` classes to restyle)
111
+ - `DEVTOOLS_CSS`: default theme string (override the `fx-*` classes to restyle)
package/dist/callsite.cjs CHANGED
@@ -77,5 +77,12 @@ function callSite(stack, origin) {
77
77
  return fallback ? { location: { ...fallback, external: true }, external: true } : void 0;
78
78
  }
79
79
  function currentCallSite(origin) {
80
- return callSite(new Error().stack, origin);
80
+ const errors = Error;
81
+ const limit = errors.stackTraceLimit;
82
+ errors.stackTraceLimit = 50;
83
+ try {
84
+ return callSite(new Error().stack, origin);
85
+ } finally {
86
+ errors.stackTraceLimit = limit;
87
+ }
81
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"callsite.d.ts","sourceRoot":"","sources":["../src/callsite.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AA8D1D;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAgBrE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAErE"}
1
+ {"version":3,"file":"callsite.d.ts","sourceRoot":"","sources":["../src/callsite.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AA8D1D;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAgBrE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAYrE"}
package/dist/callsite.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * A browser extension's own bundle.
3
3
  *
4
4
  * The devtools agent carries a copy of this package, so every frame inside it looked like
5
- * application code and every node reported `chrome-extension://…/agent.js` as its origin.
5
+ * application code and every node reported `chrome-extension://.../agent.js` as its origin.
6
6
  * Nothing under these schemes is ever the page's own source.
7
7
  */
8
8
  const EXTENSION = ['chrome-extension://', 'moz-extension://', 'safari-web-extension://', 'extension://'];
@@ -87,5 +87,16 @@ export function callSite(stack, origin) {
87
87
  * the frames of the creating call are still on the stack.
88
88
  */
89
89
  export function currentCallSite(origin) {
90
- return callSite(new Error().stack, origin);
90
+ // Deeper than the default ten frames: the reporting machinery and the framework's own calls
91
+ // can fill those, and the application's frame is the one past them.
92
+ // A V8 extension, absent from the DOM types; elsewhere it is an ignored property.
93
+ const errors = Error;
94
+ const limit = errors.stackTraceLimit;
95
+ errors.stackTraceLimit = 50;
96
+ try {
97
+ return callSite(new Error().stack, origin);
98
+ }
99
+ finally {
100
+ errors.stackTraceLimit = limit;
101
+ }
91
102
  }
package/dist/callsite.mjs CHANGED
@@ -52,7 +52,14 @@ function callSite(stack, origin) {
52
52
  return fallback ? { location: { ...fallback, external: true }, external: true } : void 0;
53
53
  }
54
54
  function currentCallSite(origin) {
55
- return callSite(new Error().stack, origin);
55
+ const errors = Error;
56
+ const limit = errors.stackTraceLimit;
57
+ errors.stackTraceLimit = 50;
58
+ try {
59
+ return callSite(new Error().stack, origin);
60
+ } finally {
61
+ errors.stackTraceLimit = limit;
62
+ }
56
63
  }
57
64
  export {
58
65
  callSite,
package/dist/diff.cjs CHANGED
@@ -106,6 +106,8 @@ function walk(value, depth, seen) {
106
106
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
107
107
  let read;
108
108
  try {
109
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
110
+ if (descriptor?.get) return `${key}: [getter]`;
109
111
  read = walk(object[key], depth + 1, seen);
110
112
  } catch {
111
113
  read = "[unreadable]";
package/dist/diff.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // What changed, rather than what the value is now.
2
2
  //
3
- // `todos [{…}] → [{…}, {…}]` says nothing the graph did not. "One row added at index 2, here
3
+ // `todos [{...}] → [{...}, {...}]` says nothing the graph did not. "One row added at index 2, here
4
4
  // it is" does. Computed at the write, while both sides are still in hand, afterwards only
5
5
  // the new value exists.
6
6
  import { formatValue } from './format.js';
package/dist/diff.mjs CHANGED
@@ -80,6 +80,8 @@ function walk(value, depth, seen) {
80
80
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
81
81
  let read;
82
82
  try {
83
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
84
+ if (descriptor?.get) return `${key}: [getter]`;
83
85
  read = walk(object[key], depth + 1, seen);
84
86
  } catch {
85
87
  read = "[unreadable]";
package/dist/export.cjs CHANGED
@@ -122,6 +122,8 @@ function walk(value, depth, seen) {
122
122
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
123
123
  let read;
124
124
  try {
125
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
126
+ if (descriptor?.get) return `${key}: [getter]`;
125
127
  read = walk(object[key], depth + 1, seen);
126
128
  } catch {
127
129
  read = "[unreadable]";
package/dist/export.mjs CHANGED
@@ -106,6 +106,8 @@ function walk(value, depth, seen) {
106
106
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
107
107
  let read;
108
108
  try {
109
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
110
+ if (descriptor?.get) return `${key}: [getter]`;
109
111
  read = walk(object[key], depth + 1, seen);
110
112
  } catch {
111
113
  read = "[unreadable]";
package/dist/format.cjs CHANGED
@@ -106,6 +106,8 @@ function walk(value, depth, seen) {
106
106
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
107
107
  let read;
108
108
  try {
109
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
110
+ if (descriptor?.get) return `${key}: [getter]`;
109
111
  read = walk(object[key], depth + 1, seen);
110
112
  } catch {
111
113
  read = "[unreadable]";
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAoHA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAM,GAAG,MAAM,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CA4C5D"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAwHA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAM,GAAG,MAAM,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CA4C5D"}
package/dist/format.js CHANGED
@@ -98,9 +98,14 @@ function walk(value, depth, seen) {
98
98
  }
99
99
  const keys = Object.keys(object);
100
100
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
101
- // A getter can throw, and a proxy can refuse. Neither is worth losing the whole value.
101
+ // A getter is shown, never called: on a component's props it builds the content
102
+ // (a `children` getter makes fresh nodes per read) and reads signals, so printing it
103
+ // ran the component's work again on every sample. A proxy can still refuse.
102
104
  let read;
103
105
  try {
106
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
107
+ if (descriptor?.get)
108
+ return `${key}: [getter]`;
104
109
  read = walk(object[key], depth + 1, seen);
105
110
  }
106
111
  catch {
package/dist/format.mjs CHANGED
@@ -80,6 +80,8 @@ function walk(value, depth, seen) {
80
80
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
81
81
  let read;
82
82
  try {
83
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
84
+ if (descriptor?.get) return `${key}: [getter]`;
83
85
  read = walk(object[key], depth + 1, seen);
84
86
  } catch {
85
87
  read = "[unreadable]";
package/dist/hook.cjs CHANGED
@@ -352,7 +352,14 @@ function callSite(stack, origin) {
352
352
  return fallback ? { location: { ...fallback, external: true }, external: true } : void 0;
353
353
  }
354
354
  function currentCallSite(origin) {
355
- return callSite(new Error().stack, origin);
355
+ const errors = Error;
356
+ const limit = errors.stackTraceLimit;
357
+ errors.stackTraceLimit = 50;
358
+ try {
359
+ return callSite(new Error().stack, origin);
360
+ } finally {
361
+ errors.stackTraceLimit = limit;
362
+ }
356
363
  }
357
364
 
358
365
  // src/instrument.ts
@@ -441,6 +448,8 @@ function walk(value, depth, seen) {
441
448
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
442
449
  let read;
443
450
  try {
451
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
452
+ if (descriptor?.get) return `${key}: [getter]`;
444
453
  read = walk(object[key], depth + 1, seen);
445
454
  } catch {
446
455
  read = "[unreadable]";
@@ -877,7 +886,7 @@ function observeGraph(options = {}) {
877
886
  const claims = (node, value) => {
878
887
  for (const dep of node.deps) {
879
888
  const other = graph.get(dep);
880
- if (other?.read && other.read() === value) return false;
889
+ if (other?.read && readRaw(other) === value) return false;
881
890
  }
882
891
  return true;
883
892
  };
@@ -895,6 +904,7 @@ function observeGraph(options = {}) {
895
904
  const scopeOfTarget = /* @__PURE__ */ new WeakMap();
896
905
  const fieldsOf = /* @__PURE__ */ new WeakMap();
897
906
  let described = null;
907
+ let descendants = null;
898
908
  const rendered = /* @__PURE__ */ new Map();
899
909
  const elementsIn = (value) => {
900
910
  if (value instanceof Element) return [value];
@@ -1256,6 +1266,7 @@ function observeGraph(options = {}) {
1256
1266
  graph,
1257
1267
  sample() {
1258
1268
  described = null;
1269
+ descendants = null;
1259
1270
  const changed = [];
1260
1271
  const moved = (id) => {
1261
1272
  changed.push(id);
@@ -1311,7 +1322,7 @@ function observeGraph(options = {}) {
1311
1322
  runsAtLastSample.set(node.id, node.runs);
1312
1323
  continue;
1313
1324
  }
1314
- const raw = node.read();
1325
+ const raw = readRaw(node);
1315
1326
  if (recording) raws.set(node.id, raw);
1316
1327
  edges(node.id, node);
1317
1328
  const stamped = raw?.$name;
@@ -1386,17 +1397,8 @@ function observeGraph(options = {}) {
1386
1397
  if (live.length) return live;
1387
1398
  collect(id);
1388
1399
  if (!live.length) {
1389
- const inside = (node) => {
1390
- const seen = /* @__PURE__ */ new Set();
1391
- let at = node.createdIn;
1392
- while (at !== void 0 && !seen.has(at)) {
1393
- if (at === id) return true;
1394
- seen.add(at);
1395
- at = graph.get(at)?.createdIn;
1396
- }
1397
- return false;
1398
- };
1399
- for (const node of graph.values()) if (inside(node)) collect(node.id);
1400
+ descendants ??= descendantsByCreator(graph);
1401
+ for (const below of descendants.get(id) ?? []) collect(below);
1400
1402
  }
1401
1403
  return live;
1402
1404
  },
@@ -1509,6 +1511,21 @@ function observeGraph(options = {}) {
1509
1511
  }
1510
1512
  return api;
1511
1513
  }
1514
+ function descendantsByCreator(graph) {
1515
+ const out = /* @__PURE__ */ new Map();
1516
+ for (const node of graph.values()) {
1517
+ const seen = /* @__PURE__ */ new Set();
1518
+ let at = node.createdIn;
1519
+ while (at !== void 0 && !seen.has(at)) {
1520
+ seen.add(at);
1521
+ let list = out.get(at);
1522
+ if (!list) out.set(at, list = []);
1523
+ list.push(node.id);
1524
+ at = graph.get(at)?.createdIn;
1525
+ }
1526
+ }
1527
+ return out;
1528
+ }
1512
1529
 
1513
1530
  // src/highlight.ts
1514
1531
  var LAYER = "fx-highlight-layer";
package/dist/hook.mjs CHANGED
@@ -330,7 +330,14 @@ function callSite(stack, origin) {
330
330
  return fallback ? { location: { ...fallback, external: true }, external: true } : void 0;
331
331
  }
332
332
  function currentCallSite(origin) {
333
- return callSite(new Error().stack, origin);
333
+ const errors = Error;
334
+ const limit = errors.stackTraceLimit;
335
+ errors.stackTraceLimit = 50;
336
+ try {
337
+ return callSite(new Error().stack, origin);
338
+ } finally {
339
+ errors.stackTraceLimit = limit;
340
+ }
334
341
  }
335
342
 
336
343
  // src/instrument.ts
@@ -430,6 +437,8 @@ function walk(value, depth, seen) {
430
437
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
431
438
  let read;
432
439
  try {
440
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
441
+ if (descriptor?.get) return `${key}: [getter]`;
433
442
  read = walk(object[key], depth + 1, seen);
434
443
  } catch {
435
444
  read = "[unreadable]";
@@ -866,7 +875,7 @@ function observeGraph(options = {}) {
866
875
  const claims = (node, value) => {
867
876
  for (const dep of node.deps) {
868
877
  const other = graph.get(dep);
869
- if (other?.read && other.read() === value) return false;
878
+ if (other?.read && readRaw(other) === value) return false;
870
879
  }
871
880
  return true;
872
881
  };
@@ -884,6 +893,7 @@ function observeGraph(options = {}) {
884
893
  const scopeOfTarget = /* @__PURE__ */ new WeakMap();
885
894
  const fieldsOf = /* @__PURE__ */ new WeakMap();
886
895
  let described = null;
896
+ let descendants = null;
887
897
  const rendered = /* @__PURE__ */ new Map();
888
898
  const elementsIn = (value) => {
889
899
  if (value instanceof Element) return [value];
@@ -1245,6 +1255,7 @@ function observeGraph(options = {}) {
1245
1255
  graph,
1246
1256
  sample() {
1247
1257
  described = null;
1258
+ descendants = null;
1248
1259
  const changed = [];
1249
1260
  const moved = (id) => {
1250
1261
  changed.push(id);
@@ -1300,7 +1311,7 @@ function observeGraph(options = {}) {
1300
1311
  runsAtLastSample.set(node.id, node.runs);
1301
1312
  continue;
1302
1313
  }
1303
- const raw = node.read();
1314
+ const raw = readRaw(node);
1304
1315
  if (recording) raws.set(node.id, raw);
1305
1316
  edges(node.id, node);
1306
1317
  const stamped = raw?.$name;
@@ -1375,17 +1386,8 @@ function observeGraph(options = {}) {
1375
1386
  if (live.length) return live;
1376
1387
  collect(id);
1377
1388
  if (!live.length) {
1378
- const inside = (node) => {
1379
- const seen = /* @__PURE__ */ new Set();
1380
- let at = node.createdIn;
1381
- while (at !== void 0 && !seen.has(at)) {
1382
- if (at === id) return true;
1383
- seen.add(at);
1384
- at = graph.get(at)?.createdIn;
1385
- }
1386
- return false;
1387
- };
1388
- for (const node of graph.values()) if (inside(node)) collect(node.id);
1389
+ descendants ??= descendantsByCreator(graph);
1390
+ for (const below of descendants.get(id) ?? []) collect(below);
1389
1391
  }
1390
1392
  return live;
1391
1393
  },
@@ -1498,6 +1500,21 @@ function observeGraph(options = {}) {
1498
1500
  }
1499
1501
  return api;
1500
1502
  }
1503
+ function descendantsByCreator(graph) {
1504
+ const out = /* @__PURE__ */ new Map();
1505
+ for (const node of graph.values()) {
1506
+ const seen = /* @__PURE__ */ new Set();
1507
+ let at = node.createdIn;
1508
+ while (at !== void 0 && !seen.has(at)) {
1509
+ seen.add(at);
1510
+ let list = out.get(at);
1511
+ if (!list) out.set(at, list = []);
1512
+ list.push(node.id);
1513
+ at = graph.get(at)?.createdIn;
1514
+ }
1515
+ }
1516
+ return out;
1517
+ }
1501
1518
 
1502
1519
  // src/highlight.ts
1503
1520
  var LAYER = "fx-highlight-layer";
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @fluixi/devtools v0.2.0-alpha.7 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/devtools v1.0.0-alpha.101 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -259,6 +259,8 @@ function walk(value, depth, seen) {
259
259
  const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
260
260
  let read;
261
261
  try {
262
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
263
+ if (descriptor?.get) return `${key}: [getter]`;
262
264
  read = walk(object[key], depth + 1, seen);
263
265
  } catch {
264
266
  read = "[unreadable]";
@@ -665,7 +667,14 @@ function callSite(stack, origin) {
665
667
  return fallback2 ? { location: { ...fallback2, external: true }, external: true } : void 0;
666
668
  }
667
669
  function currentCallSite(origin) {
668
- return callSite(new Error().stack, origin);
670
+ const errors = Error;
671
+ const limit = errors.stackTraceLimit;
672
+ errors.stackTraceLimit = 50;
673
+ try {
674
+ return callSite(new Error().stack, origin);
675
+ } finally {
676
+ errors.stackTraceLimit = limit;
677
+ }
669
678
  }
670
679
 
671
680
  // src/history.ts
@@ -1709,7 +1718,7 @@ function observeGraph(options = {}) {
1709
1718
  const claims = (node, value) => {
1710
1719
  for (const dep of node.deps) {
1711
1720
  const other = graph.get(dep);
1712
- if (other?.read && other.read() === value) return false;
1721
+ if (other?.read && readRaw(other) === value) return false;
1713
1722
  }
1714
1723
  return true;
1715
1724
  };
@@ -1727,6 +1736,7 @@ function observeGraph(options = {}) {
1727
1736
  const scopeOfTarget = /* @__PURE__ */ new WeakMap();
1728
1737
  const fieldsOf = /* @__PURE__ */ new WeakMap();
1729
1738
  let described = null;
1739
+ let descendants = null;
1730
1740
  const rendered = /* @__PURE__ */ new Map();
1731
1741
  const elementsIn = (value) => {
1732
1742
  if (value instanceof Element) return [value];
@@ -2088,6 +2098,7 @@ function observeGraph(options = {}) {
2088
2098
  graph,
2089
2099
  sample() {
2090
2100
  described = null;
2101
+ descendants = null;
2091
2102
  const changed = [];
2092
2103
  const moved = (id) => {
2093
2104
  changed.push(id);
@@ -2143,7 +2154,7 @@ function observeGraph(options = {}) {
2143
2154
  runsAtLastSample.set(node.id, node.runs);
2144
2155
  continue;
2145
2156
  }
2146
- const raw = node.read();
2157
+ const raw = readRaw(node);
2147
2158
  if (recording) raws.set(node.id, raw);
2148
2159
  edges(node.id, node);
2149
2160
  const stamped = raw?.$name;
@@ -2218,17 +2229,8 @@ function observeGraph(options = {}) {
2218
2229
  if (live.length) return live;
2219
2230
  collect(id);
2220
2231
  if (!live.length) {
2221
- const inside = (node) => {
2222
- const seen = /* @__PURE__ */ new Set();
2223
- let at3 = node.createdIn;
2224
- while (at3 !== void 0 && !seen.has(at3)) {
2225
- if (at3 === id) return true;
2226
- seen.add(at3);
2227
- at3 = graph.get(at3)?.createdIn;
2228
- }
2229
- return false;
2230
- };
2231
- for (const node of graph.values()) if (inside(node)) collect(node.id);
2232
+ descendants ??= descendantsByCreator(graph);
2233
+ for (const below of descendants.get(id) ?? []) collect(below);
2232
2234
  }
2233
2235
  return live;
2234
2236
  },
@@ -2341,6 +2343,21 @@ function observeGraph(options = {}) {
2341
2343
  }
2342
2344
  return api;
2343
2345
  }
2346
+ function descendantsByCreator(graph) {
2347
+ const out = /* @__PURE__ */ new Map();
2348
+ for (const node of graph.values()) {
2349
+ const seen = /* @__PURE__ */ new Set();
2350
+ let at3 = node.createdIn;
2351
+ while (at3 !== void 0 && !seen.has(at3)) {
2352
+ seen.add(at3);
2353
+ let list = out.get(at3);
2354
+ if (!list) out.set(at3, list = []);
2355
+ list.push(node.id);
2356
+ at3 = graph.get(at3)?.createdIn;
2357
+ }
2358
+ }
2359
+ return out;
2360
+ }
2344
2361
 
2345
2362
  // src/highlight.ts
2346
2363
  var LAYER = "fx-highlight-layer";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,20 +1,3 @@
1
- // @fluixi/devtools: live inspector for the Fluixi reactive graph.
2
- // Instrumented signal/memo/effect register into a graph; createGraphView renders + animates the
3
- // State → Memo → Effect propagation.
4
- //
5
- // import { createReactor, createGraphView } from '@fluixi/devtools';
6
- //
7
- // const reactor = createReactor();
8
- // reactor.run(({ createSignal, createMemo, createEffect }) => {
9
- // const [count, setCount] = createSignal(0, { name: 'count' });
10
- // const double = createMemo(() => count() * 2, { name: 'double' });
11
- // createEffect(() => console.log(double()), { name: 'log' });
12
- // setInterval(() => setCount((n) => n + 1), 1000);
13
- // });
14
- //
15
- // const view = createGraphView(svgEl);
16
- // const tick = () => { reactor.sample(); view.update(reactor.graph); requestAnimationFrame(tick); };
17
- // tick();
18
1
  export { createReactor, nodeVersion, nodeState } from './instrument.js';
19
2
  export { observeGraph } from './observe.js';
20
3
  export { installDevtoolsHook } from './hook.js';