@alloy-js/core 0.24.0-dev.2 → 0.24.0-dev.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 (506) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/dev/src/components/AccessExpression.test.js +1 -1
  3. package/dist/dev/src/components/AccessExpression.test.js.map +1 -1
  4. package/dist/dev/src/components/Output.js +3 -2
  5. package/dist/dev/src/components/Output.js.map +1 -1
  6. package/dist/dev/src/components/SourceFile.js.map +1 -1
  7. package/dist/dev/src/content-slot.test.js +1 -1
  8. package/dist/dev/src/content-slot.test.js.map +1 -1
  9. package/dist/dev/src/context.js +30 -3
  10. package/dist/dev/src/context.js.map +1 -1
  11. package/dist/dev/src/debug/diagnostics.test.js +1 -1
  12. package/dist/dev/src/debug/diagnostics.test.js.map +1 -1
  13. package/dist/dev/src/debug/effects.test.js +1 -1
  14. package/dist/dev/src/debug/effects.test.js.map +1 -1
  15. package/dist/dev/src/debug/file-streaming.js +103 -0
  16. package/dist/dev/src/debug/file-streaming.js.map +1 -0
  17. package/dist/dev/src/debug/files.test.js +4 -5
  18. package/dist/dev/src/debug/files.test.js.map +1 -1
  19. package/dist/dev/src/debug/index.js +4 -6
  20. package/dist/dev/src/debug/index.js.map +1 -1
  21. package/dist/dev/src/debug/message-format.test.js +50 -52
  22. package/dist/dev/src/debug/message-format.test.js.map +1 -1
  23. package/dist/dev/src/debug/render-tree-orphans.test.js +13 -23
  24. package/dist/dev/src/debug/render-tree-orphans.test.js.map +1 -1
  25. package/dist/dev/src/debug/render.js +529 -352
  26. package/dist/dev/src/debug/render.js.map +1 -1
  27. package/dist/dev/src/debug/render.test.js +171 -92
  28. package/dist/dev/src/debug/render.test.js.map +1 -1
  29. package/dist/dev/src/debug/trace-writer.js +127 -15
  30. package/dist/dev/src/debug/trace-writer.js.map +1 -1
  31. package/dist/dev/src/debug/trace.js +0 -36
  32. package/dist/dev/src/debug/trace.js.map +1 -1
  33. package/dist/dev/src/devtools/devtools-server.js +55 -32
  34. package/dist/dev/src/devtools/devtools-server.js.map +1 -1
  35. package/dist/dev/src/devtools-entry.browser.js.map +1 -1
  36. package/dist/dev/src/devtools-entry.js.map +1 -1
  37. package/dist/dev/src/diagnostics.js +19 -1
  38. package/dist/dev/src/diagnostics.js.map +1 -1
  39. package/dist/dev/src/index.js +5 -2
  40. package/dist/dev/src/index.js.map +1 -1
  41. package/dist/dev/src/jsx-runtime.js +14 -8
  42. package/dist/dev/src/jsx-runtime.js.map +1 -1
  43. package/dist/dev/src/output-types.js +2 -0
  44. package/dist/dev/src/output-types.js.map +1 -0
  45. package/dist/dev/src/reactivity.js +155 -13
  46. package/dist/dev/src/reactivity.js.map +1 -1
  47. package/dist/dev/src/render/get-string-width.js +61 -0
  48. package/dist/dev/src/render/get-string-width.js.map +1 -0
  49. package/dist/dev/src/render/index.js +2 -0
  50. package/dist/dev/src/render/index.js.map +1 -0
  51. package/dist/dev/src/render/node-context.js +7 -0
  52. package/dist/dev/src/render/node-context.js.map +1 -0
  53. package/dist/dev/src/render/node.js +386 -0
  54. package/dist/dev/src/render/node.js.map +1 -0
  55. package/dist/dev/src/render/printer-support.js +180 -0
  56. package/dist/dev/src/render/printer-support.js.map +1 -0
  57. package/dist/dev/src/render/printer.js +797 -0
  58. package/dist/dev/src/render/printer.js.map +1 -0
  59. package/dist/dev/src/render-error.js +79 -0
  60. package/dist/dev/src/render-error.js.map +1 -0
  61. package/dist/dev/src/render-output.js +209 -0
  62. package/dist/dev/src/render-output.js.map +1 -0
  63. package/dist/dev/src/runtime/create-intrinsic.js +53 -0
  64. package/dist/dev/src/runtime/create-intrinsic.js.map +1 -0
  65. package/dist/dev/src/runtime/fragment.js +21 -0
  66. package/dist/dev/src/runtime/fragment.js.map +1 -0
  67. package/dist/dev/src/runtime/index.js +13 -0
  68. package/dist/dev/src/runtime/index.js.map +1 -0
  69. package/dist/dev/src/runtime/insert.js +453 -0
  70. package/dist/dev/src/runtime/insert.js.map +1 -0
  71. package/dist/dev/src/runtime/intrinsic.js +1 -11
  72. package/dist/dev/src/runtime/intrinsic.js.map +1 -1
  73. package/dist/dev/src/scheduler.js +38 -14
  74. package/dist/dev/src/scheduler.js.map +1 -1
  75. package/dist/dev/src/stc.js +2 -0
  76. package/dist/dev/src/stc.js.map +1 -1
  77. package/dist/dev/src/sti.js +1 -1
  78. package/dist/dev/src/sti.js.map +1 -1
  79. package/dist/dev/src/symbols/symbol-slot.test.js +1 -1
  80. package/dist/dev/src/symbols/symbol-slot.test.js.map +1 -1
  81. package/dist/dev/src/test-render.js +78 -0
  82. package/dist/dev/src/test-render.js.map +1 -0
  83. package/dist/dev/src/utils.js +47 -35
  84. package/dist/dev/src/utils.js.map +1 -1
  85. package/dist/dev/test/babel-e2e.test.js +218 -0
  86. package/dist/dev/test/babel-e2e.test.js.map +1 -0
  87. package/dist/dev/test/components/block.test.js +1 -1
  88. package/dist/dev/test/components/block.test.js.map +1 -1
  89. package/dist/dev/test/components/copy-file.test.js +7 -7
  90. package/dist/dev/test/components/copy-file.test.js.map +1 -1
  91. package/dist/dev/test/components/update-file.test.js +1 -1
  92. package/dist/dev/test/components/update-file.test.js.map +1 -1
  93. package/dist/dev/test/components/wrap.test.js +1 -1
  94. package/dist/dev/test/components/wrap.test.js.map +1 -1
  95. package/dist/dev/test/control-flow/match.test.js +1 -1
  96. package/dist/dev/test/control-flow/match.test.js.map +1 -1
  97. package/dist/dev/test/control-flow/show.test.js +1 -1
  98. package/dist/dev/test/control-flow/show.test.js.map +1 -1
  99. package/dist/dev/test/lazy-isempty.test.js +6 -6
  100. package/dist/dev/test/lazy-isempty.test.js.map +1 -1
  101. package/dist/dev/test/node.test.js +80 -0
  102. package/dist/dev/test/node.test.js.map +1 -0
  103. package/dist/dev/test/output-e2e.test.js +194 -0
  104. package/dist/dev/test/output-e2e.test.js.map +1 -0
  105. package/dist/dev/test/reactivity/circular-reactives.test.js +1 -1
  106. package/dist/dev/test/reactivity/circular-reactives.test.js.map +1 -1
  107. package/dist/dev/test/reactivity/cleanup.test.js +1 -1
  108. package/dist/dev/test/reactivity/cleanup.test.js.map +1 -1
  109. package/dist/dev/test/rendering/memoization.test.js +6 -1
  110. package/dist/dev/test/rendering/memoization.test.js.map +1 -1
  111. package/dist/dev/test/rendering/render-output-diagnostics.test.js +102 -0
  112. package/dist/dev/test/rendering/render-output-diagnostics.test.js.map +1 -0
  113. package/dist/dev/test/runtime.test.js +385 -0
  114. package/dist/dev/test/runtime.test.js.map +1 -0
  115. package/dist/dev/test/tree-test-utils.js +16 -0
  116. package/dist/dev/test/tree-test-utils.js.map +1 -0
  117. package/dist/dev/test/utils.test.js +1 -1
  118. package/dist/dev/test/utils.test.js.map +1 -1
  119. package/dist/dev/testing/devtools-utils.js +1 -1
  120. package/dist/dev/testing/devtools-utils.js.map +1 -1
  121. package/dist/dev/testing/extend-expect.js +7 -33
  122. package/dist/dev/testing/extend-expect.js.map +1 -1
  123. package/dist/dev/testing/render.js +7 -17
  124. package/dist/dev/testing/render.js.map +1 -1
  125. package/dist/devtools/index.html +17 -17
  126. package/dist/src/components/AccessExpression.test.js +1 -1
  127. package/dist/src/components/AccessExpression.test.js.map +1 -1
  128. package/dist/src/components/Output.d.ts +1 -1
  129. package/dist/src/components/Output.d.ts.map +1 -1
  130. package/dist/src/components/Output.js +2 -1
  131. package/dist/src/components/Output.js.map +1 -1
  132. package/dist/src/components/ReferenceOrContent.d.ts +1 -1
  133. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -1
  134. package/dist/src/components/SourceFile.d.ts +1 -1
  135. package/dist/src/components/SourceFile.d.ts.map +1 -1
  136. package/dist/src/components/SourceFile.js.map +1 -1
  137. package/dist/src/content-slot.test.js +1 -1
  138. package/dist/src/content-slot.test.js.map +1 -1
  139. package/dist/src/context/format-options.d.ts +1 -1
  140. package/dist/src/context/format-options.d.ts.map +1 -1
  141. package/dist/src/context.d.ts +9 -1
  142. package/dist/src/context.d.ts.map +1 -1
  143. package/dist/src/context.js +30 -3
  144. package/dist/src/context.js.map +1 -1
  145. package/dist/src/debug/diagnostics.test.js +1 -1
  146. package/dist/src/debug/diagnostics.test.js.map +1 -1
  147. package/dist/src/debug/effects.test.js +1 -1
  148. package/dist/src/debug/effects.test.js.map +1 -1
  149. package/dist/src/debug/file-streaming.d.ts +22 -0
  150. package/dist/src/debug/file-streaming.d.ts.map +1 -0
  151. package/dist/src/debug/file-streaming.js +103 -0
  152. package/dist/src/debug/file-streaming.js.map +1 -0
  153. package/dist/src/debug/files.test.js +4 -5
  154. package/dist/src/debug/files.test.js.map +1 -1
  155. package/dist/src/debug/index.d.ts +5 -7
  156. package/dist/src/debug/index.d.ts.map +1 -1
  157. package/dist/src/debug/index.js +4 -6
  158. package/dist/src/debug/index.js.map +1 -1
  159. package/dist/src/debug/message-format.test.js +16 -18
  160. package/dist/src/debug/message-format.test.js.map +1 -1
  161. package/dist/src/debug/render-tree-orphans.test.js +8 -18
  162. package/dist/src/debug/render-tree-orphans.test.js.map +1 -1
  163. package/dist/src/debug/render.d.ts +71 -21
  164. package/dist/src/debug/render.d.ts.map +1 -1
  165. package/dist/src/debug/render.js +529 -352
  166. package/dist/src/debug/render.js.map +1 -1
  167. package/dist/src/debug/render.test.js +137 -74
  168. package/dist/src/debug/render.test.js.map +1 -1
  169. package/dist/src/debug/trace-writer.d.ts +6 -1
  170. package/dist/src/debug/trace-writer.d.ts.map +1 -1
  171. package/dist/src/debug/trace-writer.js +127 -15
  172. package/dist/src/debug/trace-writer.js.map +1 -1
  173. package/dist/src/debug/trace.d.ts +0 -36
  174. package/dist/src/debug/trace.d.ts.map +1 -1
  175. package/dist/src/debug/trace.js +0 -36
  176. package/dist/src/debug/trace.js.map +1 -1
  177. package/dist/src/devtools/devtools-protocol.d.ts +34 -1
  178. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -1
  179. package/dist/src/devtools/devtools-server.d.ts.map +1 -1
  180. package/dist/src/devtools/devtools-server.js +55 -32
  181. package/dist/src/devtools/devtools-server.js.map +1 -1
  182. package/dist/src/devtools-entry.browser.d.ts +1 -1
  183. package/dist/src/devtools-entry.browser.d.ts.map +1 -1
  184. package/dist/src/devtools-entry.browser.js.map +1 -1
  185. package/dist/src/devtools-entry.d.ts +1 -1
  186. package/dist/src/devtools-entry.d.ts.map +1 -1
  187. package/dist/src/devtools-entry.js.map +1 -1
  188. package/dist/src/diagnostics.d.ts +4 -0
  189. package/dist/src/diagnostics.d.ts.map +1 -1
  190. package/dist/src/diagnostics.js +19 -1
  191. package/dist/src/diagnostics.js.map +1 -1
  192. package/dist/src/index.d.ts +5 -2
  193. package/dist/src/index.d.ts.map +1 -1
  194. package/dist/src/index.js +5 -2
  195. package/dist/src/index.js.map +1 -1
  196. package/dist/src/jsx-runtime.d.ts +13 -4
  197. package/dist/src/jsx-runtime.d.ts.map +1 -1
  198. package/dist/src/jsx-runtime.js +14 -8
  199. package/dist/src/jsx-runtime.js.map +1 -1
  200. package/dist/src/output-types.d.ts +40 -0
  201. package/dist/src/output-types.d.ts.map +1 -0
  202. package/dist/src/output-types.js +2 -0
  203. package/dist/src/output-types.js.map +1 -0
  204. package/dist/src/reactivity.d.ts +49 -18
  205. package/dist/src/reactivity.d.ts.map +1 -1
  206. package/dist/src/reactivity.js +155 -13
  207. package/dist/src/reactivity.js.map +1 -1
  208. package/dist/src/render/get-string-width.d.ts +19 -0
  209. package/dist/src/render/get-string-width.d.ts.map +1 -0
  210. package/dist/src/render/get-string-width.js +61 -0
  211. package/dist/src/render/get-string-width.js.map +1 -0
  212. package/dist/src/render/index.d.ts +2 -0
  213. package/dist/src/render/index.d.ts.map +1 -0
  214. package/dist/src/render/index.js +2 -0
  215. package/dist/src/render/index.js.map +1 -0
  216. package/dist/src/render/node-context.d.ts +5 -0
  217. package/dist/src/render/node-context.d.ts.map +1 -0
  218. package/dist/src/render/node-context.js +7 -0
  219. package/dist/src/render/node-context.js.map +1 -0
  220. package/dist/src/render/node.d.ts +146 -0
  221. package/dist/src/render/node.d.ts.map +1 -0
  222. package/dist/src/render/node.js +386 -0
  223. package/dist/src/render/node.js.map +1 -0
  224. package/dist/src/render/printer-support.d.ts +50 -0
  225. package/dist/src/render/printer-support.d.ts.map +1 -0
  226. package/dist/src/render/printer-support.js +180 -0
  227. package/dist/src/render/printer-support.js.map +1 -0
  228. package/dist/src/render/printer.d.ts +35 -0
  229. package/dist/src/render/printer.d.ts.map +1 -0
  230. package/dist/src/render/printer.js +797 -0
  231. package/dist/src/render/printer.js.map +1 -0
  232. package/dist/src/render-error.d.ts +4 -0
  233. package/dist/src/render-error.d.ts.map +1 -0
  234. package/dist/src/render-error.js +79 -0
  235. package/dist/src/render-error.js.map +1 -0
  236. package/dist/src/render-output.d.ts +42 -0
  237. package/dist/src/render-output.d.ts.map +1 -0
  238. package/dist/src/render-output.js +209 -0
  239. package/dist/src/render-output.js.map +1 -0
  240. package/dist/src/runtime/component.d.ts +2 -2
  241. package/dist/src/runtime/component.d.ts.map +1 -1
  242. package/dist/src/runtime/create-intrinsic.d.ts +28 -0
  243. package/dist/src/runtime/create-intrinsic.d.ts.map +1 -0
  244. package/dist/src/runtime/create-intrinsic.js +53 -0
  245. package/dist/src/runtime/create-intrinsic.js.map +1 -0
  246. package/dist/src/runtime/fragment.d.ts +16 -0
  247. package/dist/src/runtime/fragment.d.ts.map +1 -0
  248. package/dist/src/runtime/fragment.js +21 -0
  249. package/dist/src/runtime/fragment.js.map +1 -0
  250. package/dist/src/runtime/index.d.ts +12 -0
  251. package/dist/src/runtime/index.d.ts.map +1 -0
  252. package/dist/src/runtime/index.js +13 -0
  253. package/dist/src/runtime/index.js.map +1 -0
  254. package/dist/src/runtime/insert.d.ts +29 -0
  255. package/dist/src/runtime/insert.d.ts.map +1 -0
  256. package/dist/src/runtime/insert.js +453 -0
  257. package/dist/src/runtime/insert.js.map +1 -0
  258. package/dist/src/runtime/intrinsic.d.ts +12 -29
  259. package/dist/src/runtime/intrinsic.d.ts.map +1 -1
  260. package/dist/src/runtime/intrinsic.js +1 -11
  261. package/dist/src/runtime/intrinsic.js.map +1 -1
  262. package/dist/src/scheduler.d.ts.map +1 -1
  263. package/dist/src/scheduler.js +38 -14
  264. package/dist/src/scheduler.js.map +1 -1
  265. package/dist/src/stc.d.ts.map +1 -1
  266. package/dist/src/stc.js +2 -0
  267. package/dist/src/stc.js.map +1 -1
  268. package/dist/src/sti.d.ts +7 -6
  269. package/dist/src/sti.d.ts.map +1 -1
  270. package/dist/src/sti.js +1 -1
  271. package/dist/src/sti.js.map +1 -1
  272. package/dist/src/symbols/symbol-slot.test.js +1 -1
  273. package/dist/src/symbols/symbol-slot.test.js.map +1 -1
  274. package/dist/src/test-render.d.ts +31 -0
  275. package/dist/src/test-render.d.ts.map +1 -0
  276. package/dist/src/test-render.js +78 -0
  277. package/dist/src/test-render.js.map +1 -0
  278. package/dist/src/utils.d.ts +1 -1
  279. package/dist/src/utils.d.ts.map +1 -1
  280. package/dist/src/utils.js +40 -28
  281. package/dist/src/utils.js.map +1 -1
  282. package/dist/src/write-output.d.ts +1 -1
  283. package/dist/src/write-output.d.ts.map +1 -1
  284. package/dist/test/babel-e2e.test.d.ts +13 -0
  285. package/dist/test/babel-e2e.test.d.ts.map +1 -0
  286. package/dist/test/babel-e2e.test.js +218 -0
  287. package/dist/test/babel-e2e.test.js.map +1 -0
  288. package/dist/test/components/block.test.js +1 -1
  289. package/dist/test/components/block.test.js.map +1 -1
  290. package/dist/test/components/copy-file.test.d.ts.map +1 -1
  291. package/dist/test/components/copy-file.test.js +1 -1
  292. package/dist/test/components/copy-file.test.js.map +1 -1
  293. package/dist/test/components/update-file.test.js +1 -1
  294. package/dist/test/components/update-file.test.js.map +1 -1
  295. package/dist/test/components/wrap.test.js +1 -1
  296. package/dist/test/components/wrap.test.js.map +1 -1
  297. package/dist/test/control-flow/match.test.js +1 -1
  298. package/dist/test/control-flow/match.test.js.map +1 -1
  299. package/dist/test/control-flow/show.test.js +1 -1
  300. package/dist/test/control-flow/show.test.js.map +1 -1
  301. package/dist/test/lazy-isempty.test.js +6 -6
  302. package/dist/test/lazy-isempty.test.js.map +1 -1
  303. package/dist/test/node.test.d.ts +2 -0
  304. package/dist/test/node.test.d.ts.map +1 -0
  305. package/dist/test/node.test.js +80 -0
  306. package/dist/test/node.test.js.map +1 -0
  307. package/dist/test/output-e2e.test.d.ts +13 -0
  308. package/dist/test/output-e2e.test.d.ts.map +1 -0
  309. package/dist/test/output-e2e.test.js +194 -0
  310. package/dist/test/output-e2e.test.js.map +1 -0
  311. package/dist/test/reactivity/circular-reactives.test.js +1 -1
  312. package/dist/test/reactivity/circular-reactives.test.js.map +1 -1
  313. package/dist/test/reactivity/cleanup.test.js +1 -1
  314. package/dist/test/reactivity/cleanup.test.js.map +1 -1
  315. package/dist/test/rendering/memoization.test.js +6 -1
  316. package/dist/test/rendering/memoization.test.js.map +1 -1
  317. package/dist/test/rendering/render-output-diagnostics.test.d.ts +2 -0
  318. package/dist/test/rendering/render-output-diagnostics.test.d.ts.map +1 -0
  319. package/dist/test/rendering/render-output-diagnostics.test.js +82 -0
  320. package/dist/test/rendering/render-output-diagnostics.test.js.map +1 -0
  321. package/dist/test/runtime.test.d.ts +11 -0
  322. package/dist/test/runtime.test.d.ts.map +1 -0
  323. package/dist/test/runtime.test.js +385 -0
  324. package/dist/test/runtime.test.js.map +1 -0
  325. package/dist/test/tree-test-utils.d.ts +3 -0
  326. package/dist/test/tree-test-utils.d.ts.map +1 -0
  327. package/dist/test/tree-test-utils.js +16 -0
  328. package/dist/test/tree-test-utils.js.map +1 -0
  329. package/dist/test/utils.test.js +1 -1
  330. package/dist/test/utils.test.js.map +1 -1
  331. package/dist/testing/devtools-utils.d.ts.map +1 -1
  332. package/dist/testing/devtools-utils.js +1 -1
  333. package/dist/testing/devtools-utils.js.map +1 -1
  334. package/dist/testing/extend-expect.d.ts.map +1 -1
  335. package/dist/testing/extend-expect.js +7 -33
  336. package/dist/testing/extend-expect.js.map +1 -1
  337. package/dist/testing/render.d.ts +7 -9
  338. package/dist/testing/render.d.ts.map +1 -1
  339. package/dist/testing/render.js +7 -17
  340. package/dist/testing/render.js.map +1 -1
  341. package/dist/tsconfig.tsbuildinfo +1 -1
  342. package/docs/api/components/Output.md +0 -3
  343. package/docs/api/components/SourceFile.md +0 -3
  344. package/docs/api/functions/createComment.md +18 -0
  345. package/docs/api/functions/createElement.md +19 -0
  346. package/docs/api/functions/createFragment.md +17 -0
  347. package/docs/api/functions/createTextNode.md +18 -0
  348. package/docs/api/functions/emitDiagnosticForTree.md +19 -0
  349. package/docs/api/functions/ensureIsEmpty.md +1 -1
  350. package/docs/api/functions/getContextForNode.md +18 -0
  351. package/docs/api/functions/getContextForRenderNode.md +4 -4
  352. package/docs/api/functions/getDiagnosticsForTree.md +7 -5
  353. package/docs/api/functions/getRegisteredDiagnosticsForTree.md +18 -0
  354. package/docs/api/functions/index.md +17 -12
  355. package/docs/api/functions/isCustomContext.md +4 -4
  356. package/docs/api/functions/notifyContentState.md +6 -0
  357. package/docs/api/functions/printTree.md +6 -16
  358. package/docs/api/functions/registerDiagnosticsForTree.md +19 -0
  359. package/docs/api/functions/render.md +1 -2
  360. package/docs/api/functions/renderAsync.md +1 -2
  361. package/docs/api/functions/renderTree.md +8 -5
  362. package/docs/api/functions/reportDiagnosticsForTree.md +18 -0
  363. package/docs/api/functions/runInContext.md +28 -0
  364. package/docs/api/functions/sourceFilesForTree.md +6 -16
  365. package/docs/api/index.md +3 -3
  366. package/docs/api/testing/functions/index.md +1 -1
  367. package/docs/api/testing/functions/renderToString.md +1 -1
  368. package/docs/api/types/AlloyNode.md +22 -0
  369. package/docs/api/types/Child.md +1 -1
  370. package/docs/api/types/CommentNode.md +15 -0
  371. package/docs/api/types/Context.md +13 -15
  372. package/docs/api/types/ElementNode.md +18 -0
  373. package/docs/api/types/FragmentNode.md +12 -0
  374. package/docs/api/types/Insertable.md +7 -0
  375. package/docs/api/types/NodeType.md +5 -0
  376. package/docs/api/types/OutputDirectory.md +0 -50
  377. package/docs/api/types/PrintTreeOptions.md +0 -1
  378. package/docs/api/types/RenderTreeOptions.md +7 -0
  379. package/docs/api/types/StiComponentCreator.md +4 -4
  380. package/docs/api/types/StiSignature.md +1 -1
  381. package/docs/api/types/TextNode.md +16 -0
  382. package/docs/api/types/index.md +10 -28
  383. package/docs/api/variables/COMMENT_NODE.md +5 -0
  384. package/docs/api/variables/ELEMENT_NODE.md +11 -0
  385. package/docs/api/variables/FRAGMENT_NODE.md +5 -0
  386. package/docs/api/variables/TEXT_NODE.md +5 -0
  387. package/docs/api/variables/index.md +4 -2
  388. package/docs/formatting.md +1 -1
  389. package/docs/rendering.md +4 -4
  390. package/package.json +6 -6
  391. package/src/components/AccessExpression.test.tsx +1 -1
  392. package/src/components/Output.tsx +2 -1
  393. package/src/components/SourceFile.tsx +1 -1
  394. package/src/content-slot.test.tsx +1 -1
  395. package/src/context/format-options.ts +1 -1
  396. package/src/context.ts +37 -4
  397. package/src/debug/diagnostics.test.tsx +1 -1
  398. package/src/debug/effects.test.tsx +1 -1
  399. package/src/debug/file-streaming.ts +115 -0
  400. package/src/debug/files.test.tsx +15 -11
  401. package/src/debug/index.ts +11 -11
  402. package/src/debug/message-format.test.tsx +32 -19
  403. package/src/debug/render-tree-orphans.test.tsx +10 -19
  404. package/src/debug/render.test.tsx +206 -78
  405. package/src/debug/render.ts +642 -495
  406. package/src/debug/trace-writer.ts +168 -14
  407. package/src/debug/trace.ts +0 -20
  408. package/src/devtools/devtools-protocol.ts +43 -0
  409. package/src/devtools/devtools-server.ts +57 -32
  410. package/src/devtools-entry.browser.ts +5 -0
  411. package/src/devtools-entry.ts +5 -0
  412. package/src/diagnostics.ts +31 -0
  413. package/src/index.ts +66 -2
  414. package/src/jsx-runtime.ts +16 -14
  415. package/src/output-types.ts +47 -0
  416. package/src/reactivity.ts +186 -40
  417. package/src/render/get-string-width.ts +201 -0
  418. package/src/render/index.ts +1 -0
  419. package/src/render/node-context.ts +14 -0
  420. package/src/render/node.ts +442 -0
  421. package/src/render/printer-support.ts +209 -0
  422. package/src/render/printer.ts +817 -0
  423. package/src/render-error.ts +98 -0
  424. package/src/render-output.ts +243 -0
  425. package/src/runtime/component.ts +2 -2
  426. package/src/runtime/create-intrinsic.ts +56 -0
  427. package/src/runtime/fragment.ts +22 -0
  428. package/src/runtime/index.ts +12 -0
  429. package/src/runtime/insert.ts +569 -0
  430. package/src/runtime/intrinsic.ts +14 -70
  431. package/src/scheduler.ts +40 -25
  432. package/src/stc.ts +3 -0
  433. package/src/sti.ts +17 -20
  434. package/src/symbols/symbol-slot.test.tsx +1 -1
  435. package/src/test-render.ts +103 -0
  436. package/src/utils.tsx +55 -37
  437. package/src/write-output.ts +1 -1
  438. package/temp/api-testing.json +390 -14
  439. package/temp/api.json +4320 -4144
  440. package/test/babel-e2e.test.ts +224 -0
  441. package/test/components/block.test.tsx +1 -1
  442. package/test/components/copy-file.test.tsx +2 -1
  443. package/test/components/update-file.test.tsx +1 -1
  444. package/test/components/wrap.test.tsx +1 -1
  445. package/test/control-flow/match.test.tsx +1 -1
  446. package/test/control-flow/show.test.tsx +1 -1
  447. package/test/lazy-isempty.test.tsx +6 -6
  448. package/test/node.test.ts +90 -0
  449. package/test/output-e2e.test.ts +198 -0
  450. package/test/reactivity/circular-reactives.test.tsx +1 -1
  451. package/test/reactivity/cleanup.test.tsx +1 -1
  452. package/test/rendering/memoization.test.tsx +6 -1
  453. package/test/rendering/render-output-diagnostics.test.tsx +120 -0
  454. package/test/runtime.test.ts +448 -0
  455. package/test/tree-test-utils.ts +23 -0
  456. package/test/utils.test.tsx +1 -1
  457. package/testing/devtools-utils.ts +2 -0
  458. package/testing/extend-expect.ts +8 -46
  459. package/testing/render.ts +17 -21
  460. package/dist/dev/src/print-hook.js +0 -10
  461. package/dist/dev/src/print-hook.js.map +0 -1
  462. package/dist/dev/src/render.js +0 -872
  463. package/dist/dev/src/render.js.map +0 -1
  464. package/dist/src/print-hook.d.ts +0 -14
  465. package/dist/src/print-hook.d.ts.map +0 -1
  466. package/dist/src/print-hook.js +0 -10
  467. package/dist/src/print-hook.js.map +0 -1
  468. package/dist/src/render.d.ts +0 -155
  469. package/dist/src/render.d.ts.map +0 -1
  470. package/dist/src/render.js +0 -872
  471. package/dist/src/render.js.map +0 -1
  472. package/docs/api/functions/createIntrinsic.md +0 -19
  473. package/docs/api/functions/createRenderTreeHook.md +0 -19
  474. package/docs/api/functions/getElementCache.md +0 -17
  475. package/docs/api/functions/isIntrinsicElement.md +0 -18
  476. package/docs/api/functions/isPrintHook.md +0 -18
  477. package/docs/api/types/AlignIntrinsicElement.md +0 -5
  478. package/docs/api/types/BrIntrinsicElement.md +0 -5
  479. package/docs/api/types/BreakParentIntrinsicElement.md +0 -5
  480. package/docs/api/types/DedentIntrinsicElement.md +0 -5
  481. package/docs/api/types/DedentToRootIntrinsicElement.md +0 -5
  482. package/docs/api/types/ElementCache.md +0 -5
  483. package/docs/api/types/ElementCacheKey.md +0 -5
  484. package/docs/api/types/FillIntrinsicElement.md +0 -5
  485. package/docs/api/types/GroupIntrinsicElement.md +0 -5
  486. package/docs/api/types/HardlineIntrinsicElement.md +0 -5
  487. package/docs/api/types/HbrIntrinsicElement.md +0 -5
  488. package/docs/api/types/IfBreakIntrinsicElement.md +0 -5
  489. package/docs/api/types/IndentIfBreakIntrinsicElement.md +0 -5
  490. package/docs/api/types/IndentIntrinsicElement.md +0 -5
  491. package/docs/api/types/IntrinsicElement.md +0 -5
  492. package/docs/api/types/IntrinsicElementBase.md +0 -9
  493. package/docs/api/types/LbrIntrinsicElement.md +0 -5
  494. package/docs/api/types/LineIntrinsicElement.md +0 -5
  495. package/docs/api/types/LineSuffixBoundaryIntrinsicElement.md +0 -5
  496. package/docs/api/types/LineSuffixIntrinsicElement.md +0 -5
  497. package/docs/api/types/LiterallineIntrinsicElement.md +0 -5
  498. package/docs/api/types/MarkAsRootIntrinsicElement.md +0 -5
  499. package/docs/api/types/PrintHook.md +0 -10
  500. package/docs/api/types/RenderedTextTree.md +0 -5
  501. package/docs/api/types/SbrIntrinsicElement.md +0 -5
  502. package/docs/api/types/SoftlineIntrinsicElement.md +0 -5
  503. package/docs/api/variables/intrinsicElementKey.md +0 -5
  504. package/docs/api/variables/printHookTag.md +0 -7
  505. package/src/print-hook.ts +0 -22
  506. package/src/render.ts +0 -1154
