@fluixi/devtools 0.2.0-alpha.4 → 0.2.0-alpha.6

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 (72) hide show
  1. package/dist/callsite.cjs +9 -0
  2. package/dist/callsite.d.ts +1 -1
  3. package/dist/callsite.d.ts.map +1 -1
  4. package/dist/callsite.js +10 -1
  5. package/dist/callsite.mjs +9 -0
  6. package/dist/change-view.js +2 -2
  7. package/dist/describe.js +2 -2
  8. package/dist/detail-panel.d.ts +1 -1
  9. package/dist/detail-panel.d.ts.map +1 -1
  10. package/dist/detail-panel.js +1 -1
  11. package/dist/diff.js +1 -1
  12. package/dist/export.cjs +1 -0
  13. package/dist/export.d.ts +2 -2
  14. package/dist/export.d.ts.map +1 -1
  15. package/dist/export.js +1 -1
  16. package/dist/export.mjs +1 -0
  17. package/dist/float.d.ts +1 -1
  18. package/dist/float.js +3 -3
  19. package/dist/format.js +3 -3
  20. package/dist/graph-view.cjs +12 -3
  21. package/dist/graph-view.d.ts.map +1 -1
  22. package/dist/graph-view.js +11 -2
  23. package/dist/graph-view.mjs +12 -3
  24. package/dist/history.d.ts +2 -2
  25. package/dist/history.js +2 -2
  26. package/dist/hook.cjs +158 -19
  27. package/dist/hook.d.ts +3 -3
  28. package/dist/hook.js +6 -6
  29. package/dist/hook.mjs +158 -19
  30. package/dist/index.cjs +186 -29
  31. package/dist/index.mjs +186 -29
  32. package/dist/inspector.cjs +8 -4
  33. package/dist/inspector.d.ts +2 -2
  34. package/dist/inspector.js +8 -8
  35. package/dist/inspector.mjs +8 -4
  36. package/dist/instrument.cjs +9 -3
  37. package/dist/instrument.d.ts +16 -1
  38. package/dist/instrument.d.ts.map +1 -1
  39. package/dist/instrument.mjs +9 -3
  40. package/dist/kind-filter.cjs +5 -2
  41. package/dist/kind-filter.d.ts +2 -2
  42. package/dist/kind-filter.js +4 -4
  43. package/dist/kind-filter.mjs +5 -2
  44. package/dist/observe.cjs +157 -19
  45. package/dist/observe.d.ts.map +1 -1
  46. package/dist/observe.js +165 -16
  47. package/dist/observe.mjs +157 -19
  48. package/dist/palette.cjs +4 -1
  49. package/dist/palette.d.ts.map +1 -1
  50. package/dist/palette.js +3 -0
  51. package/dist/palette.mjs +4 -1
  52. package/dist/path.d.ts +2 -2
  53. package/dist/path.js +2 -2
  54. package/dist/plugins/index.cjs +1 -1
  55. package/dist/plugins/index.mjs +1 -1
  56. package/dist/source.cjs +18 -3
  57. package/dist/source.d.ts +18 -3
  58. package/dist/source.d.ts.map +1 -1
  59. package/dist/source.js +29 -7
  60. package/dist/source.mjs +18 -3
  61. package/dist/timeline.js +3 -3
  62. package/dist/tree.cjs +19 -4
  63. package/dist/tree.d.ts.map +1 -1
  64. package/dist/tree.js +21 -2
  65. package/dist/tree.mjs +19 -4
  66. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  67. package/dist/wire.cjs +3 -1
  68. package/dist/wire.d.ts +2 -0
  69. package/dist/wire.d.ts.map +1 -1
  70. package/dist/wire.js +2 -0
  71. package/dist/wire.mjs +3 -1
  72. package/package.json +8 -2
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @fluixi/devtools v0.2.0-alpha.4 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/devtools v0.2.0-alpha.6 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/instrument.ts
4
4
  import {
@@ -18,6 +18,7 @@ import { createStore } from "@fluixi/reactive/store";
18
18
  // src/source.ts
19
19
  var MARKER = "__fx_source";
20
20
  var COMPONENT_MARKER = "__fx_component";
21
+ var CLASS_MARKER = "__fx_class";
21
22
  var HOLE_MARKER = "__fx_hole";
22
23
  var PROPS_MARKER = "__fx_props";
23
24
  var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
@@ -28,12 +29,16 @@ var shared = host[MARKS] ?? {
28
29
  components: /* @__PURE__ */ new Map()
29
30
  };
30
31
  host[MARKS] = shared;
31
- function markReactiveSource(file, line, column, name) {
32
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
33
+ function markReactiveSource(file, line, column, name, self) {
32
34
  const location2 = locate(file, line, column);
33
35
  if (shared.pending && location2.external && !shared.pending.location?.external) return;
34
- shared.pending = { name, location: location2 };
36
+ shared.pending = self && typeof self === "object" ? { name, location: location2, self } : { name, location: location2 };
35
37
  shared.expiring = false;
36
38
  }
39
+ function dropReactiveSource() {
40
+ shared.pending = null;
41
+ }
37
42
  function locate(file, line, column) {
38
43
  const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
39
44
  return external ? { file, line, column, external } : { file, line, column };
@@ -47,13 +52,19 @@ function markComponentSource(name, file, line, column) {
47
52
  }
48
53
  if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
49
54
  }
55
+ function markClassSource(constructor, file, line, column) {
56
+ if (typeof constructor === "function") shared.classes.set(constructor, locate(file, line, column));
57
+ }
58
+ function classSource(constructor) {
59
+ return typeof constructor === "function" ? shared.classes.get(constructor) : void 0;
60
+ }
50
61
  function componentSource(name) {
51
62
  return shared.components.get(name) ?? void 0;
52
63
  }
53
64
  function takeReactiveSource() {
54
65
  const mark = shared.pending;
55
66
  if (mark) {
56
- if (mark.name !== void 0) shared.pending = { location: mark.location };
67
+ if (mark.name !== void 0 || mark.self) shared.pending = { location: mark.location };
57
68
  if (!shared.expiring) {
58
69
  shared.expiring = true;
59
70
  queueMicrotask(() => {
@@ -93,6 +104,7 @@ function holeLocation(accessor) {
93
104
  function installSourceMarker() {
94
105
  if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
95
106
  if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
107
+ if (typeof host[CLASS_MARKER] !== "function") host[CLASS_MARKER] = markClassSource;
96
108
  if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
97
109
  if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
98
110
  }
@@ -541,6 +553,15 @@ var INTERNAL = [
541
553
  "/packages/dom/",
542
554
  "/packages/core/",
543
555
  "/packages/devtools/",
556
+ "/packages/web-components/",
557
+ "/packages/auth/",
558
+ "/packages/oauth2/",
559
+ "/packages/session/",
560
+ "/packages/router/",
561
+ "/packages/head/",
562
+ "/packages/start/",
563
+ "/packages/server/",
564
+ "/packages/template-runtime/",
544
565
  "/@fluixi/",
545
566
  "node_modules",
546
567
  "fluixi-devtools",
@@ -1033,7 +1054,10 @@ var KIND_COLOR = {
1033
1054
  binding: "#5eead4",
1034
1055
  setter: "#fb7185",
1035
1056
  group: "#8a90a2",
1036
- element: "#8a90a2"
1057
+ element: "#8a90a2",
1058
+ "custom-element": "#2dd4bf",
1059
+ module: "#facc15",
1060
+ service: "#60a5fa"
1037
1061
  };
1038
1062
  var kindVar = (kind) => `--fx-kind-${kind}`;
1039
1063
  var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
@@ -1050,7 +1074,11 @@ var TREE_KINDS = /* @__PURE__ */ new Set([
1050
1074
  "handler",
1051
1075
  // A component a plugin recognised as providing a context. Still a tag the author wrote,
1052
1076
  // so leaving it out took it and everything it rendered out of the tree.
1053
- "provider"
1077
+ "provider",
1078
+ // Scopes the runtime reported: what their signals and memos belong to.
1079
+ "custom-element",
1080
+ "module",
1081
+ "service"
1054
1082
  ]);
1055
1083
  function inPageOrder(nodes) {
1056
1084
  const slots = [];
@@ -1073,7 +1101,8 @@ function badgeOf(node) {
1073
1101
  return badges.get(node);
1074
1102
  }
1075
1103
  function componentTree(graph, options = {}) {
1076
- const wanted = (node) => TREE_KINDS.has(node.kind) || options.values === true && node.authored !== false;
1104
+ const held = (node) => node.scope !== void 0 && (node.kind === "state" || node.kind === "memo" || node.kind === "store");
1105
+ const wanted = (node) => TREE_KINDS.has(node.kind) || options.values === true && (node.authored !== false || held(node));
1077
1106
  const tagged = inPageOrder([...graph.values()].filter(wanted).sort((a, b) => a.id - b.id));
1078
1107
  const made = /* @__PURE__ */ new Map();
1079
1108
  for (const node of tagged) {
@@ -1269,7 +1298,8 @@ function createComponentTree(host2, options = {}) {
1269
1298
  const props = listed.length ? [{ key: group, label: "props", kind: node.kind, children: listed, props: true, owner: node.id }] : [];
1270
1299
  const row2 = {
1271
1300
  key: String(node.id),
1272
- label: node.label,
1301
+ // `<fx-counter>` in the graph, where nothing else draws brackets. Here the row adds them.
1302
+ label: node.kind === "custom-element" ? node.label.replace(/^<(.*)>$/, "$1") : node.label,
1273
1303
  kind: node.kind,
1274
1304
  id: node.id,
1275
1305
  children: [...props, ...node.children.map(build2)],
@@ -1388,6 +1418,12 @@ function createComponentTree(host2, options = {}) {
1388
1418
  };
1389
1419
  }
1390
1420
  const rendered = dom.map((node, i) => walk2(node, `${row2.id}:${i}`)).filter((node) => node !== void 0);
1421
+ const self = rendered[0];
1422
+ if (row2.kind === "custom-element" && rendered.length === 1 && self?.kind === "element") {
1423
+ rendered.splice(0, 1, ...self.children);
1424
+ if (self.element) row2.element = self.element;
1425
+ if (self.at) row2.at = self.at;
1426
+ }
1391
1427
  const rest = row2.children.filter((child) => !placed.has(child)).map(expand);
1392
1428
  const lifted = [];
1393
1429
  const spare = [];
@@ -1564,7 +1600,11 @@ var PREFIX = {
1564
1600
  // A plugin's, and it names them itself.
1565
1601
  directive: "d",
1566
1602
  context: "ctx",
1567
- provider: "p"
1603
+ provider: "p",
1604
+ // Scopes are always named by what opened them.
1605
+ "custom-element": "el",
1606
+ module: "mod",
1607
+ service: "svc"
1568
1608
  };
1569
1609
  function readProps(props) {
1570
1610
  const at3 = propsLocations(props);
@@ -1604,6 +1644,10 @@ function observeGraph(options = {}) {
1604
1644
  const boundTo = /* @__PURE__ */ new WeakMap();
1605
1645
  const drivenBy = /* @__PURE__ */ new WeakMap();
1606
1646
  let reaches2 = /* @__PURE__ */ new Map();
1647
+ const SCOPE_KINDS = /* @__PURE__ */ new Set(["custom-element", "module", "service"]);
1648
+ const scopeIds = /* @__PURE__ */ new WeakMap();
1649
+ const scopeOfTarget = /* @__PURE__ */ new WeakMap();
1650
+ const fieldsOf = /* @__PURE__ */ new WeakMap();
1607
1651
  let described = null;
1608
1652
  const rendered = /* @__PURE__ */ new Map();
1609
1653
  const elementsIn = (value) => {
@@ -1700,7 +1744,9 @@ function observeGraph(options = {}) {
1700
1744
  // Mapped, unlike `mark`: these two came off a stack, so they are positions in the
1701
1745
  // compiled file until a host translates them.
1702
1746
  ...site && !site.external ? { usedAt: mapped(site.location) } : {},
1703
- ...site && site.external && !mark?.location ? { source: mapped(site.location) } : {}
1747
+ // Not inside a scope: an element's prop is made by the package for the element, and
1748
+ // the package's line says less than the scope it sits in already does.
1749
+ ...site && site.external && !mark?.location && !report.scope ? { source: mapped(site.location) } : {}
1704
1750
  };
1705
1751
  const owner = ancestor(report.parents);
1706
1752
  if (owner !== void 0) node.createdIn = owner;
@@ -1710,6 +1756,28 @@ function observeGraph(options = {}) {
1710
1756
  node.createdIn = originId;
1711
1757
  if (from.source) node.source = from.source;
1712
1758
  }
1759
+ const scopeId = report.scope ? scopeIds.get(report.scope) : void 0;
1760
+ if (scopeId !== void 0) {
1761
+ node.scope = scopeId;
1762
+ const creator = node.createdIn !== void 0 ? graph.get(node.createdIn) : void 0;
1763
+ const insideAlready = node.createdIn === scopeId || creator?.scope === scopeId;
1764
+ if (!insideAlready) node.createdIn = scopeId;
1765
+ } else if (node.createdIn !== void 0) {
1766
+ const creator = graph.get(node.createdIn);
1767
+ const inherited = creator && SCOPE_KINDS.has(creator.kind) ? creator.id : creator?.scope;
1768
+ if (inherited !== void 0) node.scope = inherited;
1769
+ }
1770
+ const self = mark?.self;
1771
+ if (self) {
1772
+ const list = fieldsOf.get(self);
1773
+ if (list) list.push(id);
1774
+ else fieldsOf.set(self, [id]);
1775
+ const open = scopeOfTarget.get(self);
1776
+ if (open !== void 0 && graph.has(open)) {
1777
+ node.scope = open;
1778
+ node.createdIn = open;
1779
+ }
1780
+ }
1713
1781
  if (kind !== "effect" && typeof handle === "function") {
1714
1782
  const read = handle;
1715
1783
  node.read = () => untrack2(read);
@@ -1845,20 +1913,90 @@ function observeGraph(options = {}) {
1845
1913
  internals.delete(target);
1846
1914
  const id = ids.get(target);
1847
1915
  if (id === void 0) return;
1848
- removed.push({ id, rev: ++revision });
1849
- revs.delete(id);
1850
- if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1851
- history.disposed(id, graph.get(id)?.label);
1852
- graph.delete(id);
1853
- handed.delete(id);
1854
- raws.delete(id);
1855
- cores.delete(id);
1856
- rendered.delete(id);
1857
- setters.delete(id);
1858
- runsAtLastSample.delete(id);
1859
- unnamed.delete(id);
1916
+ forget(id);
1917
+ },
1918
+ scope(scope, info, parent) {
1919
+ dropReactiveSource();
1920
+ const id = seq++;
1921
+ const kind = info.kind === "element" ? "custom-element" : info.kind;
1922
+ const parentId = parent ? scopeIds.get(parent) : void 0;
1923
+ const found = currentCallSite(origin);
1924
+ const site = found && !/\0|__x00__|^virtual:/.test(found.location.file ?? "") ? found : void 0;
1925
+ const node = {
1926
+ id,
1927
+ label: info.name,
1928
+ kind,
1929
+ // The author named the element, the module and the service; none of them is plumbing.
1930
+ authored: true,
1931
+ deps: /* @__PURE__ */ new Set(),
1932
+ writes: /* @__PURE__ */ new Set(),
1933
+ runs: 0,
1934
+ lastPulse: 0,
1935
+ ...info.detail ? { value: info.detail, detail: info.detail } : {},
1936
+ ...parentId !== void 0 ? { scope: parentId, createdIn: parentId } : {}
1937
+ };
1938
+ const target = info.target;
1939
+ if (kind === "custom-element") {
1940
+ const defined = target ? classSource(target.constructor) : void 0;
1941
+ if (defined) node.declaredAt = defined;
1942
+ } else if (site && !site.external) {
1943
+ node.usedAt = mapped(site.location);
1944
+ }
1945
+ scopeIds.set(scope, id);
1946
+ if (target && typeof target === "object" && target.nodeType === 1) {
1947
+ rendered.set(id, [new WeakRef(target)]);
1948
+ }
1949
+ graph.set(id, node);
1950
+ history.created(id, node.createdIn, node.label);
1951
+ if (target && typeof target === "object") {
1952
+ scopeOfTarget.set(target, id);
1953
+ for (const field of fieldsOf.get(target) ?? []) {
1954
+ const made = graph.get(field);
1955
+ if (!made) continue;
1956
+ made.scope = id;
1957
+ made.createdIn = id;
1958
+ revs.set(field, ++revision);
1959
+ }
1960
+ }
1961
+ },
1962
+ scopeEnded(scope) {
1963
+ const id = scopeIds.get(scope);
1964
+ if (id === void 0) return;
1965
+ const ended = /* @__PURE__ */ new Set([id]);
1966
+ for (let grew = true; grew; ) {
1967
+ grew = false;
1968
+ for (const [other, node] of graph) {
1969
+ if (!ended.has(other) && node.scope !== void 0 && ended.has(node.scope)) {
1970
+ ended.add(other);
1971
+ grew = true;
1972
+ }
1973
+ }
1974
+ }
1975
+ for (const gone of ended) forget(gone);
1976
+ },
1977
+ scopeAdopted(scope, parent) {
1978
+ const node = graph.get(scopeIds.get(scope) ?? -1);
1979
+ const parentId = scopeIds.get(parent);
1980
+ if (!node || parentId === void 0 || parentId === node.id) return;
1981
+ node.scope = parentId;
1982
+ node.createdIn = parentId;
1983
+ revs.set(node.id, ++revision);
1860
1984
  }
1861
1985
  });
1986
+ function forget(id) {
1987
+ removed.push({ id, rev: ++revision });
1988
+ revs.delete(id);
1989
+ if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1990
+ history.disposed(id, graph.get(id)?.label);
1991
+ graph.delete(id);
1992
+ handed.delete(id);
1993
+ raws.delete(id);
1994
+ cores.delete(id);
1995
+ rendered.delete(id);
1996
+ setters.delete(id);
1997
+ runsAtLastSample.delete(id);
1998
+ unnamed.delete(id);
1999
+ }
1862
2000
  const edges = (id, node) => {
1863
2001
  const sources = cores.get(id)?.sources;
1864
2002
  node.deps.clear();
@@ -1878,6 +2016,14 @@ function observeGraph(options = {}) {
1878
2016
  revs.set(id, ++revision);
1879
2017
  };
1880
2018
  const now = Date.now();
2019
+ for (const [id, node] of graph) {
2020
+ if (node.kind !== "custom-element" || node.usedAt) continue;
2021
+ const element = rendered.get(id)?.[0]?.deref();
2022
+ const written = element ? element.__fx_loc : void 0;
2023
+ if (!written?.file) continue;
2024
+ node.usedAt = { file: written.file, line: written.line, column: written.column };
2025
+ revs.set(id, ++revision);
2026
+ }
1881
2027
  const bindings = /* @__PURE__ */ new Map();
1882
2028
  const drives = /* @__PURE__ */ new Map();
1883
2029
  const seen = /* @__PURE__ */ new Map();
@@ -2263,6 +2409,7 @@ function serializeGraph(graph, now = Date.now(), options = {}) {
2263
2409
  declaredAt: node.declaredAt,
2264
2410
  usedAt: node.usedAt,
2265
2411
  createdIn: node.createdIn,
2412
+ scope: node.scope,
2266
2413
  bindings: node.bindings,
2267
2414
  targets: node.targets,
2268
2415
  setAt: node.setAt,
@@ -2300,6 +2447,7 @@ function snapshotToRenderMap(snapshot) {
2300
2447
  declaredAt: node.declaredAt,
2301
2448
  usedAt: node.usedAt,
2302
2449
  createdIn: node.createdIn,
2450
+ scope: node.scope,
2303
2451
  bindings: node.bindings,
2304
2452
  targets: node.targets,
2305
2453
  setAt: node.setAt,
@@ -2452,8 +2600,8 @@ var OVER_NATURAL = 12;
2452
2600
  var MIN_MAX_ZOOM = 8;
2453
2601
  var ABSOLUTE_MAX_ZOOM = 4e3;
2454
2602
  var DRAG_SLOP = 4;
2455
- 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" };
2456
- 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" };
2603
+ 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", "custom-element": "n-custom-element", module: "n-module", service: "n-service" };
2604
+ 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", "custom-element": "⬡ custom element", module: "▦ module", service: "⚙ service" };
2457
2605
  var GROUP_ID = -1;
2458
2606
  var bindingIds = /* @__PURE__ */ new Map();
2459
2607
  var bindingId = (place2) => {
@@ -2714,6 +2862,7 @@ function createGraphView(svg, options = {}) {
2714
2862
  const inProject = (at3) => !!at3?.file && at3.external !== true;
2715
2863
  const machinery = (n) => {
2716
2864
  if (n.authored !== false) return false;
2865
+ if (n.scope !== void 0) return false;
2717
2866
  if (n.kind === "state" || n.kind === "memo" || n.kind === "effect" || n.kind === "context") {
2718
2867
  return true;
2719
2868
  }
@@ -3123,6 +3272,11 @@ ${kindRules((k) => `.fx-node.n-${k}`, "--glow")}
3123
3272
  .fx-node.n-binding { opacity: .72; }
3124
3273
  .fx-node.n-setter { opacity: .72; }
3125
3274
  .fx-node.n-binding .fx-box, .fx-node.n-setter .fx-box { stroke-dasharray: 3 3; }
3275
+ /* An element, module or service: a group its signals and memos belong to, so it reads as one. */
3276
+ .fx-node.n-custom-element .fx-box, .fx-node.n-module .fx-box, .fx-node.n-service .fx-box {
3277
+ stroke-width: 2.2;
3278
+ fill: color-mix(in oklab, var(--glow, #5eead4) 14%, var(--fx-node-bg, #131722));
3279
+ }
3126
3280
  .fx-node.n-external { opacity: .55; }
3127
3281
  .fx-node.n-external .fx-box { stroke-dasharray: 3 2; }
3128
3282
  .fx-box { fill: var(--fx-node-bg, #131722); stroke: var(--glow, #5eead4); stroke-width: 1.4; stroke-opacity: calc(.5 + var(--pulse,0) * .5); }
@@ -3581,7 +3735,7 @@ function createInspector(host2, options = {}) {
3581
3735
  // no locations at all, and an empty line there reads as missing data.
3582
3736
  (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
3583
3737
  // else, and only worth a line when it is.
3584
- (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
3738
+ (node.declaredAt && at(node.declaredAt, root) !== at(node.source, root) ? `<div class="ins-source">◧ ${node.kind === "custom-element" ? "defined at" : "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
3585
3739
  // was created, and this is the line that asked for it.
3586
3740
  (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
3587
3741
  // the stack had nothing to offer either. Saying so beats a card that just sits there.
@@ -3622,8 +3776,8 @@ function createInspector(host2, options = {}) {
3622
3776
  if (editing !== null) return;
3623
3777
  if (next === signature) return;
3624
3778
  signature = next;
3625
- body.innerHTML = `<div class="ins-head"><span class="ins-kind n-element">element</span><b>${escape3(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
3626
- // component that rendered it does — so this is as close as the source gets.
3779
+ body.innerHTML = `<div class="ins-head"><span class="ins-kind n-element">element</span><b>${escape3(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
3780
+ // component that rendered it does: so this is as close as the source gets.
3627
3781
  (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>` : "");
3628
3782
  },
3629
3783
  clear() {
@@ -3994,9 +4148,12 @@ var FILTERABLE_KINDS = [
3994
4148
  "router",
3995
4149
  "handler",
3996
4150
  "context",
3997
- "directive"
4151
+ "directive",
4152
+ "custom-element",
4153
+ "module",
4154
+ "service"
3998
4155
  ];
3999
- var LABEL = { handler: "route" };
4156
+ var LABEL = { handler: "route", "custom-element": "custom element" };
4000
4157
  function createKindFilter(host2, options) {
4001
4158
  const doc = host2.ownerDocument;
4002
4159
  const kinds = options.kinds ?? FILTERABLE_KINDS;
@@ -38,6 +38,7 @@ var shared = host[MARKS] ?? {
38
38
  components: /* @__PURE__ */ new Map()
39
39
  };
40
40
  host[MARKS] = shared;
41
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
41
42
 
42
43
  // src/format.ts
43
44
  var MAX_DEPTH = 3;
@@ -260,7 +261,10 @@ var KIND_COLOR = {
260
261
  binding: "#5eead4",
261
262
  setter: "#fb7185",
262
263
  group: "#8a90a2",
263
- element: "#8a90a2"
264
+ element: "#8a90a2",
265
+ "custom-element": "#2dd4bf",
266
+ module: "#facc15",
267
+ service: "#60a5fa"
264
268
  };
265
269
  var kindVar = (kind) => `--fx-kind-${kind}`;
266
270
  var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
@@ -595,7 +599,7 @@ function createInspector(host2, options = {}) {
595
599
  // no locations at all, and an empty line there reads as missing data.
596
600
  (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
597
601
  // else, and only worth a line when it is.
598
- (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
602
+ (node.declaredAt && at(node.declaredAt, root) !== at(node.source, root) ? `<div class="ins-source">◧ ${node.kind === "custom-element" ? "defined at" : "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
599
603
  // was created, and this is the line that asked for it.
600
604
  (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
601
605
  // the stack had nothing to offer either. Saying so beats a card that just sits there.
@@ -636,8 +640,8 @@ function createInspector(host2, options = {}) {
636
640
  if (editing !== null) return;
637
641
  if (next === signature) return;
638
642
  signature = next;
639
- 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
640
- // component that rendered it does — so this is as close as the source gets.
643
+ 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
644
+ // component that rendered it does: so this is as close as the source gets.
641
645
  (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>` : "");
642
646
  },
643
647
  clear() {
@@ -6,7 +6,7 @@ export interface Inspector {
6
6
  /**
7
7
  * Draw an element instead of a node.
8
8
  *
9
- * An element is not in the graph — nothing computes it — but it is the part of the page a
9
+ * An element is not in the graph: nothing computes it, but it is the part of the page a
10
10
  * reader recognises, and the graph knows which tag rendered it and which values write into
11
11
  * it. Those, and the line each binding was written on, are what there is to say.
12
12
  */
@@ -44,7 +44,7 @@ export interface InspectorOptions {
44
44
  * counts as a value is the application's business, and returns whether it took.
45
45
  *
46
46
  * Omitted leaves values read-only, which is what a panel reading a snapshot over a wire
47
- * wants — there is nothing on the other end to write to.
47
+ * wants: there is nothing on the other end to write to.
48
48
  */
49
49
  onEdit?: (id: number, raw: string) => boolean;
50
50
  /**
package/dist/inspector.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // What one node is, in words.
2
2
  //
3
3
  // The graph draws a node in the space of a card: a name, a kind, a clipped value, a file and
4
- // a line. Everything else about it — the version, the run count, what it reads, what reads
5
- // it, where it was declared as against where it was used — needs room, and every host that
4
+ // a line. Everything else about it: the version, the run count, what it reads, what reads
5
+ // it, where it was declared as against where it was used, needs room, and every host that
6
6
  // draws the graph wants the same answer. So it lives here rather than in each of them.
7
7
  //
8
8
  // Rendered as html into an element the host owns, and re-rendered only when what it says
@@ -48,7 +48,7 @@ const bothAt = (source, root) => {
48
48
  /**
49
49
  * The line worth opening for a tag.
50
50
  *
51
- * A component the framework instantiated — a lazy route, a layout the router mounted — was
51
+ * A component the framework instantiated: a lazy route, a layout the router mounted, was
52
52
  * created inside the framework, so `source` is a line in a package. `declaredAt` is where it
53
53
  * was actually written. Only one line is shown for an element, so it had better be that one.
54
54
  */
@@ -63,7 +63,7 @@ function authoredPlace(node) {
63
63
  * The tags this value writes inside, and how many times each.
64
64
  *
65
65
  * A signal handed to a component through a prop is read by the render effect that binds it,
66
- * which is machinery the graph does not draw — so there is no edge from the signal to the
66
+ * which is machinery the graph does not draw: so there is no edge from the signal to the
67
67
  * component, and a card for it looks connected only to whatever declared it. The
68
68
  * descriptions already say it exactly: each element records the values writing into it, and
69
69
  * it sits in the tree of the tag that rendered it.
@@ -255,7 +255,7 @@ export function createInspector(host, options = {}) {
255
255
  .join('');
256
256
  const next = `${node.id}|${node.kind}|${value}|${node.runs}|${state ?? ''}|${relations}|${at(node.source, root)}|${at(node.usedAt, root)}|${bound}`;
257
257
  // An input that has left the page is not one anybody is typing into. Without this a
258
- // commit that never arrived — the panel redrew, the tab changed — would hold the
258
+ // commit that never arrived: the panel redrew, the tab changed, would hold the
259
259
  // inspector on whatever it last wrote, for good.
260
260
  if (editing !== null && field?.isConnected !== true) {
261
261
  editing = null;
@@ -285,7 +285,7 @@ export function createInspector(host, options = {}) {
285
285
  // A component is created where its tag is written; where it is declared is somewhere
286
286
  // else, and only worth a line when it is.
287
287
  (node.declaredAt && at(node.declaredAt, root) !== at(node.source, root)
288
- ? `<div class="ins-source">◧ declared ${place(at(node.declaredAt, root), node.declaredAt, reveals)}</div>`
288
+ ? `<div class="ins-source">◧ ${node.kind === 'custom-element' ? 'defined at' : 'declared'} ${place(at(node.declaredAt, root), node.declaredAt, reveals)}</div>`
289
289
  : '') +
290
290
  // A node a package made on the application's behalf: the package's line is where it
291
291
  // was created, and this is the line that asked for it.
@@ -357,8 +357,8 @@ export function createInspector(host, options = {}) {
357
357
  `<b>${escape(pick.label)}</b>` +
358
358
  `<span class="ins-meta">not a node</span></div>` +
359
359
  `<div class="ins-rel">${relations}</div>` +
360
- // The tag's own line. An element has no position of its own in the graph — only the
361
- // component that rendered it does — so this is as close as the source gets.
360
+ // The tag's own line. An element has no position of its own in the graph, only the
361
+ // component that rendered it does: so this is as close as the source gets.
362
362
  (source
363
363
  ? `<div class="ins-source">${source.external ? '📦' : '📍'} ${place(bothAt(source, root), source, reveals)}</div>`
364
364
  : '') +
@@ -22,6 +22,7 @@ var shared = host[MARKS] ?? {
22
22
  components: /* @__PURE__ */ new Map()
23
23
  };
24
24
  host[MARKS] = shared;
25
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
25
26
 
26
27
  // src/format.ts
27
28
  var MAX_DEPTH = 3;
@@ -244,7 +245,10 @@ var KIND_COLOR = {
244
245
  binding: "#5eead4",
245
246
  setter: "#fb7185",
246
247
  group: "#8a90a2",
247
- element: "#8a90a2"
248
+ element: "#8a90a2",
249
+ "custom-element": "#2dd4bf",
250
+ module: "#facc15",
251
+ service: "#60a5fa"
248
252
  };
249
253
  var kindVar = (kind) => `--fx-kind-${kind}`;
250
254
  var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
@@ -579,7 +583,7 @@ function createInspector(host2, options = {}) {
579
583
  // no locations at all, and an empty line there reads as missing data.
580
584
  (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
585
  // 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
586
+ (node.declaredAt && at(node.declaredAt, root) !== at(node.source, root) ? `<div class="ins-source">◧ ${node.kind === "custom-element" ? "defined at" : "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
587
  // was created, and this is the line that asked for it.
584
588
  (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
589
  // the stack had nothing to offer either. Saying so beats a card that just sits there.
@@ -620,8 +624,8 @@ function createInspector(host2, options = {}) {
620
624
  if (editing !== null) return;
621
625
  if (next === signature) return;
622
626
  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.
627
+ 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
628
+ // component that rendered it does: so this is as close as the source gets.
625
629
  (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
630
  },
627
631
  clear() {
@@ -34,6 +34,7 @@ var import_store = require("@fluixi/reactive/store");
34
34
  // src/source.ts
35
35
  var MARKER = "__fx_source";
36
36
  var COMPONENT_MARKER = "__fx_component";
37
+ var CLASS_MARKER = "__fx_class";
37
38
  var HOLE_MARKER = "__fx_hole";
38
39
  var PROPS_MARKER = "__fx_props";
39
40
  var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
@@ -44,10 +45,11 @@ var shared = host[MARKS] ?? {
44
45
  components: /* @__PURE__ */ new Map()
45
46
  };
46
47
  host[MARKS] = shared;
47
- function markReactiveSource(file, line, column, name) {
48
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
49
+ function markReactiveSource(file, line, column, name, self) {
48
50
  const location = locate(file, line, column);
49
51
  if (shared.pending && location.external && !shared.pending.location?.external) return;
50
- shared.pending = { name, location };
52
+ shared.pending = self && typeof self === "object" ? { name, location, self } : { name, location };
51
53
  shared.expiring = false;
52
54
  }
53
55
  function locate(file, line, column) {
@@ -63,13 +65,16 @@ function markComponentSource(name, file, line, column) {
63
65
  }
64
66
  if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
65
67
  }
68
+ function markClassSource(constructor, file, line, column) {
69
+ if (typeof constructor === "function") shared.classes.set(constructor, locate(file, line, column));
70
+ }
66
71
  function componentSource(name) {
67
72
  return shared.components.get(name) ?? void 0;
68
73
  }
69
74
  function takeReactiveSource() {
70
75
  const mark = shared.pending;
71
76
  if (mark) {
72
- if (mark.name !== void 0) shared.pending = { location: mark.location };
77
+ if (mark.name !== void 0 || mark.self) shared.pending = { location: mark.location };
73
78
  if (!shared.expiring) {
74
79
  shared.expiring = true;
75
80
  queueMicrotask(() => {
@@ -102,6 +107,7 @@ function markProps(props, at) {
102
107
  function installSourceMarker() {
103
108
  if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
104
109
  if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
110
+ if (typeof host[CLASS_MARKER] !== "function") host[CLASS_MARKER] = markClassSource;
105
111
  if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
106
112
  if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
107
113
  }
@@ -19,7 +19,16 @@ export type NodeKind = 'state' | 'memo' | 'effect' | 'store' | 'resource' | 'com
19
19
  /** A component whose job is providing a context. */
20
20
  | 'provider'
21
21
  /** Not a node: a place in the code that sets a signal. */
22
- | 'setter';
22
+ | 'setter'
23
+ /**
24
+ * A custom element instance: its props and everything its render made. Not `element`,
25
+ * which the component tree already uses for the DOM element rows it lists.
26
+ */
27
+ | 'custom-element'
28
+ /** An app module registered with `defineModule` / `defineApp`. */
29
+ | 'module'
30
+ /** A service a DI provider built, such as the auth service: its own signals and memos. */
31
+ | 'service';
23
32
  export type NodeState = 'clean' | 'check' | 'dirty';
24
33
  /**
25
34
  * The names the compiler supplies without an import, and what each one is. A control-flow
@@ -70,6 +79,12 @@ export interface RNode {
70
79
  version?: number;
71
80
  state?: number;
72
81
  } | null;
82
+ /**
83
+ * The element, module or service this node belongs to: the id of that scope's node. Set
84
+ * for what was created while the scope was open, and for the scope nodes themselves when
85
+ * they sit inside another (a service built by a module).
86
+ */
87
+ scope?: number;
73
88
  /** Where the author created this node, when the compiler said so. Always optional. */
74
89
  source?: ReactiveSourceLocation;
75
90
  /**