@fluixi/devtools 0.2.0-alpha.5 → 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 (50) hide show
  1. package/dist/callsite.cjs +9 -0
  2. package/dist/callsite.d.ts.map +1 -1
  3. package/dist/callsite.js +9 -0
  4. package/dist/callsite.mjs +9 -0
  5. package/dist/export.cjs +1 -0
  6. package/dist/export.mjs +1 -0
  7. package/dist/graph-view.cjs +12 -3
  8. package/dist/graph-view.d.ts.map +1 -1
  9. package/dist/graph-view.js +11 -2
  10. package/dist/graph-view.mjs +12 -3
  11. package/dist/hook.cjs +158 -19
  12. package/dist/hook.mjs +158 -19
  13. package/dist/index.cjs +184 -27
  14. package/dist/index.mjs +184 -27
  15. package/dist/inspector.cjs +6 -2
  16. package/dist/inspector.js +1 -1
  17. package/dist/inspector.mjs +6 -2
  18. package/dist/instrument.cjs +9 -3
  19. package/dist/instrument.d.ts +16 -1
  20. package/dist/instrument.d.ts.map +1 -1
  21. package/dist/instrument.mjs +9 -3
  22. package/dist/kind-filter.cjs +5 -2
  23. package/dist/kind-filter.js +3 -3
  24. package/dist/kind-filter.mjs +5 -2
  25. package/dist/observe.cjs +157 -19
  26. package/dist/observe.d.ts.map +1 -1
  27. package/dist/observe.js +165 -16
  28. package/dist/observe.mjs +157 -19
  29. package/dist/palette.cjs +4 -1
  30. package/dist/palette.d.ts.map +1 -1
  31. package/dist/palette.js +3 -0
  32. package/dist/palette.mjs +4 -1
  33. package/dist/plugins/index.cjs +1 -1
  34. package/dist/plugins/index.mjs +1 -1
  35. package/dist/source.cjs +18 -3
  36. package/dist/source.d.ts +16 -1
  37. package/dist/source.d.ts.map +1 -1
  38. package/dist/source.js +25 -3
  39. package/dist/source.mjs +18 -3
  40. package/dist/tree.cjs +19 -4
  41. package/dist/tree.d.ts.map +1 -1
  42. package/dist/tree.js +21 -2
  43. package/dist/tree.mjs +19 -4
  44. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  45. package/dist/wire.cjs +3 -1
  46. package/dist/wire.d.ts +2 -0
  47. package/dist/wire.d.ts.map +1 -1
  48. package/dist/wire.js +2 -0
  49. package/dist/wire.mjs +3 -1
  50. package/package.json +2 -2