@@ -1,3 +1,50 @@
1
+ /**
2
+ * Debug-render module — bridges the AlloyNode render tree to the
3
+ * trace-writer SQLite DB and the devtools WebSocket protocol.
4
+ *
5
+ * # Tree shape
6
+ *
7
+ * The trace tree mirrors the real AlloyNode tree. Component invocations
8
+ * are recorded separately as canonical metadata (`component_instances`
9
+ * and `component_roots`) so devtools can derive presentation groupings
10
+ * without storing UI artifacts as render nodes.
11
+ *
12
+ * Real AlloyNodes are exposed as:
13
+ *
14
+ * - `TextNode` → `kind: "text"`, with `value: data`
15
+ * - `ElementNode` with a marker `localName` (`alloy:source-file`,
16
+ * `alloy:directory`, `alloy:copy-file`) → `kind` set to the marker
17
+ * - any other `ElementNode` → `kind: "intrinsic"` with `name: localName`
18
+ * - `FragmentNode` → `kind: "fragment"`
19
+ * - `CommentNode` (slot/ctx markers) → not exposed (skipped)
20
+ *
21
+ * # Hook surface
22
+ *
23
+ * Mutation hooks:
24
+ * - `nodeAttached(node, parent, before)` — called from
25
+ * `render/node.ts::insertBefore`. Emits a `node_added` event for
26
+ * `node` (and any descendants it brings with it, e.g. a previously
27
+ * built subtree being moved or a fragment splice).
28
+ * - `nodeDetached(node, parent)` — called from
29
+ * `render/node.ts::detach`. Emits `node_removed` for `node` and all
30
+ * descendants.
31
+ *
32
+ * Lifecycle hooks:
33
+ * - `initialize(root)` — called at the start of each `render` /
34
+ * `renderAsync`. Resets module state and emits `render:reset`.
35
+ * - `complete()` — emits `render:complete`.
36
+ * - `flushJobsComplete()` — emits `flushJobs:complete`.
37
+ * - `error(info, stack)` — emits `render:error` and writes a
38
+ * `render_errors` row.
39
+ *
40
+ * Component hooks:
41
+ * - `beginComponent(opts)` — records a component instance and captures
42
+ * the real top-level AlloyNodes it emits.
43
+ *
44
+ * Each hook short-circuits when neither devtools nor the trace DB
45
+ * are enabled.
46
+ */
47
+
1
48
  import { watch } from "@vue/reactivity";
