@alloy-js/core 0.23.0-dev.1 → 0.23.0-dev.11

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 (337) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +80 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +60 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AccessExpression.d.ts +78 -0
  8. package/dist/src/components/AccessExpression.d.ts.map +1 -0
  9. package/dist/src/components/AccessExpression.js +218 -0
  10. package/dist/src/components/AccessExpression.js.map +1 -0
  11. package/dist/src/components/AccessExpression.test.d.ts +2 -0
  12. package/dist/src/components/AccessExpression.test.d.ts.map +1 -0
  13. package/dist/src/components/AccessExpression.test.js +137 -0
  14. package/dist/src/components/AccessExpression.test.js.map +1 -0
  15. package/dist/src/components/AppendFile.d.ts.map +1 -1
  16. package/dist/src/components/AppendFile.js +14 -3
  17. package/dist/src/components/AppendFile.js.map +1 -1
  18. package/dist/src/components/Block.js +1 -1
  19. package/dist/src/components/Block.js.map +1 -1
  20. package/dist/src/components/Declaration.d.ts.map +1 -1
  21. package/dist/src/components/Declaration.js +2 -1
  22. package/dist/src/components/Declaration.js.map +1 -1
  23. package/dist/src/components/For.d.ts.map +1 -1
  24. package/dist/src/components/For.js +1 -1
  25. package/dist/src/components/For.js.map +1 -1
  26. package/dist/src/components/List.d.ts.map +1 -1
  27. package/dist/src/components/List.js +1 -1
  28. package/dist/src/components/List.js.map +1 -1
  29. package/dist/src/components/Prose.js +2 -2
  30. package/dist/src/components/Prose.js.map +1 -1
  31. package/dist/src/components/Scope.d.ts.map +1 -1
  32. package/dist/src/components/Scope.js +6 -1
  33. package/dist/src/components/Scope.js.map +1 -1
  34. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  35. package/dist/src/components/SourceDirectory.js +1 -2
  36. package/dist/src/components/SourceDirectory.js.map +1 -1
  37. package/dist/src/components/Switch.d.ts.map +1 -1
  38. package/dist/src/components/Switch.js +1 -1
  39. package/dist/src/components/Switch.js.map +1 -1
  40. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  41. package/dist/src/components/TemplateFile.js +18 -3
  42. package/dist/src/components/TemplateFile.js.map +1 -1
  43. package/dist/src/components/index.d.ts +1 -0
  44. package/dist/src/components/index.d.ts.map +1 -1
  45. package/dist/src/components/index.js +1 -0
  46. package/dist/src/components/index.js.map +1 -1
  47. package/dist/src/content-slot.d.ts.map +1 -1
  48. package/dist/src/content-slot.js +7 -6
  49. package/dist/src/content-slot.js.map +1 -1
  50. package/dist/src/context.d.ts.map +1 -1
  51. package/dist/src/context.js +10 -3
  52. package/dist/src/context.js.map +1 -1
  53. package/dist/src/debug/cli.d.ts +6 -0
  54. package/dist/src/debug/cli.d.ts.map +1 -0
  55. package/dist/src/{debug.js → debug/cli.js} +79 -82
  56. package/dist/src/debug/cli.js.map +1 -0
  57. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  58. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  59. package/dist/src/debug/diagnostics.test.js +46 -0
  60. package/dist/src/debug/diagnostics.test.js.map +1 -0
  61. package/dist/src/debug/effects.d.ts +81 -0
  62. package/dist/src/debug/effects.d.ts.map +1 -0
  63. package/dist/src/debug/effects.js +358 -0
  64. package/dist/src/debug/effects.js.map +1 -0
  65. package/dist/src/debug/effects.test.d.ts +2 -0
  66. package/dist/src/debug/effects.test.d.ts.map +1 -0
  67. package/dist/src/debug/effects.test.js +256 -0
  68. package/dist/src/debug/effects.test.js.map +1 -0
  69. package/dist/src/debug/files.d.ts +14 -0
  70. package/dist/src/debug/files.d.ts.map +1 -0
  71. package/dist/src/debug/files.js +29 -0
  72. package/dist/src/debug/files.js.map +1 -0
  73. package/dist/src/debug/files.test.d.ts +2 -0
  74. package/dist/src/debug/files.test.d.ts.map +1 -0
  75. package/dist/src/debug/files.test.js +66 -0
  76. package/dist/src/debug/files.test.js.map +1 -0
  77. package/dist/src/debug/index.d.ts +63 -0
  78. package/dist/src/debug/index.d.ts.map +1 -0
  79. package/dist/src/debug/index.js +71 -0
  80. package/dist/src/debug/index.js.map +1 -0
  81. package/dist/src/debug/message-format.test.d.ts +2 -0
  82. package/dist/src/debug/message-format.test.d.ts.map +1 -0
  83. package/dist/src/debug/message-format.test.js +700 -0
  84. package/dist/src/debug/message-format.test.js.map +1 -0
  85. package/dist/src/debug/render-tree-orphans.test.d.ts +2 -0
  86. package/dist/src/debug/render-tree-orphans.test.d.ts.map +1 -0
  87. package/dist/src/debug/render-tree-orphans.test.js +297 -0
  88. package/dist/src/debug/render-tree-orphans.test.js.map +1 -0
  89. package/dist/src/debug/render.d.ts +57 -0
  90. package/dist/src/debug/render.d.ts.map +1 -0
  91. package/dist/src/debug/render.js +472 -0
  92. package/dist/src/debug/render.js.map +1 -0
  93. package/dist/src/debug/render.test.d.ts +2 -0
  94. package/dist/src/debug/render.test.d.ts.map +1 -0
  95. package/dist/src/debug/render.test.js +291 -0
  96. package/dist/src/debug/render.test.js.map +1 -0
  97. package/dist/src/debug/serialize.d.ts +9 -0
  98. package/dist/src/debug/serialize.d.ts.map +1 -0
  99. package/dist/src/debug/serialize.js +70 -0
  100. package/dist/src/debug/serialize.js.map +1 -0
  101. package/dist/src/debug/symbols.d.ts +16 -0
  102. package/dist/src/debug/symbols.d.ts.map +1 -0
  103. package/dist/src/debug/symbols.js +196 -0
  104. package/dist/src/debug/symbols.js.map +1 -0
  105. package/dist/src/debug/symbols.test.d.ts +2 -0
  106. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  107. package/dist/src/debug/symbols.test.js +93 -0
  108. package/dist/src/debug/symbols.test.js.map +1 -0
  109. package/dist/src/debug/trace-writer.d.ts +55 -0
  110. package/dist/src/debug/trace-writer.d.ts.map +1 -0
  111. package/dist/src/debug/trace-writer.js +658 -0
  112. package/dist/src/debug/trace-writer.js.map +1 -0
  113. package/dist/src/debug/trace.d.ts +342 -0
  114. package/dist/src/debug/trace.d.ts.map +1 -0
  115. package/dist/src/debug/trace.js +446 -0
  116. package/dist/src/debug/trace.js.map +1 -0
  117. package/dist/src/devtools/devtools-protocol.d.ts +389 -0
  118. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  119. package/dist/src/devtools/devtools-protocol.js +2 -0
  120. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  121. package/dist/src/devtools/devtools-server.browser.d.ts +23 -0
  122. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  123. package/dist/src/devtools/devtools-server.browser.js +33 -0
  124. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  125. package/dist/src/devtools/devtools-server.d.ts +66 -0
  126. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  127. package/dist/src/devtools/devtools-server.js +444 -0
  128. package/dist/src/devtools/devtools-server.js.map +1 -0
  129. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  130. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  131. package/dist/src/devtools/devtools-transport.js +114 -0
  132. package/dist/src/devtools/devtools-transport.js.map +1 -0
  133. package/dist/src/devtools-entry.browser.d.ts +4 -0
  134. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  135. package/dist/src/devtools-entry.browser.js +2 -0
  136. package/dist/src/devtools-entry.browser.js.map +1 -0
  137. package/dist/src/devtools-entry.d.ts +4 -0
  138. package/dist/src/devtools-entry.d.ts.map +1 -0
  139. package/dist/src/devtools-entry.js +2 -0
  140. package/dist/src/devtools-entry.js.map +1 -0
  141. package/dist/src/diagnostics.d.ts +34 -0
  142. package/dist/src/diagnostics.d.ts.map +1 -0
  143. package/dist/src/diagnostics.js +89 -0
  144. package/dist/src/diagnostics.js.map +1 -0
  145. package/dist/src/index.d.ts +3 -2
  146. package/dist/src/index.d.ts.map +1 -1
  147. package/dist/src/index.js +3 -2
  148. package/dist/src/index.js.map +1 -1
  149. package/dist/src/print-hook.d.ts +14 -0
  150. package/dist/src/print-hook.d.ts.map +1 -0
  151. package/dist/src/print-hook.js +10 -0
  152. package/dist/src/print-hook.js.map +1 -0
  153. package/dist/src/reactive-union-set.d.ts.map +1 -1
  154. package/dist/src/reactive-union-set.js +28 -3
  155. package/dist/src/reactive-union-set.js.map +1 -1
  156. package/dist/src/reactivity.d.ts +60 -7
  157. package/dist/src/reactivity.d.ts.map +1 -1
  158. package/dist/src/reactivity.js +308 -39
  159. package/dist/src/reactivity.js.map +1 -1
  160. package/dist/src/render-stack.d.ts +18 -1
  161. package/dist/src/render-stack.d.ts.map +1 -1
  162. package/dist/src/render-stack.js +61 -1
  163. package/dist/src/render-stack.js.map +1 -1
  164. package/dist/src/render.d.ts +8 -15
  165. package/dist/src/render.d.ts.map +1 -1
  166. package/dist/src/render.js +424 -109
  167. package/dist/src/render.js.map +1 -1
  168. package/dist/src/resource.d.ts.map +1 -1
  169. package/dist/src/resource.js +5 -0
  170. package/dist/src/resource.js.map +1 -1
  171. package/dist/src/scheduler.d.ts +13 -0
  172. package/dist/src/scheduler.d.ts.map +1 -1
  173. package/dist/src/scheduler.js +150 -13
  174. package/dist/src/scheduler.js.map +1 -1
  175. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  176. package/dist/src/symbols/basic-symbol.js +6 -1
  177. package/dist/src/symbols/basic-symbol.js.map +1 -1
  178. package/dist/src/symbols/decl.d.ts.map +1 -1
  179. package/dist/src/symbols/decl.js +5 -1
  180. package/dist/src/symbols/decl.js.map +1 -1
  181. package/dist/src/symbols/output-scope.d.ts +2 -1
  182. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  183. package/dist/src/symbols/output-scope.js +13 -8
  184. package/dist/src/symbols/output-scope.js.map +1 -1
  185. package/dist/src/symbols/output-symbol.d.ts +1 -0
  186. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  187. package/dist/src/symbols/output-symbol.js +25 -8
  188. package/dist/src/symbols/output-symbol.js.map +1 -1
  189. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  190. package/dist/src/symbols/symbol-flow.js +24 -8
  191. package/dist/src/symbols/symbol-flow.js.map +1 -1
  192. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  193. package/dist/src/symbols/symbol-slot.js +15 -0
  194. package/dist/src/symbols/symbol-slot.js.map +1 -1
  195. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  196. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  197. package/dist/src/symbols/symbol-slot.test.js +35 -0
  198. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  199. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  200. package/dist/src/symbols/symbol-table.js +6 -5
  201. package/dist/src/symbols/symbol-table.js.map +1 -1
  202. package/dist/src/trace.d.ts +2 -0
  203. package/dist/src/trace.d.ts.map +1 -0
  204. package/dist/src/trace.js +2 -0
  205. package/dist/src/trace.js.map +1 -0
  206. package/dist/src/tracer.d.ts +2 -228
  207. package/dist/src/tracer.d.ts.map +1 -1
  208. package/dist/src/tracer.js +5 -298
  209. package/dist/src/tracer.js.map +1 -1
  210. package/dist/src/utils.d.ts.map +1 -1
  211. package/dist/src/utils.js +17 -9
  212. package/dist/src/utils.js.map +1 -1
  213. package/dist/test/components/append-file.test.d.ts.map +1 -1
  214. package/dist/test/components/append-file.test.js +18 -10
  215. package/dist/test/components/append-file.test.js.map +1 -1
  216. package/dist/test/components/template-file.test.d.ts.map +1 -1
  217. package/dist/test/components/template-file.test.js +6 -4
  218. package/dist/test/components/template-file.test.js.map +1 -1
  219. package/dist/test/lazy-isempty.test.d.ts +2 -0
  220. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  221. package/dist/test/lazy-isempty.test.js +89 -0
  222. package/dist/test/lazy-isempty.test.js.map +1 -0
  223. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  224. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  225. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  226. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  227. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  228. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  229. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  230. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  231. package/dist/test/rendering/basic.test.js +3 -0
  232. package/dist/test/rendering/basic.test.js.map +1 -1
  233. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -1
  234. package/dist/test/rendering/print-render-stack.test.js +91 -98
  235. package/dist/test/rendering/print-render-stack.test.js.map +1 -1
  236. package/dist/test/scheduler-extended.test.d.ts +2 -0
  237. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  238. package/dist/test/scheduler-extended.test.js +96 -0
  239. package/dist/test/scheduler-extended.test.js.map +1 -0
  240. package/dist/test/scheduler.test.d.ts +2 -0
  241. package/dist/test/scheduler.test.d.ts.map +1 -0
  242. package/dist/test/scheduler.test.js +46 -0
  243. package/dist/test/scheduler.test.js.map +1 -0
  244. package/dist/testing/create-test-wrapper.d.ts +1 -1
  245. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  246. package/dist/testing/create-test-wrapper.js +1 -1
  247. package/dist/testing/create-test-wrapper.js.map +1 -1
  248. package/dist/testing/devtools-utils.d.ts +35 -0
  249. package/dist/testing/devtools-utils.d.ts.map +1 -0
  250. package/dist/testing/devtools-utils.js +162 -0
  251. package/dist/testing/devtools-utils.js.map +1 -0
  252. package/dist/testing/extend-expect.d.ts.map +1 -1
  253. package/dist/testing/extend-expect.js +63 -1
  254. package/dist/testing/extend-expect.js.map +1 -1
  255. package/dist/testing/render.d.ts +2 -2
  256. package/dist/testing/render.d.ts.map +1 -1
  257. package/dist/testing/render.js +2 -2
  258. package/dist/testing/render.js.map +1 -1
  259. package/dist/tsconfig.tsbuildinfo +1 -1
  260. package/package.json +21 -7
  261. package/scripts/copy-devtools-ui.mjs +26 -0
  262. package/src/binder.ts +117 -53
  263. package/src/components/AccessExpression.test.tsx +132 -0
  264. package/src/components/AccessExpression.tsx +344 -0
  265. package/src/components/AppendFile.tsx +14 -9
  266. package/src/components/Block.tsx +1 -1
  267. package/src/components/Declaration.tsx +2 -1
  268. package/src/components/For.tsx +14 -10
  269. package/src/components/List.tsx +7 -4
  270. package/src/components/Prose.tsx +1 -1
  271. package/src/components/Scope.tsx +6 -1
  272. package/src/components/SourceDirectory.tsx +1 -2
  273. package/src/components/Switch.tsx +11 -7
  274. package/src/components/TemplateFile.tsx +18 -9
  275. package/src/components/index.tsx +1 -0
  276. package/src/content-slot.tsx +7 -7
  277. package/src/context.ts +17 -6
  278. package/src/{debug.ts → debug/cli.ts} +114 -125
  279. package/src/debug/diagnostics.test.tsx +56 -0
  280. package/src/debug/effects.test.tsx +301 -0
  281. package/src/debug/effects.ts +531 -0
  282. package/src/debug/files.test.tsx +76 -0
  283. package/src/debug/files.ts +40 -0
  284. package/src/debug/index.ts +132 -0
  285. package/src/debug/message-format.test.tsx +759 -0
  286. package/src/debug/render-tree-orphans.test.tsx +344 -0
  287. package/src/debug/render.test.tsx +357 -0
  288. package/src/debug/render.ts +698 -0
  289. package/src/debug/serialize.ts +85 -0
  290. package/src/debug/symbols.test.tsx +105 -0
  291. package/src/debug/symbols.ts +322 -0
  292. package/src/debug/trace-writer.ts +969 -0
  293. package/src/debug/trace.ts +309 -0
  294. package/src/devtools/devtools-protocol.ts +497 -0
  295. package/src/devtools/devtools-server.browser.ts +62 -0
  296. package/src/devtools/devtools-server.ts +468 -0
  297. package/src/devtools/devtools-transport.ts +154 -0
  298. package/src/devtools-entry.browser.ts +48 -0
  299. package/src/devtools-entry.ts +48 -0
  300. package/src/diagnostics.ts +150 -0
  301. package/src/index.ts +2 -7
  302. package/src/print-hook.ts +22 -0
  303. package/src/reactive-union-set.ts +85 -44
  304. package/src/reactivity.ts +396 -58
  305. package/src/render-stack.ts +73 -1
  306. package/src/render.ts +544 -161
  307. package/src/resource.ts +28 -19
  308. package/src/scheduler.ts +209 -14
  309. package/src/symbols/basic-symbol.ts +6 -1
  310. package/src/symbols/decl.ts +5 -1
  311. package/src/symbols/output-scope.ts +21 -13
  312. package/src/symbols/output-symbol.ts +34 -14
  313. package/src/symbols/symbol-flow.ts +76 -39
  314. package/src/symbols/symbol-slot.test.tsx +41 -0
  315. package/src/symbols/symbol-slot.tsx +47 -20
  316. package/src/symbols/symbol-table.ts +6 -10
  317. package/src/trace.ts +1 -0
  318. package/src/tracer.ts +13 -242
  319. package/src/utils.tsx +31 -21
  320. package/temp/api.json +5700 -3015
  321. package/test/components/append-file.test.tsx +36 -29
  322. package/test/components/template-file.test.tsx +11 -11
  323. package/test/lazy-isempty.test.tsx +106 -0
  324. package/test/reactive-union-set-disposers.test.tsx +112 -0
  325. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  326. package/test/rendering/basic.test.tsx +4 -0
  327. package/test/rendering/print-render-stack.test.tsx +52 -43
  328. package/test/scheduler-extended.test.tsx +122 -0
  329. package/test/scheduler.test.tsx +50 -0
  330. package/testing/create-test-wrapper.tsx +1 -1
  331. package/testing/devtools-utils.ts +245 -0
  332. package/testing/extend-expect.ts +89 -0
  333. package/testing/render.ts +2 -2
  334. package/testing/vitest.d.ts +9 -0
  335. package/dist/src/debug.d.ts +0 -14
  336. package/dist/src/debug.d.ts.map +0 -1
  337. package/dist/src/debug.js.map +0 -1
