mutts 1.0.8 → 1.0.10

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 (195) hide show
  1. package/README.md +77 -35
  2. package/dist/browser.cjs +2144 -5355
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1655
  5. package/dist/browser.dev.cjs +110 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +20 -107
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-BU_IfxYD.cjs} +19 -107
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  15. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  16. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-3PrbVAbB.cjs} +7 -12
  17. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  18. package/dist/chunks/index-CaaQQlPJ.esm.js +3174 -0
  19. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  20. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  21. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  22. package/dist/chunks/proxy-Dtg-bJ3T.cjs +3763 -0
  23. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  24. package/dist/chunks/proxy-r7lARftl.esm.js +3701 -0
  25. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  26. package/dist/debug.cjs +980 -0
  27. package/dist/debug.cjs.map +1 -0
  28. package/dist/debug.d.ts +208 -0
  29. package/dist/debug.esm.js +950 -0
  30. package/dist/debug.esm.js.map +1 -0
  31. package/dist/devtools/manifest.json +1 -1
  32. package/dist/devtools/panel.html +1 -1
  33. package/dist/devtools/panel.js +107 -94
  34. package/dist/devtools/panel.js.map +1 -1
  35. package/dist/index.d.ts +1447 -13
  36. package/dist/mutts.umd.js +7167 -1
  37. package/dist/mutts.umd.js.map +1 -1
  38. package/dist/mutts.umd.min.js +1 -1
  39. package/dist/mutts.umd.min.js.map +1 -1
  40. package/dist/node.cjs +70 -66
  41. package/dist/node.cjs.map +1 -1
  42. package/dist/node.d.ts +2 -1
  43. package/dist/node.dev.cjs +110 -0
  44. package/dist/node.dev.cjs.map +1 -0
  45. package/dist/node.dev.d.ts +2 -0
  46. package/dist/node.dev.esm.js +6 -0
  47. package/dist/node.dev.esm.js.map +1 -0
  48. package/dist/node.esm.js +4 -103
  49. package/dist/node.esm.js.map +1 -1
  50. package/dist/{reactive/types.d.ts → types-W5vD6m2n.d.ts} +186 -93
  51. package/docs/ai/api-reference.md +12 -19
  52. package/docs/ai/manual.md +521 -17
  53. package/docs/events.md +57 -2
  54. package/docs/flavored.md +210 -0
  55. package/docs/reactive/advanced.md +314 -5
  56. package/docs/reactive/attend.md +142 -0
  57. package/docs/reactive/collections.md +76 -39
  58. package/docs/reactive/core.md +100 -14
  59. package/docs/reactive/debugging.md +123 -18
  60. package/docs/reactive/error-handling.md +121 -0
  61. package/docs/reactive/resource.md +125 -0
  62. package/docs/reactive/scan.md +247 -1
  63. package/docs/reactive.md +4 -1
  64. package/docs/std-decorators.md +102 -51
  65. package/docs/utils.md +70 -0
  66. package/docs/zone.md +3 -7
  67. package/package.json +64 -30
  68. package/dist/async/browser.d.ts +0 -2
  69. package/dist/async/browser.d.ts.map +0 -1
  70. package/dist/async/index.d.ts +0 -18
  71. package/dist/async/index.d.ts.map +0 -1
  72. package/dist/async/node.d.ts +0 -2
  73. package/dist/async/node.d.ts.map +0 -1
  74. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  75. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  76. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  77. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  78. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  79. package/dist/decorator.d.ts +0 -106
  80. package/dist/decorator.d.ts.map +0 -1
  81. package/dist/destroyable.d.ts +0 -87
  82. package/dist/destroyable.d.ts.map +0 -1
  83. package/dist/entry-browser.d.ts +0 -3
  84. package/dist/entry-browser.d.ts.map +0 -1
  85. package/dist/entry-node.d.ts +0 -3
  86. package/dist/entry-node.d.ts.map +0 -1
  87. package/dist/eventful.d.ts +0 -18
  88. package/dist/eventful.d.ts.map +0 -1
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/indexable.d.ts +0 -243
  91. package/dist/indexable.d.ts.map +0 -1
  92. package/dist/introspection.d.ts +0 -27
  93. package/dist/introspection.d.ts.map +0 -1
  94. package/dist/iterableWeak.d.ts +0 -53
  95. package/dist/iterableWeak.d.ts.map +0 -1
  96. package/dist/mixins.d.ts +0 -25
  97. package/dist/mixins.d.ts.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -20
  99. package/dist/promiseChain.d.ts.map +0 -1
  100. package/dist/reactive/array.d.ts +0 -49
  101. package/dist/reactive/array.d.ts.map +0 -1
  102. package/dist/reactive/buffer.d.ts +0 -44
  103. package/dist/reactive/buffer.d.ts.map +0 -1
  104. package/dist/reactive/change.d.ts +0 -29
  105. package/dist/reactive/change.d.ts.map +0 -1
  106. package/dist/reactive/debug.d.ts +0 -111
  107. package/dist/reactive/debug.d.ts.map +0 -1
  108. package/dist/reactive/deep-touch.d.ts +0 -28
  109. package/dist/reactive/deep-touch.d.ts.map +0 -1
  110. package/dist/reactive/deep-watch-state.d.ts +0 -25
  111. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  112. package/dist/reactive/deep-watch.d.ts +0 -19
  113. package/dist/reactive/deep-watch.d.ts.map +0 -1
  114. package/dist/reactive/effect-context.d.ts +0 -7
  115. package/dist/reactive/effect-context.d.ts.map +0 -1
  116. package/dist/reactive/effects.d.ts +0 -151
  117. package/dist/reactive/effects.d.ts.map +0 -1
  118. package/dist/reactive/index.d.ts +0 -20
  119. package/dist/reactive/index.d.ts.map +0 -1
  120. package/dist/reactive/interface.d.ts +0 -64
  121. package/dist/reactive/interface.d.ts.map +0 -1
  122. package/dist/reactive/map.d.ts +0 -30
  123. package/dist/reactive/map.d.ts.map +0 -1
  124. package/dist/reactive/memoize.d.ts +0 -5
  125. package/dist/reactive/memoize.d.ts.map +0 -1
  126. package/dist/reactive/non-reactive-state.d.ts +0 -9
  127. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  128. package/dist/reactive/non-reactive.d.ts +0 -11
  129. package/dist/reactive/non-reactive.d.ts.map +0 -1
  130. package/dist/reactive/project.d.ts +0 -41
  131. package/dist/reactive/project.d.ts.map +0 -1
  132. package/dist/reactive/proxy-state.d.ts +0 -8
  133. package/dist/reactive/proxy-state.d.ts.map +0 -1
  134. package/dist/reactive/proxy.d.ts +0 -23
  135. package/dist/reactive/proxy.d.ts.map +0 -1
  136. package/dist/reactive/record.d.ts +0 -116
  137. package/dist/reactive/record.d.ts.map +0 -1
  138. package/dist/reactive/register.d.ts +0 -64
  139. package/dist/reactive/register.d.ts.map +0 -1
  140. package/dist/reactive/registry.d.ts +0 -20
  141. package/dist/reactive/registry.d.ts.map +0 -1
  142. package/dist/reactive/set.d.ts +0 -28
  143. package/dist/reactive/set.d.ts.map +0 -1
  144. package/dist/reactive/tracking.d.ts +0 -7
  145. package/dist/reactive/tracking.d.ts.map +0 -1
  146. package/dist/reactive/types.d.ts.map +0 -1
  147. package/dist/std-decorators.d.ts +0 -50
  148. package/dist/std-decorators.d.ts.map +0 -1
  149. package/dist/utils.d.ts +0 -49
  150. package/dist/utils.d.ts.map +0 -1
  151. package/dist/zone.d.ts +0 -40
  152. package/dist/zone.d.ts.map +0 -1
  153. package/docs/reactive/project.md +0 -93
  154. package/src/async/browser.ts +0 -319
  155. package/src/async/index.ts +0 -23
  156. package/src/async/node.ts +0 -104
  157. package/src/decorator.ts +0 -272
  158. package/src/destroyable.ts +0 -199
  159. package/src/entry-browser.ts +0 -5
  160. package/src/entry-node.ts +0 -5
  161. package/src/eventful.ts +0 -77
  162. package/src/index.d.ts +0 -12
  163. package/src/index.ts +0 -64
  164. package/src/indexable.ts +0 -526
  165. package/src/introspection.ts +0 -59
  166. package/src/iterableWeak.ts +0 -233
  167. package/src/mixins.ts +0 -123
  168. package/src/promiseChain.ts +0 -110
  169. package/src/reactive/array.ts +0 -507
  170. package/src/reactive/buffer.ts +0 -168
  171. package/src/reactive/change.ts +0 -138
  172. package/src/reactive/debug.ts +0 -517
  173. package/src/reactive/deep-touch.ts +0 -268
  174. package/src/reactive/deep-watch-state.ts +0 -82
  175. package/src/reactive/deep-watch.ts +0 -168
  176. package/src/reactive/effect-context.ts +0 -18
  177. package/src/reactive/effects.ts +0 -1382
  178. package/src/reactive/index.ts +0 -75
  179. package/src/reactive/interface.ts +0 -210
  180. package/src/reactive/map.ts +0 -158
  181. package/src/reactive/memoize.ts +0 -163
  182. package/src/reactive/non-reactive-state.ts +0 -49
  183. package/src/reactive/non-reactive.ts +0 -43
  184. package/src/reactive/project.ts +0 -453
  185. package/src/reactive/proxy-state.ts +0 -27
  186. package/src/reactive/proxy.ts +0 -264
  187. package/src/reactive/record.ts +0 -196
  188. package/src/reactive/register.ts +0 -419
  189. package/src/reactive/registry.ts +0 -59
  190. package/src/reactive/set.ts +0 -130
  191. package/src/reactive/tracking.ts +0 -44
  192. package/src/reactive/types.ts +0 -440
  193. package/src/std-decorators.ts +0 -217
  194. package/src/utils.ts +0 -301
  195. package/src/zone.ts +0 -138