2
49
  import * as devalue from "devalue";
3
50
  import type {
@@ -8,34 +55,48 @@ import {
8
55
  isDevtoolsEnabled,
9
56
  registerDevtoolsMessageHandler,
10
57
  } from "../devtools/devtools-server.js";
11
- import {
12
- isPrintHook,
13
- type PrintHook,
14
- type RenderedTextTree,
15
- } from "../print-hook.js";
16
58
  import { getContext, untrack } from "../reactivity.js";
17
- import type { ComponentCreator } from "../runtime/component.js";
59
+ import { getContextForNode } from "../render/node-context.js";
60
+ import {
61
+ AlloyNode,
62
+ CommentNode,
63
+ ELEMENT_NODE,
64
+ ElementNode,
65
+ FRAGMENT_NODE,
66
+ setMutationListener,
67
+ TEXT_NODE,
68
+ TextNode,
69
+ } from "../render/node.js";
18
70
  import { flushJobsAsync } from "../scheduler.js";
71
+ import type { ComponentCreator } from "./../runtime/component.js";
72
+ import {
73
+ flushAllDirtyFiles,
74
+ markFileDirtyForNode,
75
+ reset as resetFileStreaming,
76
+ } from "./file-streaming.js";
19
77
  import { sanitizeRecord } from "./serialize.js";
20
78
  import { resolveComponentSource } from "./source-map.js";
21
79
  import {
22
- deleteDirectory,
23
- deleteOutputFile,
80
+ deleteComponentInstance,
81
+ deleteComponentRoot,
82
+ insertComponentInstance,
83
+ insertComponentRoot,
24
84
  insertDirectory,
25
85
  insertOutputFile,
26
86
  insertRenderError,
27
- insertRenderNode,
28
87
  isTraceEnabled,
29
88
  notifyFlushComplete,
30
89
  notifyRenderComplete,
31
90
  notifyRenderReset,
32
91
  deleteRenderNode as traceDeleteRenderNode,
33
- updateRenderNodeProps as traceUpdateRenderNodeProps,
92
+ insertRenderNode as traceInsertRenderNode,
93
+ updateComponentInstanceProps,
34
94
  updateEffectComponentByContext,
35
- updateRenderNodeContext,
36
95
  } from "./trace-writer.js";
37
96
  import { isDebugEnabled, logDevtoolsMessage } from "./trace.js";
38
97
 
98
+ // #region Public debug types used by runtime and devtools integration
99
+
39
100
  /** The kind discriminant for render tree nodes. */
40
101
  export type RenderTreeNodeKind = RenderTreeNode["kind"];
41
102
 
@@ -54,14 +115,17 @@ export interface RenderNodeActions {
54
115
  }
55
116
 
56
117
  export interface BeginComponentOptions {
57
- parent: RenderedTextTree;
58
- index: number;
59
- node: RenderedTextTree;
60
118
  component: ComponentCreator<unknown>;
61
119
  propsSource: Record<string, unknown> | undefined;
62
120
  source: RenderTreeNodeInfo["source"] | undefined;
63
- isExisting: boolean;
64
- actions: RenderNodeActions;
121
+ /** Parent AlloyNode the component is being inserted into. */
122
+ parent: AlloyNode;
123
+ /** Optional rerender bindings (only meaningful in devtools). */
124
+ actions?: RenderNodeActions;
125
+ }
126
+
127
+ export function isRerenderEnabled(): boolean {
128
+ return isDevtoolsEnabled();
65
129
  }
66
130
 
67
131
  export interface ComponentDebugSession {
@@ -70,118 +134,145 @@ export interface ComponentDebugSession {
70
134
  dispose(): void;
71
135
  }
72
136
 
73
- /** Any node tracked by the devtools render tree. */
74
- type TrackedNode = RenderedTextTree | PrintHook;
137
+ export interface RenderErrorInfo {
138
+ name: string;
139
+ message: string;
140
+ stack?: string;
141
+ }
142
+
143
+ export interface RenderErrorStackEntry extends ProtocolRenderErrorStackEntry {
144
+ props?: Record<string, unknown> | undefined;
145
+ }
146
+
147
+ // #endregion
75
148
 
76
- // ─────────────────────────────────────────────────────────────────────────────
77
- // Module state — reset in initialize()
78
- // ─────────────────────────────────────────────────────────────────────────────
149
+ // #region Module state
79
150
 
80
- let nodeIds = new WeakMap<TrackedNode, number>();
81
- let idToNode = new Map<number, TrackedNode>();
82
- let entryIds = new WeakMap<RenderedTextTree, number[]>();
83
- let nodeKinds = new WeakMap<
84
- TrackedNode,
151
+ /** Map AlloyNode trace node id (assigned when first emitted). */
152
+ let nodeIds = new WeakMap<AlloyNode, number>();
153
+ /** AlloyNodes that have been emitted to the trace tree. */
154
+ let tracked = new WeakSet<AlloyNode>();
155
+ /** Component owner captured while a subtree is built before being rooted. */
156
+ let pendingOwnerComponent = new WeakMap<AlloyNode, ComponentFrame>();
157
+ /** Component roots captured while a subtree is built before being rooted. */
158
+ let pendingRootComponents = new WeakMap<AlloyNode, ComponentFrame[]>();
159
+ /** Sidecar metadata per node id — kind + name + source for cleanup re-emit. */
160
+ let nodeKinds = new Map<
161
+ number,
85
162
  { kind: string; name?: string; source?: RenderTreeNodeInfo["source"] }
86
163
  >();
164
+ /** Sidecar: nodes that own a file/directory entry in the trace DB. */
87
165
  let fileNodes = new Map<number, { path: string; filetype: string }>();
88
166
  let directoryNodes = new Map<number, { path: string }>();
167
+ /** Latest props-serialized value per id (for de-duplicated updates). */
89
168
  let nodeProps = new Map<number, string | undefined>();
169
+ /** Devtools-side rerender bindings keyed by render-tree node id. */
90
170
  let rerenderActions = new Map<number, RenderNodeActions>();
91
- let nextId = 1;
92
- let handlerRegistered = false;
93
171
 
94
- // ─────────────────────────────────────────────────────────────────────────────
95
- // Props serialization
96
- // ─────────────────────────────────────────────────────────────────────────────
172
+ type ComponentFrame = {
173
+ id: number;
174
+ parentComponentId: number | null;
175
+ hostParent: AlloyNode;
176
+ name: string;
177
+ propsSerialized: string | undefined;
178
+ source: RenderTreeNodeInfo["source"] | undefined;
179
+ contextId: number | null;
180
+ roots: number[];
181
+ rootSet: Set<number>;
182
+ file?: { path: string; filetype: string };
183
+ directory?: { path: string };
184
+ actions?: RenderNodeActions;
185
+ stopWatch?: () => void;
186
+ };
187
+ let componentStack: ComponentFrame[] = [];
188
+ let componentsById = new Map<number, ComponentFrame>();
189
+ let componentByContextId = new Map<number, ComponentFrame>();
190
+ let ownerComponentByNodeId = new Map<number, number>();
191
+ let childComponentsById = new Map<number, Set<number>>();
192
+ let rootComponentsByNodeId = new Map<number, Set<number>>();
97
193
 
98
- function serializeRenderTreeProps(input: Record<string, unknown> | undefined) {
99
- return untrack(() => {
100
- if (!input) return undefined;
101
- const { children: _children, ...rest } = input;
102
- const sanitized = sanitizeRecord(rest);
103
- if (!sanitized) return undefined;
104
- try {
105
- return devalue.stringify(sanitized);
106
- } catch {
107
- return undefined;
194
+ /**
195
+ * Reverse index: parent render-tree id → set of currently-emitted child
196
+ * render-tree ids. Maintained by every `insertRenderNode` call in this
197
+ * module, and used by `emitRemoved` to cascade-remove descendants whose
198
+ * lifetime is bound to the removed parent.
199
+ */
200
+ let renderChildIds = new Map<number, Set<number>>();
201
+
202
+ function insertRenderNode(
203
+ id: number,
204
+ parentId: number | null,
205
+ kind: string,
206
+ name: string | undefined,
207
+ props: string | undefined,
208
+ sourceFile: string | undefined,
209
+ sourceLine: number | undefined,
210
+ sourceCol: number | undefined,
211
+ contextId: number | null,
212
+ value: string | undefined,
213
+ ): void {
214
+ if (parentId !== null) {
215
+ let set = renderChildIds.get(parentId);
216
+ if (!set) {
217
+ set = new Set();
218
+ renderChildIds.set(parentId, set);
108
219
  }
109
- });
220
+ set.add(id);
221
+ }
222
+ traceInsertRenderNode(
223
+ id,
224
+ parentId,
225
+ kind,
226
+ name,
227
+ props,
228
+ sourceFile,
229
+ sourceLine,
230
+ sourceCol,
231
+ contextId,
232
+ value,
233
+ );
110
234
  }
111
235
 
112
- // ─────────────────────────────────────────────────────────────────────────────
113
- // Node ID management & tree structure
114
- // ─────────────────────────────────────────────────────────────────────────────
115
-
116
- function emitNodeRemoved(parentId: number | null, id: number) {
117
- clearRenderTreeChildrenForId(id);
118
- traceDeleteRenderNode(id);
119
-
120
- rerenderActions.delete(id);
121
- nodeProps.delete(id);
122
- idToNode.delete(id);
236
+ let nextId = 1;
237
+ let nextErrorId = 1;
238
+ let handlerRegistered = false;
123
239
 
124
- const fileInfo = fileNodes.get(id);
125
- if (fileInfo) {
126
- deleteOutputFile(fileInfo.path);
127
- fileNodes.delete(id);
128
- }
240
+ // #endregion
129
241
 
130
- const dirInfo = directoryNodes.get(id);
131
- if (dirInfo) {
132
- deleteDirectory(dirInfo.path);
133
- directoryNodes.delete(id);
134
- }
135
- }
242
+ // #region Lifecycle
136
243
 
137
- function getEntryList(parent: RenderedTextTree) {
138
- let list = entryIds.get(parent);
139
- if (!list) {
140
- list = [];
141
- entryIds.set(parent, list);
244
+ export function initialize(root: AlloyNode) {
245
+ for (const frame of componentsById.values()) {
246
+ frame.stopWatch?.();
142
247
  }
143
- return list;
144
- }
145
-
146
- function getOrCreateNodeId(node: TrackedNode) {
147
- const existing = nodeIds.get(node);
148
- if (existing) {
149
- // Restore reverse mapping — emitNodeRemoved deletes idToNode but nodeIds
150
- // (WeakMap) survives. Without this, clearRenderTreeChildrenForId can't
151
- // find the node on the next cleanup, leaving orphaned children in the DB.
152
- idToNode.set(existing, node);
153
- return existing;
248
+ if (!isDebugEnabled()) {
249
+ setMutationListener(null);
250
+ return;
154
251
  }
155
- const id = nextId++;
156
- nodeIds.set(node, id);
157
- idToNode.set(id, node);
158
- return id;
159
- }
160
-
161
- export function getRenderNodeId(node: RenderedTextTree | PrintHook) {
162
- if (!isDebugEnabled()) return undefined;
163
- return getOrCreateNodeId(node);
164
- }
165
-
166
- function setEntryId(parent: RenderedTextTree, index: number, id: number) {
167
- const list = getEntryList(parent);
168
- list[index] = id;
169
- }
170
-
171
- export function initialize(root: RenderedTextTree) {
172
- if (!isDebugEnabled()) return;
173
252
  ensureDevtoolsHandler();
174
253
  nodeIds = new WeakMap();
175
- idToNode = new Map();
176
- entryIds = new WeakMap();
177
- nodeKinds = new WeakMap();
254
+ tracked = new WeakSet();
255
+ pendingOwnerComponent = new WeakMap();
256
+ pendingRootComponents = new WeakMap();
257
+ renderChildIds = new Map();
258
+ nodeKinds = new Map();
178
259
  fileNodes = new Map();
179
260
  directoryNodes = new Map();
180
261
  nodeProps = new Map();
181
262
  rerenderActions = new Map();
263
+ componentStack = [];
264
+ componentsById = new Map();
265
+ componentByContextId = new Map();
266
+ ownerComponentByNodeId = new Map();
267
+ childComponentsById = new Map();
268
+ rootComponentsByNodeId = new Map();
182
269
  nextId = 1;
270
+ resetFileStreaming();
271
+ setMutationListener({ attached: nodeAttached, detached: nodeDetached });
183
272
  notifyRenderReset();
184
273
  const rootId = getOrCreateNodeId(root);
274
+ tracked.add(root);
275
+ nodeKinds.set(rootId, { kind: "root" });
185
276
  insertRenderNode(
186
277
  rootId,
187
278
  null,
@@ -196,346 +287,402 @@ export function initialize(root: RenderedTextTree) {
196
287
  );
197
288
  }
198
289
 
199
- export function registerRenderNodeActions(
200
- node: RenderedTextTree | PrintHook,
201
- actions: RenderNodeActions,
202
- ) {
203
- if (!isDebugEnabled()) return;
204
- const id = getOrCreateNodeId(node);
205
- rerenderActions.set(id, actions);
290
+ export function complete() {
291
+ flushAllDirtyFiles();
292
+ logDevtoolsMessage({ type: "render:complete" });
293
+ notifyRenderComplete();
206
294
  }
207
295
 
208
- export function unregisterRenderNodeActions(
209
- node: RenderedTextTree | PrintHook,
210
- ) {
211
- if (!isDebugEnabled()) return;
212
- const id = nodeIds.get(node);
213
- if (id !== undefined) {
214
- rerenderActions.delete(id);
296
+ export function flushJobsComplete() {
297
+ logDevtoolsMessage({ type: "flushJobs:complete" });
298
+ notifyFlushComplete();
299
+ }
300
+
301
+ // #endregion
302
+
303
+ // #region Node-id management
304
+
305
+ function getOrCreateNodeId(node: AlloyNode): number {
306
+ const existing = nodeIds.get(node);
307
+ if (existing !== undefined) return existing;
308
+ const id = nextId++;
309
+ nodeIds.set(node, id);
310
+ return id;
311
+ }
312
+
313
+ export function getRenderNodeId(node: AlloyNode | unknown): number | undefined {
314
+ if (!isDebugEnabled()) return undefined;
315
+ if (!(node instanceof AlloyNode)) return undefined;
316
+ return nodeIds.get(node);
317
+ }
318
+
319
+ // #endregion
320
+
321
+ // #region Kind classification
322
+
323
+ const SKIPPED_COMMENT_DATAS = new Set([
324
+ "slot:start",
325
+ "slot:end",
326
+ "slot:item:start",
327
+ "slot:item:end",
328
+ "ctx:start",
329
+ "ctx:end",
330
+ "component:start",
331
+ "component:end",
332
+ ]);
333
+
334
+ function shouldExposeNode(node: AlloyNode): boolean {
335
+ if (node instanceof CommentNode) {
336
+ // Bookkeeping comments are not part of the render tree.
337
+ return !SKIPPED_COMMENT_DATAS.has(node.data);
215
338
  }
339
+ return true;
216
340
  }
217
341
 
218
- function ensureDevtoolsHandler() {
219
- if (handlerRegistered || !isDevtoolsEnabled()) return;
220
- handlerRegistered = true;
221
- registerDevtoolsMessageHandler((message) => {
342
+ function classifyNode(node: AlloyNode): {
343
+ kind: string;
344
+ name?: string;
345
+ value?: string;
346
+ } {
347
+ const t = node.nodeType;
348
+ if (t === TEXT_NODE) {
349
+ return { kind: "text", value: (node as TextNode).data };
350
+ }
351
+ if (t === FRAGMENT_NODE) {
352
+ return { kind: "fragment" };
353
+ }
354
+ if (t === ELEMENT_NODE) {
355
+ const ln = (node as ElementNode).localName;
222
356
  if (
223
- message.type !== "render:rerender" &&
224
- message.type !== "render:rerenderAndBreak"
357
+ ln === "alloy:source-file" ||
358
+ ln === "alloy:directory" ||
359
+ ln === "alloy:copy-file"
225
360
  ) {
226
- return;
227
- }
228
- const rawId = (message as { id?: unknown }).id;
229
- if (typeof rawId !== "number" && typeof rawId !== "string") return;
230
- const id = Number(rawId);
231
- if (!Number.isFinite(id)) return;
232
- const actions = rerenderActions.get(id);
233
- if (!actions) return;
234
- if (message.type === "render:rerender") {
235
- actions.rerender();
236
- } else {
237
- actions.rerenderAndBreak();
361
+ return { kind: ln.slice("alloy:".length), name: ln };
238
362
  }
239
- void flushJobsAsync();
240
- });
363
+ return { kind: "intrinsic", name: ln };
364
+ }
365
+ // CommentNode that's exposed — generic "comment" kind. Marker
366
+ // comments are filtered out by `shouldExposeNode`, so this is rare.
367
+ return { kind: "comment", value: (node as CommentNode).data };
241
368
  }
242
369
 
243
- export function recordTextNode(
244
- parent: RenderedTextTree,
245
- index: number,
246
- value: string,
247
- ) {
248
- if (!isDebugEnabled()) return;
249
- const id = nextId++;
250
- setEntryId(parent, index, id);
251
- insertRenderNode(
252
- id,
253
- getOrCreateNodeId(parent),
254
- "text",
255
- undefined,
256
- undefined,
257
- undefined,
258
- undefined,
259
- undefined,
260
- null,
261
- value,
262
- );
370
+ // #endregion
371
+
372
+ // #region Component ownership resolution
373
+
374
+ function currentComponentFrame(): ComponentFrame | undefined {
375
+ return componentStack[componentStack.length - 1];
263
376
  }
264
377
 
265
- function recordNodeAdded(
266
- parent: RenderedTextTree,
267
- index: number,
268
- node: RenderedTextTree | PrintHook,
269
- info: RenderTreeNodeInfo,
270
- ) {
271
- if (!isDebugEnabled()) return;
272
- const id = getOrCreateNodeId(node);
273
- if (info.propsSerialized !== undefined) {
274
- nodeProps.set(id, info.propsSerialized);
275
- }
276
- // Remember the kind and source so cached re-adds preserve them
277
- nodeKinds.set(node, {
278
- kind: info.kind,
279
- name: info.name,
280
- source: info.source,
281
- });
282
- setEntryId(parent, index, id);
283
- insertRenderNode(
284
- id,
285
- getOrCreateNodeId(parent),
286
- info.kind,
287
- info.name,
288
- info.propsSerialized,
289
- info.source?.fileName,
290
- info.source?.lineNumber,
291
- info.source?.columnNumber,
292
- null,
293
- undefined,
294
- );
378
+ function currentContextComponentFrame(): ComponentFrame | undefined {
379
+ const ctx = getContext();
380
+ return ctx ? componentByContextId.get(ctx.id) : undefined;
295
381
  }
296
382
 
297
- function recordSubtreeAdded(
298
- parentNode: RenderedTextTree | PrintHook,
299
- subtree: RenderedTextTree,
300
- info: RenderTreeNodeInfo = { kind: "fragment" },
301
- ) {
302
- if (!isDebugEnabled()) return;
303
- const parentId = getOrCreateNodeId(parentNode);
304
- // Check if this node was previously rendered (cached) by seeing if it already has an ID
305
- const existingId = nodeIds.get(subtree);
306
- const isCached = existingId !== undefined;
307
- const id = isCached ? existingId : getOrCreateNodeId(subtree);
308
- // Merge source from previously-saved nodeKinds if the caller didn't provide one
309
- const savedKind = nodeKinds.get(subtree);
310
- const source = info.source ?? savedKind?.source;
311
- // Remember the kind and source so cached re-adds preserve them
312
- nodeKinds.set(subtree, { kind: info.kind, name: info.name, source });
313
- // Track in entryIds so clearRenderTreeChildren can find and remove it
314
- if (Array.isArray(parentNode)) {
315
- const list = getEntryList(parentNode);
316
- list.push(id);
383
+ function findRootComponentsForAttach(parent: AlloyNode): ComponentFrame[] {
384
+ const frames: ComponentFrame[] = [];
385
+ for (let i = componentStack.length - 1; i >= 0; i--) {
386
+ const frame = componentStack[i];
387
+ if (frame.hostParent === parent) frames.unshift(frame);
388
+ }
389
+ return frames;
390
+ }
391
+
392
+ function ownerFrameForAttach(
393
+ parentId: number | null,
394
+ inheritedOwner: ComponentFrame | undefined,
395
+ ): ComponentFrame | undefined {
396
+ if (inheritedOwner) return inheritedOwner;
397
+ const active = currentComponentFrame();
398
+ if (active) return active;
399
+ const contextFrame = currentContextComponentFrame();
400
+ if (contextFrame) return contextFrame;
401
+ if (parentId !== null) {
402
+ const parentOwner = ownerComponentByNodeId.get(parentId);
403
+ if (parentOwner !== undefined) return componentsById.get(parentOwner);
404
+ }
405
+ return undefined;
406
+ }
407
+
408
+ function recordComponentRoot(
409
+ frame: ComponentFrame,
410
+ renderNodeId: number,
411
+ ): void {
412
+ if (frame.rootSet.has(renderNodeId)) return;
413
+ frame.rootSet.add(renderNodeId);
414
+ const ordinal = frame.roots.length;
415
+ frame.roots.push(renderNodeId);
416
+ let components = rootComponentsByNodeId.get(renderNodeId);
417
+ if (!components) {
418
+ components = new Set();
419
+ rootComponentsByNodeId.set(renderNodeId, components);
420
+ }
421
+ components.add(frame.id);
422
+ insertComponentRoot(frame.id, renderNodeId, ordinal);
423
+ if (ordinal === 0) {
424
+ if (frame.file) {
425
+ fileNodes.set(renderNodeId, frame.file);
426
+ insertOutputFile(frame.file.path, frame.file.filetype, renderNodeId);
427
+ }
428
+ if (frame.directory) {
429
+ directoryNodes.set(renderNodeId, frame.directory);
430
+ }
431
+ }
432
+ if (frame.actions) rerenderActions.set(renderNodeId, frame.actions);
433
+ }
434
+
435
+ function removeComponentRoot(
436
+ frame: ComponentFrame,
437
+ renderNodeId: number,
438
+ deleteWhenEmpty = true,
439
+ ): void {
440
+ if (!frame.rootSet.delete(renderNodeId)) return;
441
+ frame.roots = frame.roots.filter((id) => id !== renderNodeId);
442
+ const components = rootComponentsByNodeId.get(renderNodeId);
443
+ if (components) {
444
+ components.delete(frame.id);
445
+ if (components.size === 0) rootComponentsByNodeId.delete(renderNodeId);
446
+ }
447
+ deleteComponentRoot(frame.id, renderNodeId);
448
+ if (deleteWhenEmpty && frame.rootSet.size === 0) {
449
+ deleteComponentFrame(frame);
450
+ }
451
+ }
452
+
453
+ function deleteComponentFrame(frame: ComponentFrame): void {
454
+ if (!componentsById.delete(frame.id)) return;
455
+
456
+ const children = childComponentsById.get(frame.id);
457
+ if (children) {
458
+ childComponentsById.delete(frame.id);
459
+ for (const childId of [...children]) {
460
+ const child = componentsById.get(childId);
461
+ if (child) deleteComponentFrame(child);
462
+ }
463
+ }
464
+
465
+ for (const rootId of [...frame.roots]) {
466
+ removeComponentRoot(frame, rootId, false);
317
467
  }
318
- insertRenderNode(
319
- id,
320
- parentId,
321
- info.kind,
322
- info.name,
323
- info.propsSerialized,
324
- source?.fileName,
325
- source?.lineNumber,
326
- source?.columnNumber,
327
- null,
328
- undefined,
329
- );
330
468
 
331
- // For cached nodes, we need to recursively re-add all their children since
332
- // clearRenderTreeChildren removed them when the parent re-rendered
333
- if (isCached) {
334
- recordCachedSubtreeChildrenRecursively(subtree);
469
+ if (frame.parentComponentId !== null) {
470
+ const siblings = childComponentsById.get(frame.parentComponentId);
471
+ siblings?.delete(frame.id);
335
472
  }
473
+ if (
474
+ frame.contextId !== null &&
475
+ componentByContextId.get(frame.contextId)?.id === frame.id
476
+ ) {
477
+ componentByContextId.delete(frame.contextId);
478
+ }
479
+ nodeProps.delete(frame.id);
480
+ frame.stopWatch?.();
481
+ deleteComponentInstance(frame.id);
336
482
  }
337
483
 
338
484
  /**
339
- * Recursively re-adds all children of a cached render tree node to devtools.
340
- * This is needed because clearRenderTreeChildren recursively removes all
341
- * descendants, but cached nodes aren't re-rendered so their children need
342
- * to be explicitly re-added.
485
+ * Decide whether `attachedParent` is currently part of the live trace
486
+ * tree (rooted at the initialized root), and if so which trace node id
487
+ * should be the new node's parent.
488
+ *
489
+ * Returns `undefined` when `attachedParent` isn't tracked — the caller
490
+ * should defer emission until the subtree is later attached at a
491
+ * tracked location.
343
492
  */
344
- function recordCachedSubtreeChildrenRecursively(node: RenderedTextTree) {
345
- if (!isDebugEnabled()) return;
346
- const parentId = getOrCreateNodeId(node);
347
-
348
- // Clear any previously-recorded children from the DB before re-adding.
349
- // The in-memory tree may have changed since the last cached re-add
350
- // (e.g., a memo inside the cached subtree re-ran), so old DB children
351
- // that are no longer in the tree would become orphans.
352
- clearRenderTreeChildren(node);
353
-
354
- // Rebuild the entryIds for this node
355
- const list = getEntryList(node);
356
- list.length = 0;
357
-
358
- for (let i = 0; i < node.length; i++) {
359
- const child = node[i];
360
- if (typeof child === "string") {
361
- // Text nodes - re-record them with new IDs
362
- if (child !== "") {
363
- const id = nextId++;
364
- list.push(id);
365
- idToNode.set(id, child as unknown as RenderedTextTree);
366
- insertRenderNode(
367
- id,
368
- parentId,
369
- "text",
370
- undefined,
371
- undefined,
372
- undefined,
373
- undefined,
374
- undefined,
375
- null,
376
- child,
377
- );
378
- }
379
- } else if (Array.isArray(child)) {
380
- // Nested RenderedTextTree - record and recurse, preserving original kind and source
381
- const id = getOrCreateNodeId(child);
382
- list.push(id);
383
- const savedKind = nodeKinds.get(child);
384
- insertRenderNode(
385
- id,
386
- parentId,
387
- savedKind?.kind ?? "fragment",
388
- savedKind?.name,
389
- undefined,
390
- savedKind?.source?.fileName,
391
- savedKind?.source?.lineNumber,
392
- savedKind?.source?.columnNumber,
393
- null,
394
- undefined,
395
- );
396
- recordCachedSubtreeChildrenRecursively(child);
397
- } else if (isPrintHook(child)) {
398
- // PrintHook - record and recurse into subtree
399
- const id = getOrCreateNodeId(child);
400
- list.push(id);
401
- insertRenderNode(
402
- id,
403
- parentId,
404
- "printHook",
405
- (child as { name?: string }).name ?? "hook",
406
- undefined,
407
- undefined,
408
- undefined,
409
- undefined,
410
- null,
411
- undefined,
412
- );
413
- if (child.subtree) {
414
- const subtreeId = getOrCreateNodeId(child.subtree);
415
- const hookList = getEntryList(child as unknown as RenderedTextTree);
416
- hookList.length = 0;
417
- hookList.push(subtreeId);
418
- insertRenderNode(
419
- subtreeId,
420
- id,
421
- "fragment",
422
- undefined,
423
- undefined,
424
- undefined,
425
- undefined,
426
- undefined,
427
- null,
428
- undefined,
429
- );
430
- recordCachedSubtreeChildrenRecursively(child.subtree);
431
- }
432
- }
493
+ function resolveParentId(
494
+ node: AlloyNode,
495
+ attachedParent: AlloyNode,
496
+ ): number | null | undefined {
497
+ if (tracked.has(attachedParent)) {
498
+ const id = nodeIds.get(attachedParent);
499
+ return id === undefined ? null : id;
433
500
  }
501
+ return undefined;
434
502
  }
435
503
 
436
- function recordNodePropsUpdated(
437
- node: RenderedTextTree | PrintHook,
438
- propsSerialized: string | undefined,
439
- ) {
440
- if (!isDebugEnabled()) return;
441
- const id = getOrCreateNodeId(node);
442
- const previous = nodeProps.get(id);
443
- if (previous === propsSerialized) return;
444
- nodeProps.set(id, propsSerialized);
445
- traceUpdateRenderNodeProps(id, propsSerialized);
446
- }
504
+ // #endregion
505
+
506
+ // #region Tree-mutation hooks
447
507
 
448
- function clearRenderTreeChildren(parent: RenderedTextTree) {
508
+ /**
509
+ * Called after `node` is attached as a child of `parent`. Emits an
510
+ * `added` event for `node` itself (if exposed) and recursively for any
511
+ * descendants it brought with it (move / fragment splice / cached subtree).
512
+ *
513
+ * If `parent` isn't part of the live tree yet, stamps the active component
514
+ * ownership onto `node` and defers emission until the subtree finally attaches.
515
+ */
516
+ export function nodeAttached(node: AlloyNode, parent: AlloyNode): void {
449
517
  if (!isDebugEnabled()) return;
450
- const list = entryIds.get(parent);
451
- if (!list || list.length === 0) return;
452
- const parentId = getOrCreateNodeId(parent);
453
- for (const id of list) {
454
- if (id !== undefined) {
455
- emitNodeRemoved(parentId, id);
456
- }
518
+ markFileDirtyForNode(parent);
519
+ const parentId = resolveParentId(node, parent);
520
+ const ownerFrame = currentComponentFrame() ?? currentContextComponentFrame();
521
+ let rootFrames = findRootComponentsForAttach(parent);
522
+ if (
523
+ rootFrames.length === 0 &&
524
+ ownerFrame !== undefined &&
525
+ parentId !== undefined &&
526
+ (parentId === null ||
527
+ ownerComponentByNodeId.get(parentId) !== ownerFrame.id)
528
+ ) {
529
+ rootFrames = [ownerFrame];
457
530
  }
458
- entryIds.set(parent, []);
459
- }
460
-
461
- function clearRenderTreeChildrenForId(id: number) {
462
- const node = idToNode.get(id);
463
- if (!node) return;
464
- if (Array.isArray(node)) {
465
- clearRenderTreeChildren(node);
531
+ if (parentId === undefined) {
532
+ if (ownerFrame) pendingOwnerComponent.set(node, ownerFrame);
533
+ if (rootFrames.length > 0) pendingRootComponents.set(node, rootFrames);
534
+ return;
535
+ }
536
+ attachWithSelf(node, parentId, ownerFrame, rootFrames);
537
+ }
538
+
539
+ function attachWithSelf(
540
+ node: AlloyNode,
541
+ parentId: number | null,
542
+ inheritedOwner: ComponentFrame | undefined,
543
+ inheritedRoots: ComponentFrame[],
544
+ ): void {
545
+ const pendingOwner = pendingOwnerComponent.get(node);
546
+ if (pendingOwner !== undefined) {
547
+ pendingOwnerComponent.delete(node);
548
+ }
549
+ const pendingRoots = pendingRootComponents.get(node);
550
+ if (pendingRoots !== undefined) {
551
+ pendingRootComponents.delete(node);
552
+ }
553
+ const ownerFrame = ownerFrameForAttach(
554
+ parentId,
555
+ pendingOwner ?? inheritedOwner,
556
+ );
557
+ const rootFrames = pendingRoots ?? inheritedRoots;
558
+ if (!shouldExposeNode(node)) {
559
+ for (let c = node.firstChild; c !== null; c = c.nextSibling) {
560
+ attachWithSelf(c, parentId, ownerFrame, rootFrames);
561
+ }
466
562
  return;
467
563
  }
468
- // For PrintHook nodes: clear the subtree's children, then remove
469
- // the subtree node itself (which is a child of this hook).
470
- const subtree = (node as { subtree?: RenderedTextTree }).subtree;
471
- if (subtree && Array.isArray(subtree)) {
472
- clearRenderTreeChildren(subtree);
473
- const subtreeId = nodeIds.get(subtree);
474
- if (subtreeId !== undefined) {
475
- emitNodeRemoved(id, subtreeId);
564
+ if (tracked.has(node)) {
565
+ // Already eagerly emitted (e.g. an alloy:* wrapper pre-emitted via
566
+ // ensureWrapperHostTracked while inside a thunk body). Don't re-emit;
567
+ // just walk newly-attached children that may not have been seen yet.
568
+ const existingId = nodeIds.get(node)!;
569
+ for (let c = node.firstChild; c !== null; c = c.nextSibling) {
570
+ if (!tracked.has(c)) {
571
+ attachWithSelf(c, existingId, ownerFrame, []);
572
+ }
476
573
  }
574
+ return;
575
+ }
576
+ const id = getOrCreateNodeId(node);
577
+ emitAdded(node, id, parentId);
578
+ tracked.add(node);
579
+ if (ownerFrame !== undefined) {
580
+ ownerComponentByNodeId.set(id, ownerFrame.id);
581
+ }
582
+ for (const rootFrame of rootFrames) {
583
+ recordComponentRoot(rootFrame, id);
584
+ }
585
+ for (let c = node.firstChild; c !== null; c = c.nextSibling) {
586
+ if (!tracked.has(c)) attachWithSelf(c, id, ownerFrame, []);
477
587
  }
478
588
  }
479
589
 
480
- // ─────────────────────────────────────────────────────────────────────────────
481
- // File / directory node tracking
482
- // ─────────────────────────────────────────────────────────────────────────────
483
-
484
- function recordDirectoryNode(node: RenderedTextTree, path: string) {
485
- if (!isDebugEnabled()) return;
486
- const id = getOrCreateNodeId(node);
487
- if (directoryNodes.has(id)) return;
488
- directoryNodes.set(id, { path });
489
- insertDirectory(path);
590
+ function emitAdded(node: AlloyNode, id: number, parentId: number | null): void {
591
+ const cls = classifyNode(node);
592
+ // Stamp metadata so cleanup re-emit (and `error()` stack resolution)
593
+ // can recover identity.
594
+ let entry = nodeKinds.get(id);
595
+ if (!entry) {
596
+ entry = { kind: cls.kind, name: cls.name };
597
+ nodeKinds.set(id, entry);
598
+ } else {
599
+ entry.kind = cls.kind;
600
+ if (cls.name !== undefined) entry.name = cls.name;
601
+ }
602
+ const props = nodeProps.get(id);
603
+ insertRenderNode(
604
+ id,
605
+ parentId,
606
+ cls.kind,
607
+ cls.name,
608
+ props,
609
+ entry.source?.fileName,
610
+ entry.source?.lineNumber,
611
+ entry.source?.columnNumber,
612
+ getContextForNode(node)?.id ?? null,
613
+ cls.value,
614
+ );
490
615
  }
491
616
 
492
- function recordFileNode(
493
- node: RenderedTextTree,
494
- path: string,
495
- filetype: string,
496
- ) {
617
+ /**
618
+ * Called after `node` has been detached. Emits `node_removed` for
619
+ * `node` and all of its still-attached descendants in post-order.
620
+ */
621
+ export function nodeDetached(node: AlloyNode, formerParent: AlloyNode): void {
497
622
  if (!isDebugEnabled()) return;
498
- const id = getOrCreateNodeId(node);
499
- if (fileNodes.has(id)) return;
500
- fileNodes.set(id, { path, filetype });
501
- insertOutputFile(path, filetype, id);
623
+ markFileDirtyForNode(formerParent);
624
+ detachRecursive(node);
502
625
  }
503
626
 
504
- function removeFileEntriesForNode(node: RenderedTextTree | PrintHook) {
505
- if (!isDebugEnabled()) return;
627
+ function detachRecursive(node: AlloyNode): void {
628
+ for (let c = node.firstChild; c !== null; c = c.nextSibling) {
629
+ detachRecursive(c);
630
+ }
631
+ if (!tracked.has(node)) return;
632
+ tracked.delete(node);
633
+ if (!shouldExposeNode(node)) return;
506
634
  const id = nodeIds.get(node);
507
635
  if (id === undefined) return;
508
- const fileInfo = fileNodes.get(id);
509
- if (fileInfo) {
510
- deleteOutputFile(fileInfo.path);
511
- fileNodes.delete(id);
636
+ emitRemoved(id);
637
+ }
638
+
639
+ function emitRemoved(id: number): void {
640
+ const componentIds = rootComponentsByNodeId.get(id);
641
+ if (componentIds) {
642
+ for (const componentId of [...componentIds]) {
643
+ const component = componentsById.get(componentId);
644
+ if (component) removeComponentRoot(component, id);
645
+ }
512
646
  }
513
- const dirInfo = directoryNodes.get(id);
514
- if (dirInfo) {
515
- deleteDirectory(dirInfo.path);
516
- directoryNodes.delete(id);
647
+ traceDeleteRenderNode(id);
648
+ rerenderActions.delete(id);
649
+ nodeProps.delete(id);
650
+ nodeKinds.delete(id);
651
+ // Files & directories owned by this node.
652
+ fileNodes.delete(id);
653
+ directoryNodes.delete(id);
654
+ ownerComponentByNodeId.delete(id);
655
+ // Cascade to any descendants currently registered as children of this id.
656
+ const children = renderChildIds.get(id);
657
+ if (children !== undefined) {
658
+ renderChildIds.delete(id);
659
+ for (const childId of children) {
660
+ emitRemoved(childId);
661
+ }
517
662
  }
518
663
  }
519
664
 
520
- // ─────────────────────────────────────────────────────────────────────────────
521
- // Public API — called from render.ts via the debug object
522
- // ─────────────────────────────────────────────────────────────────────────────
665
+ // #endregion
666
+
667
+ // #region Component lifecycle
668
+
669
+ function serializeRenderTreeProps(input: Record<string, unknown> | undefined) {
670
+ return untrack(() => {
671
+ if (!input) return undefined;
672
+ const { children: _children, ...rest } = input;
673
+ const sanitized = sanitizeRecord(rest);
674
+ if (!sanitized) return undefined;
675
+ try {
676
+ return devalue.stringify(sanitized);
677
+ } catch {
678
+ return undefined;
679
+ }
680
+ });
681
+ }
523
682
 
524
- /** Begin tracking a component render. Returns a session to record files/dirs and dispose watchers. */
525
683
  export function beginComponent(
526
684
  options: BeginComponentOptions,
527
685
  ): ComponentDebugSession {
528
- const {
529
- parent,
530
- index,
531
- node,
532
- component,
533
- propsSource,
534
- source,
535
- isExisting,
536
- actions,
537
- } = options;
538
-
539
686
  if (!isDebugEnabled()) {
540
687
  return {
541
688
  recordDirectory() {},
@@ -544,133 +691,142 @@ export function beginComponent(
544
691
  };
545
692
  }
546
693
 
694
+ const { component, propsSource, source, parent, actions } = options;
695
+
547
696
  return untrack(() => {
548
697
  let componentName = component.component.name;
549
698
  if (componentName === "Provider") {
550
- const contextName = (component.component as any).contextName as
551
- | string
552
- | undefined;
699
+ const contextName = (component.component as { contextName?: string })
700
+ .contextName;
553
701
  if (contextName) {
554
702
  componentName = `Context ${contextName}`;
555
703
  }
556
704
  }
557
705
  const propsSerialized = serializeRenderTreeProps(propsSource);
558
706
  const resolvedSource = resolveComponentSource(source);
559
- if (isExisting) {
560
- clearRenderTreeChildren(node);
561
- } else {
562
- recordNodeAdded(parent, index, node, {
563
- kind: "component",
564
- name: componentName,
565
- propsSerialized,
566
- source: resolvedSource,
567
- });
568
- }
569
707
 
570
- if (isTraceEnabled()) {
571
- const ctx = getContext();
572
- if (ctx) {
573
- updateRenderNodeContext(getOrCreateNodeId(node), ctx.id);
574
- updateEffectComponentByContext(ctx.id, componentName);
708
+ const id = nextId++;
709
+ const ctx = getContext();
710
+ const parentComponent =
711
+ componentStack[componentStack.length - 1] ??
712
+ (ctx?.owner ? componentByContextId.get(ctx.owner.id) : undefined);
713
+ const frame: ComponentFrame = {
714
+ id,
715
+ parentComponentId: parentComponent?.id ?? null,
716
+ hostParent: parent,
717
+ name: componentName,
718
+ propsSerialized,
719
+ source: resolvedSource,
720
+ contextId: ctx?.id ?? null,
721
+ roots: [],
722
+ rootSet: new Set(),
723
+ actions,
724
+ };
725
+ componentsById.set(id, frame);
726
+ if (frame.parentComponentId !== null) {
727
+ let children = childComponentsById.get(frame.parentComponentId);
728
+ if (!children) {
729
+ children = new Set();
730
+ childComponentsById.set(frame.parentComponentId, children);
575
731
  }
732
+ children.add(id);
733
+ }
734
+ componentStack.push(frame);
735
+ if (ctx) componentByContextId.set(ctx.id, frame);
736
+ if (propsSerialized !== undefined) nodeProps.set(id, propsSerialized);
737
+ insertComponentInstance(
738
+ id,
739
+ frame.parentComponentId,
740
+ componentName,
741
+ propsSerialized,
742
+ resolvedSource?.fileName,
743
+ resolvedSource?.lineNumber,
744
+ resolvedSource?.columnNumber,
745
+ frame.contextId,
746
+ );
747
+ if (isTraceEnabled() && ctx) {
748
+ updateEffectComponentByContext(ctx.id, componentName);
576
749
  }
577
- recordNodePropsUpdated(node, propsSerialized);
578
- registerRenderNodeActions(node, actions);
579
750
 
751
+ // Watch reactive props and re-emit on change.
580
752
  let stopWatch: (() => void) | undefined;
581
753
  if (propsSource) {
582
- const propKeys = Object.keys(propsSource).filter(
583
- (key) => key !== "children",
584
- );
754
+ const propKeys = Object.keys(propsSource).filter((k) => k !== "children");
585
755
  if (propKeys.length > 0) {
586
756
  stopWatch = watch(
587
- () => propKeys.map((key) => propsSource[key]),
757
+ () => propKeys.map((k) => propsSource[k]),
588
758
  () => {
589
- const nextSerialized = serializeRenderTreeProps(propsSource);
590
- recordNodePropsUpdated(node, nextSerialized);
759
+ const next = serializeRenderTreeProps(propsSource);
760
+ const previous = nodeProps.get(id);
761
+ if (previous === next) return;
762
+ nodeProps.set(id, next);
763
+ updateComponentInstanceProps(id, next);
591
764
  },
592
765
  );
593
766
  }
594
767
  }
768
+ frame.stopWatch = stopWatch;
595
769
 
770
+ let disposed = false;
596
771
  return {
597
772
  recordDirectory(path: string) {
598
- recordDirectoryNode(node, path);
773
+ if (frame.directory) return;
774
+ frame.directory = { path };
775
+ insertDirectory(path);
599
776
  },
600
777
  recordFile(path: string, filetype: string) {
601
- recordFileNode(node, path, filetype);
778
+ if (frame.file) return;
779
+ frame.file = { path, filetype };
602
780
  },
603
781
  dispose() {
604
- stopWatch?.();
605
- removeFileEntriesForNode(node);
606
- unregisterRenderNodeActions(node);
782
+ if (disposed) return;
783
+ disposed = true;
784
+ for (let i = componentStack.length - 1; i >= 0; i--) {
785
+ if (componentStack[i].id === id) {
786
+ componentStack.splice(i, 1);
787
+ break;
788
+ }
789
+ }
607
790
  },
608
791
  };
609
792
  });
610
793
  }
611
794
 
612
- export function appendCustomContext(
613
- parent: RenderedTextTree,
614
- node: RenderedTextTree,
615
- ) {
616
- recordSubtreeAdded(parent, node, { kind: "customContext" });
617
- }
795
+ // #endregion
618
796
 
619
- export function appendPrintHook(
620
- parent: RenderedTextTree,
621
- index: number,
622
- hook: PrintHook,
623
- name: string,
624
- subtree?: RenderedTextTree,
625
- ) {
626
- recordNodeAdded(parent, index, hook, { kind: "printHook", name });
627
- if (subtree) {
628
- recordSubtreeAdded(hook, subtree);
629
- }
630
- }
631
-
632
- export function appendFragmentChild(
633
- parent: RenderedTextTree,
634
- child: RenderedTextTree,
635
- ) {
636
- recordSubtreeAdded(parent, child, { kind: "fragment" });
637
- }
638
-
639
- export function appendTextNode(
640
- parent: RenderedTextTree,
641
- index: number,
642
- value: string,
643
- ) {
644
- recordTextNode(parent, index, value);
645
- }
797
+ // #region Devtools rerender bridge
646
798
 
647
- export function prepareMemoNode(
648
- parent: RenderedTextTree,
649
- node: RenderedTextTree,
650
- isExisting: boolean,
651
- ) {
652
- if (isExisting) {
653
- clearRenderTreeChildren(node);
654
- return;
655
- }
656
- recordSubtreeAdded(parent, node, { kind: "memo" });
799
+ function ensureDevtoolsHandler() {
800
+ if (handlerRegistered || !isDevtoolsEnabled()) return;
801
+ handlerRegistered = true;
802
+ registerDevtoolsMessageHandler((message) => {
803
+ if (
804
+ message.type !== "render:rerender" &&
805
+ message.type !== "render:rerenderAndBreak"
806
+ ) {
807
+ return;
808
+ }
809
+ const rawId = (message as { id?: unknown }).id;
810
+ if (typeof rawId !== "number" && typeof rawId !== "string") return;
811
+ const id = Number(rawId);
812
+ if (!Number.isFinite(id)) return;
813
+ const actions = rerenderActions.get(id);
814
+ if (!actions) return;
815
+ if (message.type === "render:rerender") {
816
+ actions.rerender();
817
+ } else {
818
+ actions.rerenderAndBreak();
819
+ }
820
+ void flushJobsAsync();
821
+ });
657
822
  }
658
823
 
659
- let nextErrorId = 1;
824
+ // #endregion
660
825
 
661
- export interface RenderErrorInfo {
662
- name: string;
663
- message: string;
664
- stack?: string;
665
- }
666
-
667
- /** Render error stack entry with optional runtime props (extends protocol type). */
668
- export interface RenderErrorStackEntry extends ProtocolRenderErrorStackEntry {
669
- props?: Record<string, unknown> | undefined;
670
- }
826
+ // #region Errors
671
827
 
672
828
  export function error(
673
- error: RenderErrorInfo,
829
+ err: RenderErrorInfo,
674
830
  componentStack: RenderErrorStackEntry[],
675
831
  ) {
676
832
  if (!isDebugEnabled()) return;
@@ -685,26 +841,17 @@ export function error(
685
841
  logDevtoolsMessage({
686
842
  type: "render:error" as const,
687
843
  id: nextErrorId++,
688
- name: error.name,
689
- message: error.message,
690
- stack: error.stack,
844
+ name: err.name,
845
+ message: err.message,
846
+ stack: err.stack,
691
847
  componentStack: serializedStack,
692
848
  });
693
-
694
849
  insertRenderError(
695
- error.name,
696
- error.message,
697
- error.stack,
850
+ err.name,
851
+ err.message,
852
+ err.stack,
698
853
  JSON.stringify(serializedStack),
699
854
  );
700
855
  }
701
856
 
702
- export function complete() {
703
- logDevtoolsMessage({ type: "render:complete" });
704
- notifyRenderComplete();
705
- }
706
-
707
- export function flushJobsComplete() {
708
- logDevtoolsMessage({ type: "flushJobs:complete" });
709
- notifyFlushComplete();
710
- }
857
+ // #endregion