@@ -0,0 +1,698 @@
1
+ import { watch } from "@vue/reactivity";
2
+ import * as devalue from "devalue";
3
+ import type {
4
+ RenderErrorStackEntry as ProtocolRenderErrorStackEntry,
5
+ RenderTreeNode,
6
+ } from "../devtools/devtools-protocol.js";
7
+ import {
8
+ isDevtoolsEnabled,
9
+ registerDevtoolsMessageHandler,
10
+ } from "../devtools/devtools-server.js";
11
+ import {
12
+ isPrintHook,
13
+ type PrintHook,
14
+ type RenderedTextTree,
15
+ } from "../print-hook.js";
16
+ import { getContext, untrack } from "../reactivity.js";
17
+ import type { ComponentCreator } from "../runtime/component.js";
18
+ import { flushJobsAsync } from "../scheduler.js";
19
+ import { sanitizeRecord } from "./serialize.js";
20
+ import {
21
+ deleteDirectory,
22
+ deleteOutputFile,
23
+ insertDirectory,
24
+ insertOutputFile,
25
+ insertRenderError,
26
+ insertRenderNode,
27
+ isTraceEnabled,
28
+ notifyFlushComplete,
29
+ notifyRenderComplete,
30
+ notifyRenderReset,
31
+ deleteRenderNode as traceDeleteRenderNode,
32
+ updateRenderNodeProps as traceUpdateRenderNodeProps,
33
+ updateEffectComponentByContext,
34
+ updateRenderNodeContext,
35
+ } from "./trace-writer.js";
36
+ import { isDebugEnabled, logDevtoolsMessage } from "./trace.js";
37
+
38
+ /** The kind discriminant for render tree nodes. */
39
+ export type RenderTreeNodeKind = RenderTreeNode["kind"];
40
+
41
+ /** Information about a render tree node, used when recording nodes to devtools. */
42
+ export interface RenderTreeNodeInfo {
43
+ kind: RenderTreeNodeKind;
44
+ name?: string;
45
+ propsSerialized?: string;
46
+ value?: string;
47
+ source?: RenderTreeNode["source"];
48
+ }
49
+
50
+ export interface RenderNodeActions {
51
+ rerender: () => void;
52
+ rerenderAndBreak: () => void;
53
+ }
54
+
55
+ export interface BeginComponentOptions {
56
+ parent: RenderedTextTree;
57
+ index: number;
58
+ node: RenderedTextTree;
59
+ component: ComponentCreator<unknown>;
60
+ propsSource: Record<string, unknown> | undefined;
61
+ source: RenderTreeNodeInfo["source"] | undefined;
62
+ isExisting: boolean;
63
+ actions: RenderNodeActions;
64
+ }
65
+
66
+ export interface ComponentDebugSession {
67
+ recordDirectory(path: string): void;
68
+ recordFile(path: string, filetype: string): void;
69
+ dispose(): void;
70
+ }
71
+
72
+ /** Any node tracked by the devtools render tree. */
73
+ type TrackedNode = RenderedTextTree | PrintHook;
74
+
75
+ // ─────────────────────────────────────────────────────────────────────────────
76
+ // Module state — reset in initialize()
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+
79
+ let nodeIds = new WeakMap<TrackedNode, number>();
80
+ let idToNode = new Map<number, TrackedNode>();
81
+ let entryIds = new WeakMap<RenderedTextTree, number[]>();
82
+ let nodeKinds = new WeakMap<TrackedNode, { kind: string; name?: string }>();
83
+ let fileNodes = new Map<number, { path: string; filetype: string }>();
84
+ let directoryNodes = new Map<number, { path: string }>();
85
+ let nodeProps = new Map<number, string | undefined>();
86
+ let rerenderActions = new Map<number, RenderNodeActions>();
87
+ let nextId = 1;
88
+ let handlerRegistered = false;
89
+
90
+ // ─────────────────────────────────────────────────────────────────────────────
91
+ // Props serialization
92
+ // ─────────────────────────────────────────────────────────────────────────────
93
+
94
+ function serializeRenderTreeProps(input: Record<string, unknown> | undefined) {
95
+ return untrack(() => {
96
+ if (!input) return undefined;
97
+ const { children: _children, ...rest } = input;
98
+ const sanitized = sanitizeRecord(rest);
99
+ if (!sanitized) return undefined;
100
+ try {
101
+ return devalue.stringify(sanitized);
102
+ } catch {
103
+ return undefined;
104
+ }
105
+ });
106
+ }
107
+
108
+ // ─────────────────────────────────────────────────────────────────────────────
109
+ // Node ID management & tree structure
110
+ // ─────────────────────────────────────────────────────────────────────────────
111
+
112
+ function emitNodeRemoved(parentId: number | null, id: number) {
113
+ clearRenderTreeChildrenForId(id);
114
+ traceDeleteRenderNode(id);
115
+
116
+ rerenderActions.delete(id);
117
+ nodeProps.delete(id);
118
+ idToNode.delete(id);
119
+
120
+ const fileInfo = fileNodes.get(id);
121
+ if (fileInfo) {
122
+ deleteOutputFile(fileInfo.path);
123
+ fileNodes.delete(id);
124
+ }
125
+
126
+ const dirInfo = directoryNodes.get(id);
127
+ if (dirInfo) {
128
+ deleteDirectory(dirInfo.path);
129
+ directoryNodes.delete(id);
130
+ }
131
+ }
132
+
133
+ function getEntryList(parent: RenderedTextTree) {
134
+ let list = entryIds.get(parent);
135
+ if (!list) {
136
+ list = [];
137
+ entryIds.set(parent, list);
138
+ }
139
+ return list;
140
+ }
141
+
142
+ function getOrCreateNodeId(node: TrackedNode) {
143
+ const existing = nodeIds.get(node);
144
+ if (existing) {
145
+ // Restore reverse mapping — emitNodeRemoved deletes idToNode but nodeIds
146
+ // (WeakMap) survives. Without this, clearRenderTreeChildrenForId can't
147
+ // find the node on the next cleanup, leaving orphaned children in the DB.
148
+ idToNode.set(existing, node);
149
+ return existing;
150
+ }
151
+ const id = nextId++;
152
+ nodeIds.set(node, id);
153
+ idToNode.set(id, node);
154
+ return id;
155
+ }
156
+
157
+ export function getRenderNodeId(node: RenderedTextTree | PrintHook) {
158
+ if (!isDebugEnabled()) return undefined;
159
+ return getOrCreateNodeId(node);
160
+ }
161
+
162
+ function setEntryId(parent: RenderedTextTree, index: number, id: number) {
163
+ const list = getEntryList(parent);
164
+ list[index] = id;
165
+ }
166
+
167
+ export function initialize(root: RenderedTextTree) {
168
+ if (!isDebugEnabled()) return;
169
+ ensureDevtoolsHandler();
170
+ nodeIds = new WeakMap();
171
+ idToNode = new Map();
172
+ entryIds = new WeakMap();
173
+ nodeKinds = new WeakMap();
174
+ fileNodes = new Map();
175
+ directoryNodes = new Map();
176
+ nodeProps = new Map();
177
+ rerenderActions = new Map();
178
+ nextId = 1;
179
+ notifyRenderReset();
180
+ const rootId = getOrCreateNodeId(root);
181
+ insertRenderNode(
182
+ rootId,
183
+ null,
184
+ "root",
185
+ undefined,
186
+ undefined,
187
+ undefined,
188
+ undefined,
189
+ undefined,
190
+ null,
191
+ undefined,
192
+ );
193
+ }
194
+
195
+ export function registerRenderNodeActions(
196
+ node: RenderedTextTree | PrintHook,
197
+ actions: RenderNodeActions,
198
+ ) {
199
+ if (!isDebugEnabled()) return;
200
+ const id = getOrCreateNodeId(node);
201
+ rerenderActions.set(id, actions);
202
+ }
203
+
204
+ export function unregisterRenderNodeActions(
205
+ node: RenderedTextTree | PrintHook,
206
+ ) {
207
+ if (!isDebugEnabled()) return;
208
+ const id = nodeIds.get(node);
209
+ if (id !== undefined) {
210
+ rerenderActions.delete(id);
211
+ }
212
+ }
213
+
214
+ function ensureDevtoolsHandler() {
215
+ if (handlerRegistered || !isDevtoolsEnabled()) return;
216
+ handlerRegistered = true;
217
+ registerDevtoolsMessageHandler((message) => {
218
+ if (
219
+ message.type !== "render:rerender" &&
220
+ message.type !== "render:rerenderAndBreak"
221
+ ) {
222
+ return;
223
+ }
224
+ const rawId = (message as { id?: unknown }).id;
225
+ if (typeof rawId !== "number" && typeof rawId !== "string") return;
226
+ const id = Number(rawId);
227
+ if (!Number.isFinite(id)) return;
228
+ const actions = rerenderActions.get(id);
229
+ if (!actions) return;
230
+ if (message.type === "render:rerender") {
231
+ actions.rerender();
232
+ } else {
233
+ actions.rerenderAndBreak();
234
+ }
235
+ void flushJobsAsync();
236
+ });
237
+ }
238
+
239
+ export function recordTextNode(
240
+ parent: RenderedTextTree,
241
+ index: number,
242
+ value: string,
243
+ ) {
244
+ if (!isDebugEnabled()) return;
245
+ const id = nextId++;
246
+ setEntryId(parent, index, id);
247
+ insertRenderNode(
248
+ id,
249
+ getOrCreateNodeId(parent),
250
+ "text",
251
+ undefined,
252
+ undefined,
253
+ undefined,
254
+ undefined,
255
+ undefined,
256
+ null,
257
+ value,
258
+ );
259
+ }
260
+
261
+ function recordNodeAdded(
262
+ parent: RenderedTextTree,
263
+ index: number,
264
+ node: RenderedTextTree | PrintHook,
265
+ info: RenderTreeNodeInfo,
266
+ ) {
267
+ if (!isDebugEnabled()) return;
268
+ const id = getOrCreateNodeId(node);
269
+ if (info.propsSerialized !== undefined) {
270
+ nodeProps.set(id, info.propsSerialized);
271
+ }
272
+ // Remember the kind so cached re-adds preserve it
273
+ nodeKinds.set(node, { kind: info.kind, name: info.name });
274
+ setEntryId(parent, index, id);
275
+ insertRenderNode(
276
+ id,
277
+ getOrCreateNodeId(parent),
278
+ info.kind,
279
+ info.name,
280
+ info.propsSerialized,
281
+ info.source?.fileName,
282
+ info.source?.lineNumber,
283
+ info.source?.columnNumber,
284
+ null,
285
+ undefined,
286
+ );
287
+ }
288
+
289
+ function recordSubtreeAdded(
290
+ parentNode: RenderedTextTree | PrintHook,
291
+ subtree: RenderedTextTree,
292
+ info: RenderTreeNodeInfo = { kind: "fragment" },
293
+ ) {
294
+ if (!isDebugEnabled()) return;
295
+ const parentId = getOrCreateNodeId(parentNode);
296
+ // Check if this node was previously rendered (cached) by seeing if it already has an ID
297
+ const existingId = nodeIds.get(subtree);
298
+ const isCached = existingId !== undefined;
299
+ const id = isCached ? existingId : getOrCreateNodeId(subtree);
300
+ // Remember the kind so cached re-adds preserve it
301
+ nodeKinds.set(subtree, { kind: info.kind, name: info.name });
302
+ // Track in entryIds so clearRenderTreeChildren can find and remove it
303
+ if (Array.isArray(parentNode)) {
304
+ const list = getEntryList(parentNode);
305
+ list.push(id);
306
+ }
307
+ insertRenderNode(
308
+ id,
309
+ parentId,
310
+ info.kind,
311
+ info.name,
312
+ info.propsSerialized,
313
+ info.source?.fileName,
314
+ info.source?.lineNumber,
315
+ info.source?.columnNumber,
316
+ null,
317
+ undefined,
318
+ );
319
+
320
+ // For cached nodes, we need to recursively re-add all their children since
321
+ // clearRenderTreeChildren removed them when the parent re-rendered
322
+ if (isCached) {
323
+ recordCachedSubtreeChildrenRecursively(subtree);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Recursively re-adds all children of a cached render tree node to devtools.
329
+ * This is needed because clearRenderTreeChildren recursively removes all
330
+ * descendants, but cached nodes aren't re-rendered so their children need
331
+ * to be explicitly re-added.
332
+ */
333
+ function recordCachedSubtreeChildrenRecursively(node: RenderedTextTree) {
334
+ if (!isDebugEnabled()) return;
335
+ const parentId = getOrCreateNodeId(node);
336
+
337
+ // Clear any previously-recorded children from the DB before re-adding.
338
+ // The in-memory tree may have changed since the last cached re-add
339
+ // (e.g., a memo inside the cached subtree re-ran), so old DB children
340
+ // that are no longer in the tree would become orphans.
341
+ clearRenderTreeChildren(node);
342
+
343
+ // Rebuild the entryIds for this node
344
+ const list = getEntryList(node);
345
+ list.length = 0;
346
+
347
+ for (let i = 0; i < node.length; i++) {
348
+ const child = node[i];
349
+ if (typeof child === "string") {
350
+ // Text nodes - re-record them with new IDs
351
+ if (child !== "") {
352
+ const id = nextId++;
353
+ list.push(id);
354
+ idToNode.set(id, child as unknown as RenderedTextTree);
355
+ insertRenderNode(
356
+ id,
357
+ parentId,
358
+ "text",
359
+ undefined,
360
+ undefined,
361
+ undefined,
362
+ undefined,
363
+ undefined,
364
+ null,
365
+ child,
366
+ );
367
+ }
368
+ } else if (Array.isArray(child)) {
369
+ // Nested RenderedTextTree - record and recurse, preserving original kind
370
+ const id = getOrCreateNodeId(child);
371
+ list.push(id);
372
+ const savedKind = nodeKinds.get(child);
373
+ insertRenderNode(
374
+ id,
375
+ parentId,
376
+ savedKind?.kind ?? "fragment",
377
+ savedKind?.name,
378
+ undefined,
379
+ undefined,
380
+ undefined,
381
+ undefined,
382
+ null,
383
+ undefined,
384
+ );
385
+ recordCachedSubtreeChildrenRecursively(child);
386
+ } else if (isPrintHook(child)) {
387
+ // PrintHook - record and recurse into subtree
388
+ const id = getOrCreateNodeId(child);
389
+ list.push(id);
390
+ insertRenderNode(
391
+ id,
392
+ parentId,
393
+ "printHook",
394
+ (child as { name?: string }).name ?? "hook",
395
+ undefined,
396
+ undefined,
397
+ undefined,
398
+ undefined,
399
+ null,
400
+ undefined,
401
+ );
402
+ if (child.subtree) {
403
+ const subtreeId = getOrCreateNodeId(child.subtree);
404
+ const hookList = getEntryList(child as unknown as RenderedTextTree);
405
+ hookList.length = 0;
406
+ hookList.push(subtreeId);
407
+ insertRenderNode(
408
+ subtreeId,
409
+ id,
410
+ "fragment",
411
+ undefined,
412
+ undefined,
413
+ undefined,
414
+ undefined,
415
+ undefined,
416
+ null,
417
+ undefined,
418
+ );
419
+ recordCachedSubtreeChildrenRecursively(child.subtree);
420
+ }
421
+ }
422
+ }
423
+ }
424
+
425
+ function recordNodePropsUpdated(
426
+ node: RenderedTextTree | PrintHook,
427
+ propsSerialized: string | undefined,
428
+ ) {
429
+ if (!isDebugEnabled()) return;
430
+ const id = getOrCreateNodeId(node);
431
+ const previous = nodeProps.get(id);
432
+ if (previous === propsSerialized) return;
433
+ nodeProps.set(id, propsSerialized);
434
+ traceUpdateRenderNodeProps(id, propsSerialized);
435
+ }
436
+
437
+ function clearRenderTreeChildren(parent: RenderedTextTree) {
438
+ if (!isDebugEnabled()) return;
439
+ const list = entryIds.get(parent);
440
+ if (!list || list.length === 0) return;
441
+ const parentId = getOrCreateNodeId(parent);
442
+ for (const id of list) {
443
+ if (id !== undefined) {
444
+ emitNodeRemoved(parentId, id);
445
+ }
446
+ }
447
+ entryIds.set(parent, []);
448
+ }
449
+
450
+ function clearRenderTreeChildrenForId(id: number) {
451
+ const node = idToNode.get(id);
452
+ if (!node) return;
453
+ if (Array.isArray(node)) {
454
+ clearRenderTreeChildren(node);
455
+ return;
456
+ }
457
+ // For PrintHook nodes: clear the subtree's children, then remove
458
+ // the subtree node itself (which is a child of this hook).
459
+ const subtree = (node as { subtree?: RenderedTextTree }).subtree;
460
+ if (subtree && Array.isArray(subtree)) {
461
+ clearRenderTreeChildren(subtree);
462
+ const subtreeId = nodeIds.get(subtree);
463
+ if (subtreeId !== undefined) {
464
+ emitNodeRemoved(id, subtreeId);
465
+ }
466
+ }
467
+ }
468
+
469
+ // ─────────────────────────────────────────────────────────────────────────────
470
+ // File / directory node tracking
471
+ // ─────────────────────────────────────────────────────────────────────────────
472
+
473
+ function recordDirectoryNode(node: RenderedTextTree, path: string) {
474
+ if (!isDebugEnabled()) return;
475
+ const id = getOrCreateNodeId(node);
476
+ if (directoryNodes.has(id)) return;
477
+ directoryNodes.set(id, { path });
478
+ insertDirectory(path);
479
+ }
480
+
481
+ function recordFileNode(
482
+ node: RenderedTextTree,
483
+ path: string,
484
+ filetype: string,
485
+ ) {
486
+ if (!isDebugEnabled()) return;
487
+ const id = getOrCreateNodeId(node);
488
+ if (fileNodes.has(id)) return;
489
+ fileNodes.set(id, { path, filetype });
490
+ insertOutputFile(path, filetype, id);
491
+ }
492
+
493
+ function removeFileEntriesForNode(node: RenderedTextTree | PrintHook) {
494
+ if (!isDebugEnabled()) return;
495
+ const id = nodeIds.get(node);
496
+ if (id === undefined) return;
497
+ const fileInfo = fileNodes.get(id);
498
+ if (fileInfo) {
499
+ deleteOutputFile(fileInfo.path);
500
+ fileNodes.delete(id);
501
+ }
502
+ const dirInfo = directoryNodes.get(id);
503
+ if (dirInfo) {
504
+ deleteDirectory(dirInfo.path);
505
+ directoryNodes.delete(id);
506
+ }
507
+ }
508
+
509
+ // ─────────────────────────────────────────────────────────────────────────────
510
+ // Public API — called from render.ts via the debug object
511
+ // ─────────────────────────────────────────────────────────────────────────────
512
+
513
+ /** Begin tracking a component render. Returns a session to record files/dirs and dispose watchers. */
514
+ export function beginComponent(
515
+ options: BeginComponentOptions,
516
+ ): ComponentDebugSession {
517
+ const {
518
+ parent,
519
+ index,
520
+ node,
521
+ component,
522
+ propsSource,
523
+ source,
524
+ isExisting,
525
+ actions,
526
+ } = options;
527
+
528
+ if (!isDebugEnabled()) {
529
+ return {
530
+ recordDirectory() {},
531
+ recordFile() {},
532
+ dispose() {},
533
+ };
534
+ }
535
+
536
+ return untrack(() => {
537
+ let componentName = component.component.name;
538
+ if (componentName === "Provider") {
539
+ const contextName = (component.component as any).contextName as
540
+ | string
541
+ | undefined;
542
+ if (contextName) {
543
+ componentName = `Context ${contextName}`;
544
+ }
545
+ }
546
+ const propsSerialized = serializeRenderTreeProps(propsSource);
547
+ if (isExisting) {
548
+ clearRenderTreeChildren(node);
549
+ } else {
550
+ recordNodeAdded(parent, index, node, {
551
+ kind: "component",
552
+ name: componentName,
553
+ propsSerialized,
554
+ source,
555
+ });
556
+ }
557
+
558
+ if (isTraceEnabled()) {
559
+ const ctx = getContext();
560
+ if (ctx) {
561
+ updateRenderNodeContext(getOrCreateNodeId(node), ctx.id);
562
+ updateEffectComponentByContext(ctx.id, componentName);
563
+ }
564
+ }
565
+ recordNodePropsUpdated(node, propsSerialized);
566
+ registerRenderNodeActions(node, actions);
567
+
568
+ let stopWatch: (() => void) | undefined;
569
+ if (propsSource) {
570
+ const propKeys = Object.keys(propsSource).filter(
571
+ (key) => key !== "children",
572
+ );
573
+ if (propKeys.length > 0) {
574
+ stopWatch = watch(
575
+ () => propKeys.map((key) => propsSource[key]),
576
+ () => {
577
+ const nextSerialized = serializeRenderTreeProps(propsSource);
578
+ recordNodePropsUpdated(node, nextSerialized);
579
+ },
580
+ );
581
+ }
582
+ }
583
+
584
+ return {
585
+ recordDirectory(path: string) {
586
+ recordDirectoryNode(node, path);
587
+ },
588
+ recordFile(path: string, filetype: string) {
589
+ recordFileNode(node, path, filetype);
590
+ },
591
+ dispose() {
592
+ stopWatch?.();
593
+ removeFileEntriesForNode(node);
594
+ unregisterRenderNodeActions(node);
595
+ },
596
+ };
597
+ });
598
+ }
599
+
600
+ export function appendCustomContext(
601
+ parent: RenderedTextTree,
602
+ node: RenderedTextTree,
603
+ ) {
604
+ recordSubtreeAdded(parent, node, { kind: "customContext" });
605
+ }
606
+
607
+ export function appendPrintHook(
608
+ parent: RenderedTextTree,
609
+ index: number,
610
+ hook: PrintHook,
611
+ name: string,
612
+ subtree?: RenderedTextTree,
613
+ ) {
614
+ recordNodeAdded(parent, index, hook, { kind: "printHook", name });
615
+ if (subtree) {
616
+ recordSubtreeAdded(hook, subtree);
617
+ }
618
+ }
619
+
620
+ export function appendFragmentChild(
621
+ parent: RenderedTextTree,
622
+ child: RenderedTextTree,
623
+ ) {
624
+ recordSubtreeAdded(parent, child, { kind: "fragment" });
625
+ }
626
+
627
+ export function appendTextNode(
628
+ parent: RenderedTextTree,
629
+ index: number,
630
+ value: string,
631
+ ) {
632
+ recordTextNode(parent, index, value);
633
+ }
634
+
635
+ export function prepareMemoNode(
636
+ parent: RenderedTextTree,
637
+ node: RenderedTextTree,
638
+ isExisting: boolean,
639
+ ) {
640
+ if (isExisting) {
641
+ clearRenderTreeChildren(node);
642
+ return;
643
+ }
644
+ recordSubtreeAdded(parent, node, { kind: "memo" });
645
+ }
646
+
647
+ let nextErrorId = 1;
648
+
649
+ export interface RenderErrorInfo {
650
+ name: string;
651
+ message: string;
652
+ stack?: string;
653
+ }
654
+
655
+ /** Render error stack entry with optional runtime props (extends protocol type). */
656
+ export interface RenderErrorStackEntry extends ProtocolRenderErrorStackEntry {
657
+ props?: Record<string, unknown> | undefined;
658
+ }
659
+
660
+ export function error(
661
+ error: RenderErrorInfo,
662
+ componentStack: RenderErrorStackEntry[],
663
+ ) {
664
+ if (!isDebugEnabled()) return;
665
+ const serializedStack = untrack(() =>
666
+ componentStack.map((entry) => ({
667
+ name: entry.name,
668
+ propsSerialized: entry.propsSerialized,
669
+ renderNodeId: entry.renderNodeId,
670
+ source: entry.source,
671
+ })),
672
+ );
673
+ logDevtoolsMessage({
674
+ type: "render:error" as const,
675
+ id: nextErrorId++,
676
+ name: error.name,
677
+ message: error.message,
678
+ stack: error.stack,
679
+ componentStack: serializedStack,
680
+ });
681
+
682
+ insertRenderError(
683
+ error.name,
684
+ error.message,
685
+ error.stack,
686
+ JSON.stringify(serializedStack),
687
+ );
688
+ }
689
+
690
+ export function complete() {
691
+ logDevtoolsMessage({ type: "render:complete" });
692
+ notifyRenderComplete();
693
+ }
694
+
695
+ export function flushJobsComplete() {
696
+ logDevtoolsMessage({ type: "flushJobs:complete" });
697
+ notifyFlushComplete();
698
+ }