package/dist/hook.mjs CHANGED
@@ -184,6 +184,7 @@ import {
184
184
  // src/source.ts
185
185
  var MARKER = "__fx_source";
186
186
  var COMPONENT_MARKER = "__fx_component";
187
+ var CLASS_MARKER = "__fx_class";
187
188
  var HOLE_MARKER = "__fx_hole";
188
189
  var PROPS_MARKER = "__fx_props";
189
190
  var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
@@ -194,12 +195,16 @@ var shared = host[MARKS] ?? {
194
195
  components: /* @__PURE__ */ new Map()
195
196
  };
196
197
  host[MARKS] = shared;
197
- function markReactiveSource(file, line, column, name) {
198
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
199
+ function markReactiveSource(file, line, column, name, self) {
198
200
  const location2 = locate(file, line, column);
199
201
  if (shared.pending && location2.external && !shared.pending.location?.external) return;
200
- shared.pending = { name, location: location2 };
202
+ shared.pending = self && typeof self === "object" ? { name, location: location2, self } : { name, location: location2 };
201
203
  shared.expiring = false;
202
204
  }
205
+ function dropReactiveSource() {
206
+ shared.pending = null;
207
+ }
203
208
  function locate(file, line, column) {
204
209
  const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
205
210
  return external ? { file, line, column, external } : { file, line, column };
@@ -213,13 +218,19 @@ function markComponentSource(name, file, line, column) {
213
218
  }
214
219
  if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
215
220
  }
221
+ function markClassSource(constructor, file, line, column) {
222
+ if (typeof constructor === "function") shared.classes.set(constructor, locate(file, line, column));
223
+ }
224
+ function classSource(constructor) {
225
+ return typeof constructor === "function" ? shared.classes.get(constructor) : void 0;
226
+ }
216
227
  function componentSource(name) {
217
228
  return shared.components.get(name) ?? void 0;
218
229
  }
219
230
  function takeReactiveSource() {
220
231
  const mark = shared.pending;
221
232
  if (mark) {
222
- if (mark.name !== void 0) shared.pending = { location: mark.location };
233
+ if (mark.name !== void 0 || mark.self) shared.pending = { location: mark.location };
223
234
  if (!shared.expiring) {
224
235
  shared.expiring = true;
225
236
  queueMicrotask(() => {
@@ -259,6 +270,7 @@ function holeLocation(accessor) {
259
270
  function installSourceMarker() {
260
271
  if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
261
272
  if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
273
+ if (typeof host[CLASS_MARKER] !== "function") host[CLASS_MARKER] = markClassSource;
262
274
  if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
263
275
  if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
264
276
  }
@@ -270,6 +282,15 @@ var INTERNAL = [
270
282
  "/packages/dom/",
271
283
  "/packages/core/",
272
284
  "/packages/devtools/",
285
+ "/packages/web-components/",
286
+ "/packages/auth/",
287
+ "/packages/oauth2/",
288
+ "/packages/session/",
289
+ "/packages/router/",
290
+ "/packages/head/",
291
+ "/packages/start/",
292
+ "/packages/server/",
293
+ "/packages/template-runtime/",
273
294
  "/@fluixi/",
274
295
  "node_modules",
275
296
  "fluixi-devtools",
@@ -660,7 +681,10 @@ var KIND_COLOR = {
660
681
  binding: "#5eead4",
661
682
  setter: "#fb7185",
662
683
  group: "#8a90a2",
663
- element: "#8a90a2"
684
+ element: "#8a90a2",
685
+ "custom-element": "#2dd4bf",
686
+ module: "#facc15",
687
+ service: "#60a5fa"
664
688
  };
665
689
  var kindVar = (kind) => `--fx-kind-${kind}`;
666
690
  var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
@@ -677,7 +701,11 @@ var TREE_KINDS = /* @__PURE__ */ new Set([
677
701
  "handler",
678
702
  // A component a plugin recognised as providing a context. Still a tag the author wrote,
679
703
  // so leaving it out took it and everything it rendered out of the tree.
680
- "provider"
704
+ "provider",
705
+ // Scopes the runtime reported: what their signals and memos belong to.
706
+ "custom-element",
707
+ "module",
708
+ "service"
681
709
  ]);
682
710
  var TREE_CSS = `
683
711
  .fx-tree-row {
@@ -793,7 +821,11 @@ var PREFIX = {
793
821
  // A plugin's, and it names them itself.
794
822
  directive: "d",
795
823
  context: "ctx",
796
- provider: "p"
824
+ provider: "p",
825
+ // Scopes are always named by what opened them.
826
+ "custom-element": "el",
827
+ module: "mod",
828
+ service: "svc"
797
829
  };
798
830
  function readProps(props) {
799
831
  const at = propsLocations(props);
@@ -833,6 +865,10 @@ function observeGraph(options = {}) {
833
865
  const boundTo = /* @__PURE__ */ new WeakMap();
834
866
  const drivenBy = /* @__PURE__ */ new WeakMap();
835
867
  let reaches = /* @__PURE__ */ new Map();
868
+ const SCOPE_KINDS = /* @__PURE__ */ new Set(["custom-element", "module", "service"]);
869
+ const scopeIds = /* @__PURE__ */ new WeakMap();
870
+ const scopeOfTarget = /* @__PURE__ */ new WeakMap();
871
+ const fieldsOf = /* @__PURE__ */ new WeakMap();
836
872
  let described = null;
837
873
  const rendered = /* @__PURE__ */ new Map();
838
874
  const elementsIn = (value) => {
@@ -929,7 +965,9 @@ function observeGraph(options = {}) {
929
965
  // Mapped, unlike `mark`: these two came off a stack, so they are positions in the
930
966
  // compiled file until a host translates them.
931
967
  ...site && !site.external ? { usedAt: mapped(site.location) } : {},
932
- ...site && site.external && !mark?.location ? { source: mapped(site.location) } : {}
968
+ // Not inside a scope: an element's prop is made by the package for the element, and
969
+ // the package's line says less than the scope it sits in already does.
970
+ ...site && site.external && !mark?.location && !report.scope ? { source: mapped(site.location) } : {}
933
971
  };
934
972
  const owner = ancestor(report.parents);
935
973
  if (owner !== void 0) node.createdIn = owner;
@@ -939,6 +977,28 @@ function observeGraph(options = {}) {
939
977
  node.createdIn = originId;
940
978
  if (from.source) node.source = from.source;
941
979
  }
980
+ const scopeId = report.scope ? scopeIds.get(report.scope) : void 0;
981
+ if (scopeId !== void 0) {
982
+ node.scope = scopeId;
983
+ const creator = node.createdIn !== void 0 ? graph.get(node.createdIn) : void 0;
984
+ const insideAlready = node.createdIn === scopeId || creator?.scope === scopeId;
985
+ if (!insideAlready) node.createdIn = scopeId;
986
+ } else if (node.createdIn !== void 0) {
987
+ const creator = graph.get(node.createdIn);
988
+ const inherited = creator && SCOPE_KINDS.has(creator.kind) ? creator.id : creator?.scope;
989
+ if (inherited !== void 0) node.scope = inherited;
990
+ }
991
+ const self = mark?.self;
992
+ if (self) {
993
+ const list = fieldsOf.get(self);
994
+ if (list) list.push(id);
995
+ else fieldsOf.set(self, [id]);
996
+ const open = scopeOfTarget.get(self);
997
+ if (open !== void 0 && graph.has(open)) {
998
+ node.scope = open;
999
+ node.createdIn = open;
1000
+ }
1001
+ }
942
1002
  if (kind !== "effect" && typeof handle === "function") {
943
1003
  const read = handle;
944
1004
  node.read = () => untrack2(read);
@@ -1074,20 +1134,90 @@ function observeGraph(options = {}) {
1074
1134
  internals.delete(target);
1075
1135
  const id = ids.get(target);
1076
1136
  if (id === void 0) return;
1077
- removed.push({ id, rev: ++revision });
1078
- revs.delete(id);
1079
- if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1080
- history.disposed(id, graph.get(id)?.label);
1081
- graph.delete(id);
1082
- handed.delete(id);
1083
- raws.delete(id);
1084
- cores.delete(id);
1085
- rendered.delete(id);
1086
- setters.delete(id);
1087
- runsAtLastSample.delete(id);
1088
- unnamed.delete(id);
1137
+ forget(id);
1138
+ },
1139
+ scope(scope, info, parent) {
1140
+ dropReactiveSource();
1141
+ const id = seq++;
1142
+ const kind = info.kind === "element" ? "custom-element" : info.kind;
1143
+ const parentId = parent ? scopeIds.get(parent) : void 0;
1144
+ const found = currentCallSite(origin);
1145
+ const site = found && !/\0|__x00__|^virtual:/.test(found.location.file ?? "") ? found : void 0;
1146
+ const node = {
1147
+ id,
1148
+ label: info.name,
1149
+ kind,
1150
+ // The author named the element, the module and the service; none of them is plumbing.
1151
+ authored: true,
1152
+ deps: /* @__PURE__ */ new Set(),
1153
+ writes: /* @__PURE__ */ new Set(),
1154
+ runs: 0,
1155
+ lastPulse: 0,
1156
+ ...info.detail ? { value: info.detail, detail: info.detail } : {},
1157
+ ...parentId !== void 0 ? { scope: parentId, createdIn: parentId } : {}
1158
+ };
1159
+ const target = info.target;
1160
+ if (kind === "custom-element") {
1161
+ const defined = target ? classSource(target.constructor) : void 0;
1162
+ if (defined) node.declaredAt = defined;
1163
+ } else if (site && !site.external) {
1164
+ node.usedAt = mapped(site.location);
1165
+ }
1166
+ scopeIds.set(scope, id);
1167
+ if (target && typeof target === "object" && target.nodeType === 1) {
1168
+ rendered.set(id, [new WeakRef(target)]);
1169
+ }
1170
+ graph.set(id, node);
1171
+ history.created(id, node.createdIn, node.label);
1172
+ if (target && typeof target === "object") {
1173
+ scopeOfTarget.set(target, id);
1174
+ for (const field of fieldsOf.get(target) ?? []) {
1175
+ const made = graph.get(field);
1176
+ if (!made) continue;
1177
+ made.scope = id;
1178
+ made.createdIn = id;
1179
+ revs.set(field, ++revision);
1180
+ }
1181
+ }
1182
+ },
1183
+ scopeEnded(scope) {
1184
+ const id = scopeIds.get(scope);
1185
+ if (id === void 0) return;
1186
+ const ended = /* @__PURE__ */ new Set([id]);
1187
+ for (let grew = true; grew; ) {
1188
+ grew = false;
1189
+ for (const [other, node] of graph) {
1190
+ if (!ended.has(other) && node.scope !== void 0 && ended.has(node.scope)) {
1191
+ ended.add(other);
1192
+ grew = true;
1193
+ }
1194
+ }
1195
+ }
1196
+ for (const gone of ended) forget(gone);
1197
+ },
1198
+ scopeAdopted(scope, parent) {
1199
+ const node = graph.get(scopeIds.get(scope) ?? -1);
1200
+ const parentId = scopeIds.get(parent);
1201
+ if (!node || parentId === void 0 || parentId === node.id) return;
1202
+ node.scope = parentId;
1203
+ node.createdIn = parentId;
1204
+ revs.set(node.id, ++revision);
1089
1205
  }
1090
1206
  });
1207
+ function forget(id) {
1208
+ removed.push({ id, rev: ++revision });
1209
+ revs.delete(id);
1210
+ if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1211
+ history.disposed(id, graph.get(id)?.label);
1212
+ graph.delete(id);
1213
+ handed.delete(id);
1214
+ raws.delete(id);
1215
+ cores.delete(id);
1216
+ rendered.delete(id);
1217
+ setters.delete(id);
1218
+ runsAtLastSample.delete(id);
1219
+ unnamed.delete(id);
1220
+ }
1091
1221
  const edges = (id, node) => {
1092
1222
  const sources = cores.get(id)?.sources;
1093
1223
  node.deps.clear();
@@ -1107,6 +1237,14 @@ function observeGraph(options = {}) {
1107
1237
  revs.set(id, ++revision);
1108
1238
  };
1109
1239
  const now = Date.now();
1240
+ for (const [id, node] of graph) {
1241
+ if (node.kind !== "custom-element" || node.usedAt) continue;
1242
+ const element = rendered.get(id)?.[0]?.deref();
1243
+ const written = element ? element.__fx_loc : void 0;
1244
+ if (!written?.file) continue;
1245
+ node.usedAt = { file: written.file, line: written.line, column: written.column };
1246
+ revs.set(id, ++revision);
1247
+ }
1110
1248
  const bindings = /* @__PURE__ */ new Map();
1111
1249
  const drives = /* @__PURE__ */ new Map();
1112
1250
  const seen = /* @__PURE__ */ new Map();
@@ -1445,6 +1583,7 @@ function serializeGraph(graph, now = Date.now(), options = {}) {
1445
1583
  declaredAt: node.declaredAt,
1446
1584
  usedAt: node.usedAt,
1447
1585
  createdIn: node.createdIn,
1586
+ scope: node.scope,
1448
1587
  bindings: node.bindings,
1449
1588
  targets: node.targets,
1450
1589
  setAt: node.setAt,
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @fluixi/devtools v0.2.0-alpha.5 | (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
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -86,6 +86,7 @@ var import_store = require("@fluixi/reactive/store");
86
86
  // src/source.ts
87
87
  var MARKER = "__fx_source";
88
88
  var COMPONENT_MARKER = "__fx_component";
89
+ var CLASS_MARKER = "__fx_class";
89
90
  var HOLE_MARKER = "__fx_hole";
90
91
  var PROPS_MARKER = "__fx_props";
91
92
  var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
@@ -96,12 +97,16 @@ var shared = host[MARKS] ?? {
96
97
  components: /* @__PURE__ */ new Map()
97
98
  };
98
99
  host[MARKS] = shared;
99
- function markReactiveSource(file, line, column, name) {
100
+ shared.classes ??= /* @__PURE__ */ new WeakMap();
101
+ function markReactiveSource(file, line, column, name, self) {
100
102
  const location2 = locate(file, line, column);
101
103
  if (shared.pending && location2.external && !shared.pending.location?.external) return;
102
- shared.pending = { name, location: location2 };
104
+ shared.pending = self && typeof self === "object" ? { name, location: location2, self } : { name, location: location2 };
103
105
  shared.expiring = false;
104
106
  }
107
+ function dropReactiveSource() {
108
+ shared.pending = null;
109
+ }
105
110
  function locate(file, line, column) {
106
111
  const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
107
112
  return external ? { file, line, column, external } : { file, line, column };
@@ -115,13 +120,19 @@ function markComponentSource(name, file, line, column) {
115
120
  }
116
121
  if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
117
122
  }
123
+ function markClassSource(constructor, file, line, column) {
124
+ if (typeof constructor === "function") shared.classes.set(constructor, locate(file, line, column));
125
+ }
126
+ function classSource(constructor) {
127
+ return typeof constructor === "function" ? shared.classes.get(constructor) : void 0;
128
+ }
118
129
  function componentSource(name) {
119
130
  return shared.components.get(name) ?? void 0;
120
131
  }
121
132
  function takeReactiveSource() {
122
133
  const mark = shared.pending;
123
134
  if (mark) {
124
- if (mark.name !== void 0) shared.pending = { location: mark.location };
135
+ if (mark.name !== void 0 || mark.self) shared.pending = { location: mark.location };
125
136
  if (!shared.expiring) {
126
137
  shared.expiring = true;
127
138
  queueMicrotask(() => {
@@ -161,6 +172,7 @@ function holeLocation(accessor) {
161
172
  function installSourceMarker() {
162
173
  if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
163
174
  if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
175
+ if (typeof host[CLASS_MARKER] !== "function") host[CLASS_MARKER] = markClassSource;
164
176
  if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
165
177
  if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
166
178
  }
@@ -606,6 +618,15 @@ var INTERNAL = [
606
618
  "/packages/dom/",
607
619
  "/packages/core/",
608
620
  "/packages/devtools/",
621
+ "/packages/web-components/",
622
+ "/packages/auth/",
623
+ "/packages/oauth2/",
624
+ "/packages/session/",
625
+ "/packages/router/",
626
+ "/packages/head/",
627
+ "/packages/start/",
628
+ "/packages/server/",
629
+ "/packages/template-runtime/",
609
630
  "/@fluixi/",
610
631
  "node_modules",
611
632
  "fluixi-devtools",
@@ -1098,7 +1119,10 @@ var KIND_COLOR = {
1098
1119
  binding: "#5eead4",
1099
1120
  setter: "#fb7185",
1100
1121
  group: "#8a90a2",
1101
- element: "#8a90a2"
1122
+ element: "#8a90a2",
1123
+ "custom-element": "#2dd4bf",
1124
+ module: "#facc15",
1125
+ service: "#60a5fa"
1102
1126
  };
1103
1127
  var kindVar = (kind) => `--fx-kind-${kind}`;
1104
1128
  var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
@@ -1115,7 +1139,11 @@ var TREE_KINDS = /* @__PURE__ */ new Set([
1115
1139
  "handler",
1116
1140
  // A component a plugin recognised as providing a context. Still a tag the author wrote,
1117
1141
  // so leaving it out took it and everything it rendered out of the tree.
1118
- "provider"
1142
+ "provider",
1143
+ // Scopes the runtime reported: what their signals and memos belong to.
1144
+ "custom-element",
1145
+ "module",
1146
+ "service"
1119
1147
  ]);
1120
1148
  function inPageOrder(nodes) {
1121
1149
  const slots = [];
@@ -1138,7 +1166,8 @@ function badgeOf(node) {
1138
1166
  return badges.get(node);
1139
1167
  }
1140
1168
  function componentTree(graph, options = {}) {
1141
- const wanted = (node) => TREE_KINDS.has(node.kind) || options.values === true && node.authored !== false;
1169
+ const held = (node) => node.scope !== void 0 && (node.kind === "state" || node.kind === "memo" || node.kind === "store");
1170
+ const wanted = (node) => TREE_KINDS.has(node.kind) || options.values === true && (node.authored !== false || held(node));
1142
1171
  const tagged = inPageOrder([...graph.values()].filter(wanted).sort((a, b) => a.id - b.id));
1143
1172
  const made = /* @__PURE__ */ new Map();
1144
1173
  for (const node of tagged) {
@@ -1334,7 +1363,8 @@ function createComponentTree(host2, options = {}) {
1334
1363
  const props = listed.length ? [{ key: group, label: "props", kind: node.kind, children: listed, props: true, owner: node.id }] : [];
1335
1364
  const row2 = {
1336
1365
  key: String(node.id),
1337
- label: node.label,
1366
+ // `<fx-counter>` in the graph, where nothing else draws brackets. Here the row adds them.
1367
+ label: node.kind === "custom-element" ? node.label.replace(/^<(.*)>$/, "$1") : node.label,
1338
1368
  kind: node.kind,
1339
1369
  id: node.id,
1340
1370
  children: [...props, ...node.children.map(build2)],
@@ -1453,6 +1483,12 @@ function createComponentTree(host2, options = {}) {
1453
1483
  };
1454
1484
  }
1455
1485
  const rendered = dom.map((node, i) => walk2(node, `${row2.id}:${i}`)).filter((node) => node !== void 0);
1486
+ const self = rendered[0];
1487
+ if (row2.kind === "custom-element" && rendered.length === 1 && self?.kind === "element") {
1488
+ rendered.splice(0, 1, ...self.children);
1489
+ if (self.element) row2.element = self.element;
1490
+ if (self.at) row2.at = self.at;
1491
+ }
1456
1492
  const rest = row2.children.filter((child) => !placed.has(child)).map(expand);
1457
1493
  const lifted = [];
1458
1494
  const spare = [];
@@ -1629,7 +1665,11 @@ var PREFIX = {
1629
1665
  // A plugin's, and it names them itself.
1630
1666
  directive: "d",
1631
1667
  context: "ctx",
1632
- provider: "p"
1668
+ provider: "p",
1669
+ // Scopes are always named by what opened them.
1670
+ "custom-element": "el",
1671
+ module: "mod",
1672
+ service: "svc"
1633
1673
  };
1634
1674
  function readProps(props) {
1635
1675
  const at3 = propsLocations(props);
@@ -1669,6 +1709,10 @@ function observeGraph(options = {}) {
1669
1709
  const boundTo = /* @__PURE__ */ new WeakMap();
1670
1710
  const drivenBy = /* @__PURE__ */ new WeakMap();
1671
1711
  let reaches2 = /* @__PURE__ */ new Map();
1712
+ const SCOPE_KINDS = /* @__PURE__ */ new Set(["custom-element", "module", "service"]);
1713
+ const scopeIds = /* @__PURE__ */ new WeakMap();
1714
+ const scopeOfTarget = /* @__PURE__ */ new WeakMap();
1715
+ const fieldsOf = /* @__PURE__ */ new WeakMap();
1672
1716
  let described = null;
1673
1717
  const rendered = /* @__PURE__ */ new Map();
1674
1718
  const elementsIn = (value) => {
@@ -1765,7 +1809,9 @@ function observeGraph(options = {}) {
1765
1809
  // Mapped, unlike `mark`: these two came off a stack, so they are positions in the
1766
1810
  // compiled file until a host translates them.
1767
1811
  ...site && !site.external ? { usedAt: mapped(site.location) } : {},
1768
- ...site && site.external && !mark?.location ? { source: mapped(site.location) } : {}
1812
+ // Not inside a scope: an element's prop is made by the package for the element, and
1813
+ // the package's line says less than the scope it sits in already does.
1814
+ ...site && site.external && !mark?.location && !report.scope ? { source: mapped(site.location) } : {}
1769
1815
  };
1770
1816
  const owner = ancestor(report.parents);
1771
1817
  if (owner !== void 0) node.createdIn = owner;
@@ -1775,6 +1821,28 @@ function observeGraph(options = {}) {
1775
1821
  node.createdIn = originId;
1776
1822
  if (from.source) node.source = from.source;
1777
1823
  }
1824
+ const scopeId = report.scope ? scopeIds.get(report.scope) : void 0;
1825
+ if (scopeId !== void 0) {
1826
+ node.scope = scopeId;
1827
+ const creator = node.createdIn !== void 0 ? graph.get(node.createdIn) : void 0;
1828
+ const insideAlready = node.createdIn === scopeId || creator?.scope === scopeId;
1829
+ if (!insideAlready) node.createdIn = scopeId;
1830
+ } else if (node.createdIn !== void 0) {
1831
+ const creator = graph.get(node.createdIn);
1832
+ const inherited = creator && SCOPE_KINDS.has(creator.kind) ? creator.id : creator?.scope;
1833
+ if (inherited !== void 0) node.scope = inherited;
1834
+ }
1835
+ const self = mark?.self;
1836
+ if (self) {
1837
+ const list = fieldsOf.get(self);
1838
+ if (list) list.push(id);
1839
+ else fieldsOf.set(self, [id]);
1840
+ const open = scopeOfTarget.get(self);
1841
+ if (open !== void 0 && graph.has(open)) {
1842
+ node.scope = open;
1843
+ node.createdIn = open;
1844
+ }
1845
+ }
1778
1846
  if (kind !== "effect" && typeof handle === "function") {
1779
1847
  const read = handle;
1780
1848
  node.read = () => (0, import_signal2.untrack)(read);
@@ -1910,20 +1978,90 @@ function observeGraph(options = {}) {
1910
1978
  internals.delete(target);
1911
1979
  const id = ids.get(target);
1912
1980
  if (id === void 0) return;
1913
- removed.push({ id, rev: ++revision });
1914
- revs.delete(id);
1915
- if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1916
- history.disposed(id, graph.get(id)?.label);
1917
- graph.delete(id);
1918
- handed.delete(id);
1919
- raws.delete(id);
1920
- cores.delete(id);
1921
- rendered.delete(id);
1922
- setters.delete(id);
1923
- runsAtLastSample.delete(id);
1924
- unnamed.delete(id);
1981
+ forget(id);
1982
+ },
1983
+ scope(scope, info, parent) {
1984
+ dropReactiveSource();
1985
+ const id = seq++;
1986
+ const kind = info.kind === "element" ? "custom-element" : info.kind;
1987
+ const parentId = parent ? scopeIds.get(parent) : void 0;
1988
+ const found = currentCallSite(origin);
1989
+ const site = found && !/\0|__x00__|^virtual:/.test(found.location.file ?? "") ? found : void 0;
1990
+ const node = {
1991
+ id,
1992
+ label: info.name,
1993
+ kind,
1994
+ // The author named the element, the module and the service; none of them is plumbing.
1995
+ authored: true,
1996
+ deps: /* @__PURE__ */ new Set(),
1997
+ writes: /* @__PURE__ */ new Set(),
1998
+ runs: 0,
1999
+ lastPulse: 0,
2000
+ ...info.detail ? { value: info.detail, detail: info.detail } : {},
2001
+ ...parentId !== void 0 ? { scope: parentId, createdIn: parentId } : {}
2002
+ };
2003
+ const target = info.target;
2004
+ if (kind === "custom-element") {
2005
+ const defined = target ? classSource(target.constructor) : void 0;
2006
+ if (defined) node.declaredAt = defined;
2007
+ } else if (site && !site.external) {
2008
+ node.usedAt = mapped(site.location);
2009
+ }
2010
+ scopeIds.set(scope, id);
2011
+ if (target && typeof target === "object" && target.nodeType === 1) {
2012
+ rendered.set(id, [new WeakRef(target)]);
2013
+ }
2014
+ graph.set(id, node);
2015
+ history.created(id, node.createdIn, node.label);
2016
+ if (target && typeof target === "object") {
2017
+ scopeOfTarget.set(target, id);
2018
+ for (const field of fieldsOf.get(target) ?? []) {
2019
+ const made = graph.get(field);
2020
+ if (!made) continue;
2021
+ made.scope = id;
2022
+ made.createdIn = id;
2023
+ revs.set(field, ++revision);
2024
+ }
2025
+ }
2026
+ },
2027
+ scopeEnded(scope) {
2028
+ const id = scopeIds.get(scope);
2029
+ if (id === void 0) return;
2030
+ const ended = /* @__PURE__ */ new Set([id]);
2031
+ for (let grew = true; grew; ) {
2032
+ grew = false;
2033
+ for (const [other, node] of graph) {
2034
+ if (!ended.has(other) && node.scope !== void 0 && ended.has(node.scope)) {
2035
+ ended.add(other);
2036
+ grew = true;
2037
+ }
2038
+ }
2039
+ }
2040
+ for (const gone of ended) forget(gone);
2041
+ },
2042
+ scopeAdopted(scope, parent) {
2043
+ const node = graph.get(scopeIds.get(scope) ?? -1);
2044
+ const parentId = scopeIds.get(parent);
2045
+ if (!node || parentId === void 0 || parentId === node.id) return;
2046
+ node.scope = parentId;
2047
+ node.createdIn = parentId;
2048
+ revs.set(node.id, ++revision);
1925
2049
  }
1926
2050
  });
2051
+ function forget(id) {
2052
+ removed.push({ id, rev: ++revision });
2053
+ revs.delete(id);
2054
+ if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
2055
+ history.disposed(id, graph.get(id)?.label);
2056
+ graph.delete(id);
2057
+ handed.delete(id);
2058
+ raws.delete(id);
2059
+ cores.delete(id);
2060
+ rendered.delete(id);
2061
+ setters.delete(id);
2062
+ runsAtLastSample.delete(id);
2063
+ unnamed.delete(id);
2064
+ }
1927
2065
  const edges = (id, node) => {
1928
2066
  const sources = cores.get(id)?.sources;
1929
2067
  node.deps.clear();
@@ -1943,6 +2081,14 @@ function observeGraph(options = {}) {
1943
2081
  revs.set(id, ++revision);
1944
2082
  };
1945
2083
  const now = Date.now();
2084
+ for (const [id, node] of graph) {
2085
+ if (node.kind !== "custom-element" || node.usedAt) continue;
2086
+ const element = rendered.get(id)?.[0]?.deref();
2087
+ const written = element ? element.__fx_loc : void 0;
2088
+ if (!written?.file) continue;
2089
+ node.usedAt = { file: written.file, line: written.line, column: written.column };
2090
+ revs.set(id, ++revision);
2091
+ }
1946
2092
  const bindings = /* @__PURE__ */ new Map();
1947
2093
  const drives = /* @__PURE__ */ new Map();
1948
2094
  const seen = /* @__PURE__ */ new Map();
@@ -2328,6 +2474,7 @@ function serializeGraph(graph, now = Date.now(), options = {}) {
2328
2474
  declaredAt: node.declaredAt,
2329
2475
  usedAt: node.usedAt,
2330
2476
  createdIn: node.createdIn,
2477
+ scope: node.scope,
2331
2478
  bindings: node.bindings,
2332
2479
  targets: node.targets,
2333
2480
  setAt: node.setAt,
@@ -2365,6 +2512,7 @@ function snapshotToRenderMap(snapshot) {
2365
2512
  declaredAt: node.declaredAt,
2366
2513
  usedAt: node.usedAt,
2367
2514
  createdIn: node.createdIn,
2515
+ scope: node.scope,
2368
2516
  bindings: node.bindings,
2369
2517
  targets: node.targets,
2370
2518
  setAt: node.setAt,
@@ -2517,8 +2665,8 @@ var OVER_NATURAL = 12;
2517
2665
  var MIN_MAX_ZOOM = 8;
2518
2666
  var ABSOLUTE_MAX_ZOOM = 4e3;
2519
2667
  var DRAG_SLOP = 4;
2520
- 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" };
2521
- 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" };
2668
+ 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" };
2669
+ 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" };
2522
2670
  var GROUP_ID = -1;
2523
2671
  var bindingIds = /* @__PURE__ */ new Map();
2524
2672
  var bindingId = (place2) => {
@@ -2779,6 +2927,7 @@ function createGraphView(svg, options = {}) {
2779
2927
  const inProject = (at3) => !!at3?.file && at3.external !== true;
2780
2928
  const machinery = (n) => {
2781
2929
  if (n.authored !== false) return false;
2930
+ if (n.scope !== void 0) return false;
2782
2931
  if (n.kind === "state" || n.kind === "memo" || n.kind === "effect" || n.kind === "context") {
2783
2932
  return true;
2784
2933
  }
@@ -3188,6 +3337,11 @@ ${kindRules((k) => `.fx-node.n-${k}`, "--glow")}
3188
3337
  .fx-node.n-binding { opacity: .72; }
3189
3338
  .fx-node.n-setter { opacity: .72; }
3190
3339
  .fx-node.n-binding .fx-box, .fx-node.n-setter .fx-box { stroke-dasharray: 3 3; }
3340
+ /* An element, module or service: a group its signals and memos belong to, so it reads as one. */
3341
+ .fx-node.n-custom-element .fx-box, .fx-node.n-module .fx-box, .fx-node.n-service .fx-box {
3342
+ stroke-width: 2.2;
3343
+ fill: color-mix(in oklab, var(--glow, #5eead4) 14%, var(--fx-node-bg, #131722));
3344
+ }
3191
3345
  .fx-node.n-external { opacity: .55; }
3192
3346
  .fx-node.n-external .fx-box { stroke-dasharray: 3 2; }
3193
3347
  .fx-box { fill: var(--fx-node-bg, #131722); stroke: var(--glow, #5eead4); stroke-width: 1.4; stroke-opacity: calc(.5 + var(--pulse,0) * .5); }
@@ -3646,7 +3800,7 @@ function createInspector(host2, options = {}) {
3646
3800
  // no locations at all, and an empty line there reads as missing data.
3647
3801
  (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
3648
3802
  // else, and only worth a line when it is.
3649
- (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
3803
+ (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
3650
3804
  // was created, and this is the line that asked for it.
3651
3805
  (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
3652
3806
  // the stack had nothing to offer either. Saying so beats a card that just sits there.
@@ -4059,9 +4213,12 @@ var FILTERABLE_KINDS = [
4059
4213
  "router",
4060
4214
  "handler",
4061
4215
  "context",
4062
- "directive"
4216
+ "directive",
4217
+ "custom-element",
4218
+ "module",
4219
+ "service"
4063
4220
  ];
4064
- var LABEL = { handler: "route" };
4221
+ var LABEL = { handler: "route", "custom-element": "custom element" };
4065
4222
  function createKindFilter(host2, options) {
4066
4223
  const doc = host2.ownerDocument;
4067
4224
  const kinds = options.kinds ?? FILTERABLE_KINDS;