@@ -0,0 +1,950 @@
1
+ import { a5 as getRoot, au as effectMarker, B as getActiveEffect, av as getEffectNode, T as reactive, u as effect, U as options, aw as setDebugHooks, at as effectToReactiveObjects, ax as allProps } from './chunks/proxy-r7lARftl.esm.js';
2
+
3
+ const lineageObjects = new WeakSet();
4
+ /**
5
+ * Checks if an object is a lineage object
6
+ */
7
+ function isLineage(obj) {
8
+ return obj && typeof obj === 'object' && lineageObjects.has(obj);
9
+ }
10
+ /**
11
+ * Parses a single stack line into a structured frame.
12
+ * @param line - A line from Error.stack
13
+ */
14
+ function parseStackLine(line) {
15
+ // Node.js format: " at functionName (file:line:column)"
16
+ // or " at file:line:column"
17
+ const nodeMatch = line.match(/^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?$/);
18
+ // Browser format (Firefox/Safari): "functionName@file:line:column" or "file:line:column"
19
+ // Chrome/Edge use Node-like format but without "at" sometimes or with different prefixes
20
+ const browserMatch = line.match(/(?:(.+?)(?:\@|\(?))?(?:(.+?):(\d+):(\d+))(?:\@|\)?)$/);
21
+ const match = nodeMatch || browserMatch;
22
+ if (!match)
23
+ return null;
24
+ const [, functionName = 'anonymous', fileName, lineNumber, columnNumber] = match;
25
+ return {
26
+ functionName: functionName.trim(),
27
+ fileName,
28
+ lineNumber: parseInt(lineNumber, 10),
29
+ columnNumber: parseInt(columnNumber, 10),
30
+ raw: line.trim(),
31
+ };
32
+ }
33
+ let internalFile;
34
+ /**
35
+ * Gets the current call stack
36
+ * @param skipFrames - Number of frames to skip
37
+ * @param error - Optional error to use as source of stack
38
+ */
39
+ function getStackFrame(error = new Error()) {
40
+ if (!error.stack)
41
+ return [];
42
+ const lines = error.stack.split('\n');
43
+ const lastLine = lines.findIndex((line) => line.includes(effectMarker.enter));
44
+ if (lastLine !== -1)
45
+ lines.splice(lastLine);
46
+ const firstLine = lines.findLastIndex((line) => line.includes(effectMarker.leave));
47
+ if (firstLine !== -1)
48
+ lines.splice(0, firstLine + 1);
49
+ else {
50
+ // Dynamically identify the library's internal files if not already done
51
+ if (!internalFile && lines[1]) {
52
+ const selfFrame = parseStackLine(lines[1]);
53
+ if (selfFrame) {
54
+ internalFile = selfFrame.fileName;
55
+ }
56
+ }
57
+ // Determine the "base" directory of the library to skip other internal files
58
+ // We look for "src" or "dist" to be more specific than just the project root
59
+ const srcIndex = internalFile ? internalFile.lastIndexOf('/src/') : -1;
60
+ const distIndex = internalFile ? internalFile.lastIndexOf('/dist/') : -1;
61
+ internalFile ? (srcIndex !== -1 ? internalFile.substring(0, srcIndex + 5) :
62
+ (distIndex !== -1 ? internalFile.substring(0, distIndex + 6) :
63
+ internalFile.substring(0, internalFile.lastIndexOf('/') + 1))) : undefined;
64
+ let l;
65
+ for (l = 1; l < lines.length; l++) {
66
+ const frame = parseStackLine(lines[l]);
67
+ if (!frame)
68
+ continue;
69
+ // Robust skipping: if we are still in the internal area, skip it.
70
+ const isInternal = /Lineage$/.test(frame.functionName) ||
71
+ frame.functionName === 'eval' ||
72
+ [`getStackFrame`, `captureLineage`].includes(frame.functionName);
73
+ if (!isInternal)
74
+ break;
75
+ }
76
+ lines.splice(0, l);
77
+ }
78
+ return lines.map(parseStackLine).filter(Boolean);
79
+ }
80
+ /**
81
+ * Traces the lineage of the current execution through nested effects
82
+ * @param effect - Starting effect (defaults to active effect)
83
+ */
84
+ function getLineage(effect) {
85
+ const currentEffect = effect ?? getActiveEffect();
86
+ const currentStack = getStackFrame(); // Robustly skips internal mutts frames
87
+ const segments = [];
88
+ if (!currentEffect) {
89
+ segments.push({
90
+ effect: undefined,
91
+ effectName: 'root',
92
+ stack: filterNodeModules(currentStack),
93
+ });
94
+ return segments;
95
+ }
96
+ let current = currentEffect;
97
+ let lastStack = currentStack;
98
+ while (current) {
99
+ const rootFn = getRoot(current);
100
+ // Too aggressive for now
101
+ //if(!rootFn.name) debugger
102
+ const filteredStack = filterNodeModules(lastStack);
103
+ segments.push({
104
+ effect: rootFn,
105
+ effectName: rootFn.name || 'anonymous',
106
+ stack: filteredStack,
107
+ });
108
+ // Move to parent
109
+ const node = getEffectNode(current);
110
+ const parent = node.parent;
111
+ const creationStack = node.creationStack;
112
+ if (parent) {
113
+ current = parent;
114
+ lastStack = creationStack ?? [];
115
+ }
116
+ else if (creationStack) {
117
+ segments.push({
118
+ effect: undefined,
119
+ effectName: 'root',
120
+ stack: filterNodeModules(creationStack),
121
+ });
122
+ break;
123
+ }
124
+ else {
125
+ break;
126
+ }
127
+ }
128
+ return segments;
129
+ }
130
+ /**
131
+ * Filters out node_modules frames and groups them
132
+ * @param frames - Array of stack frames
133
+ */
134
+ function filterNodeModules(frames) {
135
+ const result = [];
136
+ let inNodeModules = false;
137
+ for (const frame of frames) {
138
+ const isNodeModule = frame.fileName.includes('/node_modules/'); //|| frame.fileName.includes('/dist/')
139
+ if (isNodeModule && !inNodeModules) {
140
+ // Start of node_modules block
141
+ inNodeModules = true;
142
+ result.push({
143
+ functionName: '...node_modules...',
144
+ fileName: '[filtered]',
145
+ lineNumber: 0,
146
+ columnNumber: 0,
147
+ raw: ' at ...node_modules...'
148
+ });
149
+ }
150
+ else if (!isNodeModule && inNodeModules) {
151
+ // End of node_modules block
152
+ inNodeModules = false;
153
+ result.push(frame);
154
+ }
155
+ else if (!isNodeModule) {
156
+ // Regular frame
157
+ result.push(frame);
158
+ }
159
+ // Skip frames inside node_modules
160
+ }
161
+ return result;
162
+ }
163
+ /**
164
+ * Formats lineage segments into a single stack-like string
165
+ * @param segments - Lineage segments
166
+ */
167
+ function formatLineage(segments) {
168
+ const result = [];
169
+ for (let i = 0; i < segments.length; i++) {
170
+ const segment = segments[i];
171
+ if (i > 0) {
172
+ const triggerEffect = segments[i - 1].effect;
173
+ const triggerName = triggerEffect ? (getRoot(triggerEffect).name || 'anonymous') : 'root';
174
+ result.push(` --- effect: ${triggerName} ---`);
175
+ }
176
+ for (const frame of segment.stack) {
177
+ result.push(` ${frame.raw}`);
178
+ }
179
+ }
180
+ return result.join('\n');
181
+ }
182
+ /**
183
+ * Logs lineage segments to console with grouping
184
+ * @param segments - Lineage segments
185
+ */
186
+ function logLineage(segments) {
187
+ console.groupCollapsed(`🦴 Effect Lineage Trace (${segments.length} segment${segments.length === 1 ? '' : 's'})`);
188
+ for (let i = 0; i < segments.length; i++) {
189
+ const segment = segments[i];
190
+ // Add segment header
191
+ const isLast = i === segments.length - 1;
192
+ const prefix = i === 0 ? '📍' : isLast ? '└─' : '├─';
193
+ console.groupCollapsed(`${prefix} Effect:`, segment.effect ? segment.effect.name || 'anonymous' : 'root');
194
+ // Add stack frames
195
+ for (let j = 0; j < segment.stack.length; j++) {
196
+ const frame = segment.stack[j];
197
+ const isLastFrame = j === segment.stack.length - 1;
198
+ const framePrefix = isLastFrame ? '└─' : '├─';
199
+ if (frame.functionName === '...node_modules...') {
200
+ console.log(`%c${framePrefix} ${frame.functionName}`, 'color: #888; font-style: italic;');
201
+ }
202
+ else {
203
+ const fnStyle = frame.functionName === 'anonymous' ? 'color: #888;' : 'color: #1a7f37; font-weight: bold;';
204
+ const fileStyle = 'color: #0550ae;';
205
+ console.log(`%c${framePrefix} %c${frame.functionName} %c(${frame.fileName}:${frame.lineNumber}:${frame.columnNumber})`, 'color: #888;', fnStyle, fileStyle);
206
+ }
207
+ }
208
+ console.groupEnd();
209
+ }
210
+ console.groupEnd();
211
+ }
212
+ /**
213
+ * Captures and logs lineage to console
214
+ */
215
+ function captureNodeLineage() {
216
+ logLineage(getLineage());
217
+ }
218
+ /**
219
+ * Custom formatter for Chrome DevTools to render lineage data nicely.
220
+ */
221
+ const lineageFormatter = {
222
+ header: (obj) => {
223
+ if (isLineage(obj)) {
224
+ // Try to detect DevTools theme - default to dark colors if uncertain
225
+ const isDark = typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches;
226
+ const headerColor = isDark ? '#cd9d5d' : '#704214';
227
+ return [
228
+ 'span',
229
+ { style: `color: ${headerColor}; font-weight: bold;` },
230
+ `🦴 Effect Lineage (${obj.segments.length} segments)`,
231
+ ];
232
+ }
233
+ return null;
234
+ },
235
+ hasBody: (obj) => isLineage(obj),
236
+ body: (obj) => {
237
+ if (!isLineage(obj))
238
+ return null;
239
+ const segments = obj.segments;
240
+ // Try to detect DevTools theme
241
+ const isDark = typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches;
242
+ const colors = isDark ? {
243
+ frameText: '#ccc',
244
+ functionName: '#ffffff',
245
+ segmentBg: '#2d2d2d',
246
+ } : {
247
+ frameText: '#555',
248
+ functionName: '#222',
249
+ segmentBg: '#eee',
250
+ };
251
+ const children = segments.map((segment, i) => {
252
+ const frames = segment.stack.map((frame) => [
253
+ 'div',
254
+ { style: `margin-left: 20px; color: ${colors.frameText}; font-family: monospace; font-size: 11px;` },
255
+ ['span', { style: `color: ${colors.functionName};` }, `at ${frame.functionName} `],
256
+ ['span', {}, `${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`],
257
+ ]);
258
+ segment.effect;
259
+ const segmentHeader = [
260
+ 'div',
261
+ { style: `margin-top: 5px; padding: 2px 5px; background: ${colors.segmentBg}; border-radius: 3px; font-weight: bold;` },
262
+ i === 0 ? '📍 Current: ' : '↖ Effect: ',
263
+ segment.effect?.name || 'anonymous',
264
+ segment.effect ? ['object', { object: segment.effect }] : 'root',
265
+ ];
266
+ return ['div', {}, segmentHeader, ...frames];
267
+ });
268
+ return ['div', { style: 'padding: 5px; line-height: 1.4;' }, ...children];
269
+ },
270
+ };
271
+ /**
272
+ * Wraps lineage data in a way that the Chrome Formatter can recognize.
273
+ */
274
+ function wrapLineageForDebug(segments) {
275
+ const lineage = {
276
+ segments,
277
+ toString: () => formatLineage(segments ?? getLineage()),
278
+ };
279
+ lineageObjects.add(lineage);
280
+ return lineage;
281
+ }
282
+
283
+ /**
284
+ * Displays a floating reactive panel in the browser showing the lineage of triggered effects.
285
+ */
286
+ function showLineagePanel() {
287
+ if (typeof document === 'undefined')
288
+ return;
289
+ // Detect color scheme preference
290
+ const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
291
+ // Color schemes
292
+ const colors = isDarkMode ? {
293
+ panelBg: 'rgba(30, 30, 30, 0.95)',
294
+ panelBorder: '#444',
295
+ headerBg: '#8B4513',
296
+ headerText: '#ffffff',
297
+ closeBtn: '#ffffff',
298
+ placeholderText: '#999',
299
+ segmentBorder: '#8B4513',
300
+ titleText: '#ffffff',
301
+ frameText: '#ccc',
302
+ atText: '#888',
303
+ linkText: '#58a6ff',
304
+ } : {
305
+ panelBg: 'rgba(255, 255, 255, 0.95)',
306
+ panelBorder: '#ddd',
307
+ headerBg: '#704214',
308
+ headerText: '#ffffff',
309
+ closeBtn: '#ffffff',
310
+ placeholderText: '#666',
311
+ segmentBorder: '#704214',
312
+ titleText: '#222',
313
+ frameText: '#555',
314
+ atText: '#999',
315
+ linkText: '#005cc5',
316
+ };
317
+ const state = reactive({
318
+ activeLineage: [],
319
+ isVisible: true,
320
+ });
321
+ // onEffectTrigger tracks "this effect" (here nothing) — not useful for a global panel.
322
+ // Would need zone-based events to track all triggers. Panel is cosmetic/experimental (260206).
323
+ // UI Creation
324
+ const panel = document.createElement('div');
325
+ panel.id = 'mutts-lineage-panel';
326
+ Object.assign(panel.style, {
327
+ position: 'fixed',
328
+ bottom: '20px',
329
+ right: '20px',
330
+ width: '400px',
331
+ maxHeight: '80vh',
332
+ backgroundColor: colors.panelBg,
333
+ backdropFilter: 'blur(10px)',
334
+ border: `1px solid ${colors.panelBorder}`,
335
+ borderRadius: '12px',
336
+ boxShadow: '0 10px 25px rgba(0,0,0,0.15)',
337
+ zIndex: '999999',
338
+ fontFamily: 'Inter, system-ui, sans-serif',
339
+ fontSize: '12px',
340
+ display: 'flex',
341
+ flexDirection: 'column',
342
+ overflow: 'hidden',
343
+ transition: 'opacity 0.3s, transform 0.3s',
344
+ });
345
+ const header = document.createElement('div');
346
+ Object.assign(header.style, {
347
+ padding: '12px 16px',
348
+ background: colors.headerBg,
349
+ color: colors.headerText,
350
+ fontWeight: '600',
351
+ display: 'flex',
352
+ justifyContent: 'space-between',
353
+ alignItems: 'center',
354
+ cursor: 'grab',
355
+ });
356
+ header.innerHTML = '<span><span style="font-size: 14px; margin-right: 5px;">🦴</span> Mutts Lineage</span>';
357
+ const closeBtn = document.createElement('button');
358
+ closeBtn.innerText = '×';
359
+ Object.assign(closeBtn.style, {
360
+ background: 'none',
361
+ border: 'none',
362
+ color: colors.closeBtn,
363
+ fontSize: '20px',
364
+ cursor: 'pointer',
365
+ padding: '0 5px',
366
+ });
367
+ closeBtn.onclick = () => {
368
+ state.isVisible = false;
369
+ };
370
+ header.appendChild(closeBtn);
371
+ panel.appendChild(header);
372
+ const content = document.createElement('div');
373
+ Object.assign(content.style, {
374
+ padding: '16px',
375
+ overflowY: 'auto',
376
+ flex: '1',
377
+ });
378
+ panel.appendChild(content);
379
+ document.body.appendChild(panel);
380
+ // Reactivity: Update Visibility
381
+ effect(() => {
382
+ panel.style.opacity = state.isVisible ? '1' : '0';
383
+ panel.style.pointerEvents = state.isVisible ? 'all' : 'none';
384
+ panel.style.transform = state.isVisible ? 'translateY(0)' : 'translateY(20px)';
385
+ });
386
+ // Reactivity: Update Content
387
+ effect(() => {
388
+ if (state.activeLineage.length === 0) {
389
+ content.innerHTML = `<div style="color: ${colors.placeholderText}; font-style: italic; text-align: center; margin-top: 20px;">No effect triggered yet...</div>`;
390
+ return;
391
+ }
392
+ content.innerHTML = '';
393
+ state.activeLineage.forEach((segment, i) => {
394
+ const segDiv = document.createElement('div');
395
+ Object.assign(segDiv.style, {
396
+ marginBottom: '16px',
397
+ borderLeft: `2px solid ${colors.segmentBorder}`,
398
+ paddingLeft: '12px',
399
+ });
400
+ const title = document.createElement('div');
401
+ Object.assign(title.style, {
402
+ fontWeight: 'bold',
403
+ marginBottom: '6px',
404
+ color: colors.titleText,
405
+ });
406
+ const effectName = segment.effect ? (getRoot(segment.effect).name || 'anonymous') : 'root';
407
+ title.innerText = i === 0 ? `📍 Current: ${effectName}` : `↖ Effect: ${effectName}`;
408
+ segDiv.appendChild(title);
409
+ segment.stack.forEach(frame => {
410
+ const frameDiv = document.createElement('div');
411
+ Object.assign(frameDiv.style, {
412
+ color: colors.frameText,
413
+ marginBottom: '3px',
414
+ fontSize: '11px',
415
+ whiteSpace: 'nowrap',
416
+ overflow: 'hidden',
417
+ textOverflow: 'ellipsis',
418
+ cursor: 'pointer',
419
+ });
420
+ frameDiv.title = frame.raw;
421
+ frameDiv.innerHTML = `<span style="color: ${colors.atText}">at</span> ${frame.functionName} <span style="color: ${colors.linkText}; text-decoration: underline;">(${frame.fileName.split('/').pop()}:${frame.lineNumber})</span>`;
422
+ segDiv.appendChild(frameDiv);
423
+ });
424
+ content.appendChild(segDiv);
425
+ });
426
+ });
427
+ }
428
+
429
+ /**
430
+ * Debug utilities for the reactivity system
431
+ * - Captures effect metadata (names, parent relationships)
432
+ * - Records cause → consequence edges with object/prop labels
433
+ * - Provides graph data for tooling (DevTools panel, etc.)
434
+ */
435
+ /**
436
+ * Log an error with detailed context if error logging is enabled
437
+ */
438
+ function logError(error, context, effect) {
439
+ if (!options.introspection?.logErrors)
440
+ return;
441
+ const contextStr = context === 'throw' ? '🔴 Thrown' : '🟡 Caught';
442
+ console.group(`${contextStr} Error: ${error.message}`);
443
+ console.error('Error:', error);
444
+ // Try to get effect name if available
445
+ if (effect && typeof effect === 'object' && effect.name) {
446
+ console.log('Effect:', effect.name);
447
+ }
448
+ console.groupEnd();
449
+ }
450
+ // Log initialization for debugging
451
+ const EXTERNAL_SOURCE = Symbol('external-source');
452
+ let devtoolsEnabled = false;
453
+ // Registry for debugging (populated lazily when DevTools are enabled)
454
+ const debugEffectRegistry = new Set();
455
+ const debugObjectRegistry = new Set();
456
+ // Human-friendly names
457
+ const effectNames = new WeakMap();
458
+ const objectNames = new WeakMap();
459
+ let effectCounter = 0;
460
+ let objectCounter = 0;
461
+ const triggerGraph = new Map();
462
+ function ensureEffectName(effect) {
463
+ let name = effectNames.get(effect);
464
+ if (!name) {
465
+ const root = getRoot(effect);
466
+ name = root?.name?.trim() || `effect_${++effectCounter}`;
467
+ }
468
+ effectNames.set(effect, name);
469
+ return name;
470
+ }
471
+ function ensureObjectName(obj) {
472
+ let name = objectNames.get(obj);
473
+ if (!name) {
474
+ // Try Symbol.toStringTag first (standard way to customize object string representation)
475
+ const toStringTag = obj?.[Symbol.toStringTag];
476
+ const ctorName = obj?.constructor?.name;
477
+ const base = toStringTag || (ctorName && ctorName !== 'Object' ? ctorName : 'object');
478
+ name = `${base}_${++objectCounter}`;
479
+ objectNames.set(obj, name);
480
+ }
481
+ return name;
482
+ }
483
+ function describeProp(obj, prop) {
484
+ const objectName = ensureObjectName(obj);
485
+ if (prop === allProps)
486
+ return `${objectName}.*`;
487
+ if (typeof prop === 'symbol')
488
+ return `${objectName}.${prop.description ?? prop.toString()}`;
489
+ return `${objectName}.${String(prop)}`;
490
+ }
491
+ function addEffectToRegistry(effect) {
492
+ if (!effect || debugEffectRegistry.has(effect))
493
+ return;
494
+ debugEffectRegistry.add(effect);
495
+ const deps = effectToReactiveObjects.get(effect);
496
+ if (deps) {
497
+ for (const obj of deps) {
498
+ documentObject(obj);
499
+ }
500
+ }
501
+ }
502
+ function documentObject(obj) {
503
+ if (!debugObjectRegistry.has(obj)) {
504
+ dbRegisterObject(obj);
505
+ }
506
+ }
507
+ function dbRegisterObject(obj) {
508
+ debugObjectRegistry.add(obj);
509
+ ensureObjectName(obj);
510
+ }
511
+ function ensureParentChains(effects) {
512
+ const queue = Array.from(effects);
513
+ for (let i = 0; i < queue.length; i++) {
514
+ const effect = queue[i];
515
+ const parent = getEffectNode(effect).parent;
516
+ if (parent && !effects.has(parent)) {
517
+ effects.add(parent);
518
+ queue.push(parent);
519
+ }
520
+ }
521
+ }
522
+ function ensureTriggerContainers(source) {
523
+ let targetMap = triggerGraph.get(source);
524
+ if (!targetMap) {
525
+ targetMap = new Map();
526
+ triggerGraph.set(source, targetMap);
527
+ }
528
+ return targetMap;
529
+ }
530
+ function ensureTriggerRecord(source, target, label, obj, prop, evolution) {
531
+ const targetMap = ensureTriggerContainers(source);
532
+ let labelMap = targetMap.get(target);
533
+ if (!labelMap) {
534
+ labelMap = new Map();
535
+ targetMap.set(target, labelMap);
536
+ }
537
+ let record = labelMap.get(label);
538
+ if (!record) {
539
+ record = { label, object: obj, prop, evolution, count: 0, lastTriggered: Date.now() };
540
+ labelMap.set(label, record);
541
+ }
542
+ return record;
543
+ }
544
+ /**
545
+ * Assign a debug-friendly name to an effect (shown in DevTools)
546
+ */
547
+ function setEffectName(effect, name) {
548
+ effectNames.set(effect, name);
549
+ }
550
+ /**
551
+ * Assign a debug-friendly name to a reactive object
552
+ */
553
+ function setObjectName(obj, name) {
554
+ objectNames.set(obj, name);
555
+ debugObjectRegistry.add(obj);
556
+ }
557
+ /**
558
+ * Register an effect so it appears in the DevTools graph
559
+ */
560
+ function registerEffectForDebug(effect) {
561
+ if (!effect || !devtoolsEnabled)
562
+ return;
563
+ addEffectToRegistry(effect);
564
+ }
565
+ /**
566
+ * Register a reactive object so it appears in the DevTools graph
567
+ */
568
+ function registerObjectForDebug(obj) {
569
+ if (!devtoolsEnabled)
570
+ return;
571
+ documentObject(obj);
572
+ }
573
+ /**
574
+ * Records a cause → consequence relationship between effects.
575
+ * @param source - The effect performing the write (undefined if external/user input)
576
+ * @param target - The effect that re-ran because of the write
577
+ * @param obj - The reactive object that changed
578
+ * @param prop - The property that changed
579
+ * @param evolution - The type of change (set/add/del/bunch)
580
+ */
581
+ function recordTriggerLink(source, target, obj, prop, evolution) {
582
+ if (options.introspection?.enableHistory) {
583
+ addToMutationHistory(source, target, obj, prop, evolution);
584
+ }
585
+ if (!devtoolsEnabled)
586
+ return;
587
+ addEffectToRegistry(target);
588
+ if (source)
589
+ addEffectToRegistry(source);
590
+ const descriptor = describeProp(obj, prop);
591
+ const record = ensureTriggerRecord(source ?? EXTERNAL_SOURCE, target, descriptor, obj, prop, evolution);
592
+ record.count += 1;
593
+ record.lastTriggered = Date.now();
594
+ documentObject(obj);
595
+ }
596
+ /**
597
+ * Traces back the chain of triggers that led to a specific effect
598
+ * @param effect The effect to trace back
599
+ * @param limit Max depth
600
+ */
601
+ function getTriggerChain(effect, limit = 5) {
602
+ const chain = [];
603
+ let current = effect;
604
+ for (let i = 0; i < limit; i++) {
605
+ // Find who triggered 'current'
606
+ // We need to reverse search the triggerGraph (source -> target)
607
+ // This is expensive O(Edges) but okay for error reporting
608
+ let foundSource;
609
+ let foundReason = '';
610
+ search: for (const [source, targetMap] of triggerGraph) {
611
+ for (const [target, labelMap] of targetMap) {
612
+ if (target === current) {
613
+ // Found a source! Use the most recent trigger record
614
+ let lastTime = 0;
615
+ for (const record of labelMap.values()) {
616
+ if (record.lastTriggered > lastTime) {
617
+ lastTime = record.lastTriggered;
618
+ foundReason = record.label;
619
+ foundSource = source === EXTERNAL_SOURCE ? undefined : source;
620
+ }
621
+ }
622
+ if (foundSource || foundReason)
623
+ break search;
624
+ }
625
+ }
626
+ }
627
+ if (foundSource) {
628
+ chain.push(`${ensureEffectName(foundSource)} -> (${foundReason}) -> ${ensureEffectName(current)}`);
629
+ current = foundSource;
630
+ }
631
+ else if (foundReason) {
632
+ chain.push(`External -> (${foundReason}) -> ${ensureEffectName(current)}`);
633
+ break;
634
+ }
635
+ else {
636
+ break;
637
+ }
638
+ }
639
+ return chain.reverse();
640
+ }
641
+ function buildEffectNodes(allEffects) {
642
+ const nodes = [];
643
+ const nodeByEffect = new Map();
644
+ const ordered = Array.from(allEffects);
645
+ for (const effect of ordered) {
646
+ const label = ensureEffectName(effect);
647
+ const node = {
648
+ id: `effect_${nodes.length}`,
649
+ label,
650
+ type: 'effect',
651
+ depth: 0,
652
+ debugName: label,
653
+ };
654
+ nodes.push(node);
655
+ nodeByEffect.set(effect, node);
656
+ }
657
+ const depthCache = new Map();
658
+ const computeDepth = (effect) => {
659
+ if (!effect)
660
+ return 0;
661
+ const cached = depthCache.get(effect);
662
+ if (cached !== undefined)
663
+ return cached;
664
+ const parent = getEffectNode(effect).parent;
665
+ const depth = computeDepth(parent) + (parent ? 1 : 0);
666
+ depthCache.set(effect, depth);
667
+ return depth;
668
+ };
669
+ for (const [effect, node] of nodeByEffect) {
670
+ node.depth = computeDepth(effect);
671
+ const parent = getEffectNode(effect).parent;
672
+ if (parent) {
673
+ const parentNode = nodeByEffect.get(parent);
674
+ if (parentNode) {
675
+ node.parentId = parentNode.id;
676
+ }
677
+ }
678
+ }
679
+ return { nodes, nodeByEffect };
680
+ }
681
+ /**
682
+ * Builds a graph representing current reactive state (effects, objects, and trigger edges)
683
+ */
684
+ function buildReactivityGraph() {
685
+ const nodes = [];
686
+ const edges = [];
687
+ const nodeIds = new Map();
688
+ const allEffects = new Set(debugEffectRegistry);
689
+ ensureParentChains(allEffects);
690
+ const { nodes: effectNodes, nodeByEffect } = buildEffectNodes(allEffects);
691
+ for (const node of effectNodes)
692
+ nodes.push(node);
693
+ for (const [effect, node] of nodeByEffect) {
694
+ nodeIds.set(effect, node.id);
695
+ }
696
+ // Object nodes (optional, used for dependency inspection)
697
+ for (const obj of debugObjectRegistry) {
698
+ const id = `object_${nodes.length}`;
699
+ nodes.push({ id, label: ensureObjectName(obj), type: 'state', debugName: objectNames.get(obj) });
700
+ nodeIds.set(obj, id);
701
+ }
702
+ // External source node (user/system outside of effects)
703
+ if (triggerGraph.has(EXTERNAL_SOURCE)) {
704
+ const externalId = `effect_external`;
705
+ nodes.push({ id: externalId, label: 'External', type: 'external', depth: 0 });
706
+ nodeIds.set(EXTERNAL_SOURCE, externalId);
707
+ }
708
+ // Dependency edges (effect → object)
709
+ for (const effect of allEffects) {
710
+ const effectId = nodeIds.get(effect);
711
+ if (!effectId)
712
+ continue;
713
+ const deps = effectToReactiveObjects.get(effect);
714
+ if (!deps)
715
+ continue;
716
+ for (const obj of deps) {
717
+ const objId = nodeIds.get(obj);
718
+ if (!objId)
719
+ continue;
720
+ edges.push({
721
+ id: `${effectId}->${objId}`,
722
+ source: effectId,
723
+ target: objId,
724
+ type: 'dependency',
725
+ label: 'depends',
726
+ });
727
+ }
728
+ }
729
+ // Cause edges (effect/object/prop → effect)
730
+ for (const [source, targetMap] of triggerGraph) {
731
+ for (const [targetEffect, labelMap] of targetMap) {
732
+ const targetId = nodeIds.get(targetEffect);
733
+ if (!targetId)
734
+ continue;
735
+ const sourceId = nodeIds.get(source);
736
+ if (!sourceId)
737
+ continue;
738
+ for (const record of labelMap.values()) {
739
+ edges.push({
740
+ id: `${sourceId}->${targetId}:${record.label}`,
741
+ source: sourceId,
742
+ target: targetId,
743
+ type: 'cause',
744
+ label: record.count > 1 ? `${record.label} (${record.count})` : record.label,
745
+ count: record.count,
746
+ });
747
+ }
748
+ }
749
+ }
750
+ return {
751
+ nodes,
752
+ edges,
753
+ meta: {
754
+ generatedAt: Date.now(),
755
+ devtoolsEnabled,
756
+ },
757
+ };
758
+ }
759
+ const captureLineage = () => wrapLineageForDebug(getLineage());
760
+ /**
761
+ * Enables the DevTools bridge and exposes the debug API on window/global.
762
+ * Call as early as possible in development builds.
763
+ */
764
+ function enableDevTools() {
765
+ const globalScope = (typeof globalThis !== 'undefined'
766
+ ? globalThis
767
+ : typeof window !== 'undefined'
768
+ ? window
769
+ : typeof global !== 'undefined'
770
+ ? global
771
+ : undefined);
772
+ if (!globalScope)
773
+ return;
774
+ if (devtoolsEnabled)
775
+ return;
776
+ devtoolsEnabled = true;
777
+ if (options.introspection) {
778
+ options.introspection.logErrors = true;
779
+ }
780
+ globalScope.__MUTTS_DEBUG__ = {
781
+ getGraph: buildReactivityGraph,
782
+ logLineage(tag) {
783
+ logLineage(getLineage());
784
+ return '🦴 Effect Lineage Trace' + (tag ? ` (${tag})` : '');
785
+ },
786
+ get browserLineage() {
787
+ return captureLineage();
788
+ },
789
+ getLineage,
790
+ captureLineage: getStackFrame,
791
+ formatLineage,
792
+ showLineagePanel,
793
+ setEffectName,
794
+ setObjectName,
795
+ registerEffect: registerEffectForDebug,
796
+ registerObject: registerObjectForDebug,
797
+ };
798
+ // @ts-ignore - devtoolsFormatters is a Chrome-specific array
799
+ if (globalScope.devtoolsFormatters) {
800
+ globalScope.devtoolsFormatters.push(lineageFormatter);
801
+ }
802
+ else {
803
+ globalScope.devtoolsFormatters = [lineageFormatter];
804
+ }
805
+ setDebugHooks({
806
+ isDevtoolsEnabled: () => devtoolsEnabled,
807
+ registerEffect: registerEffectForDebug,
808
+ getTriggerChain: getTriggerChain,
809
+ captureStack: getStackFrame,
810
+ captureLineage,
811
+ formatStack: (stack) => {
812
+ if (typeof stack === 'string') {
813
+ return [
814
+ `\n ${stack
815
+ .split('\n')
816
+ .slice(1)
817
+ .map((l) => l.trim())
818
+ .join('\n ')}`,
819
+ ];
820
+ }
821
+ if (isLineage(stack)) {
822
+ return ['\n', stack];
823
+ }
824
+ return ['\n', stack];
825
+ },
826
+ recordTriggerLink: recordTriggerLink,
827
+ });
828
+ }
829
+ function forceEnableGraphTracking() {
830
+ devtoolsEnabled = true;
831
+ }
832
+ function isDevtoolsEnabled() {
833
+ return devtoolsEnabled;
834
+ }
835
+ /**
836
+ * Returns the raw dependency graph data structure.
837
+ * This is useful for programmatic analysis of the reactive system.
838
+ */
839
+ function getDependencyGraph() {
840
+ return {
841
+ nodes: buildReactivityGraph().nodes,
842
+ edges: buildReactivityGraph().edges,
843
+ };
844
+ }
845
+ /**
846
+ * Capture a complete snapshot of the current reactive state
847
+ */
848
+ function snapshot() {
849
+ return {
850
+ graph: getDependencyGraph(),
851
+ history: getMutationHistory(),
852
+ timestamp: Date.now(),
853
+ };
854
+ }
855
+ /**
856
+ * Returns a list of effects that depend on the given object.
857
+ */
858
+ function getDependents(obj) {
859
+ const dependents = [];
860
+ // Scan the trigger graph for effects triggered by this object
861
+ // This is O(E) where E is the number of edges, might need optimization for large graphs
862
+ // but acceptable for introspection
863
+ for (const [_source, targetMap] of triggerGraph) {
864
+ for (const [targetEffect, labelMap] of targetMap) {
865
+ for (const record of labelMap.values()) {
866
+ if (record.object === obj) {
867
+ dependents.push(targetEffect);
868
+ }
869
+ }
870
+ }
871
+ }
872
+ // Also check direct dependencies (dependency graph)
873
+ // We don't have a direct obj -> effect map without walking all effects
874
+ // unless we use `watchers` from tracking.ts but that's internal
875
+ return [...new Set(dependents)];
876
+ }
877
+ /**
878
+ * Returns a list of objects that the given effect depends on.
879
+ */
880
+ function getDependencies(effect) {
881
+ const deps = effectToReactiveObjects.get(effect);
882
+ return deps ? Array.from(deps) : [];
883
+ }
884
+ const mutationHistory = [];
885
+ let mutationCounter = 0;
886
+ function addToMutationHistory(source, target, obj, prop, evolution) {
887
+ const record = {
888
+ id: ++mutationCounter,
889
+ timestamp: Date.now(),
890
+ source: source ? ensureEffectName(source) : 'External',
891
+ target: ensureEffectName(target),
892
+ objectName: ensureObjectName(obj),
893
+ prop: String(prop),
894
+ type: evolution.type,
895
+ };
896
+ mutationHistory.push(record);
897
+ if (mutationHistory.length > (options.introspection?.historySize ?? 50)) {
898
+ mutationHistory.shift();
899
+ }
900
+ }
901
+ /**
902
+ * Get the recent mutation history
903
+ */
904
+ function getMutationHistory() {
905
+ return [...mutationHistory];
906
+ }
907
+ // --- Auto DevTools Initialization ---
908
+ // Automatically enable devtools in development environments
909
+ /**
910
+ * Checks if we're in a development environment
911
+ * Detection order:
912
+ * 1. process.env.NODE_ENV (Node.js)
913
+ * 2. import.meta.env.DEV (Vite)
914
+ * 3. import.meta.env.PROD === false (Vite alternative)
915
+ * 4. Assumes development if none of the above are set (safe default)
916
+ */
917
+ function isDevelopmentMode() {
918
+ // Check for explicit production flag first
919
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
920
+ return false;
921
+ }
922
+ // Check for Vite's DEV flag
923
+ if (typeof import.meta !== 'undefined') {
924
+ const meta = import.meta;
925
+ if (meta.env?.PROD === true) {
926
+ return false;
927
+ }
928
+ if (meta.env?.DEV === true) {
929
+ return true;
930
+ }
931
+ }
932
+ // Check for custom global override
933
+ if (typeof globalThis !== 'undefined' && '__MUTTS_DEV_MODE__' in globalThis) {
934
+ return globalThis.__MUTTS_DEV_MODE__ !== false;
935
+ }
936
+ // Default to development (safer to enable in dev than disable in prod)
937
+ return true;
938
+ }
939
+ // Auto-enable devtools when the module loads in development
940
+ if (isDevelopmentMode() && !devtoolsEnabled) {
941
+ enableDevTools();
942
+ // Optional: Log that devtools were enabled (only in development)
943
+ if (typeof console !== 'undefined' && console.info) {
944
+ // Do not log these in when mode === 'test'
945
+ console.info('🦴 Mutts DevTools enabled automatically in development mode');
946
+ }
947
+ }
948
+
949
+ export { buildReactivityGraph, captureLineage, captureNodeLineage, enableDevTools, forceEnableGraphTracking, formatLineage, getDependencies, getDependencyGraph, getDependents, getLineage, getMutationHistory, getStackFrame, getTriggerChain, isDevtoolsEnabled, isLineage, lineageFormatter, logError, logLineage, parseStackLine, recordTriggerLink, registerEffectForDebug, registerObjectForDebug, setEffectName, setObjectName, showLineagePanel, snapshot, wrapLineageForDebug };
950
+ //# sourceMappingURL=debug.esm.js.map