@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
package/src/reactivity.ts CHANGED
@@ -2,21 +2,24 @@ import {
2
2
  isRef,
3
3
  pauseTracking,
4
4
  ReactiveEffectRunner,
5
- ref,
6
5
  Ref,
7
6
  resetTracking,
8
7
  ShallowReactive,
9
- shallowRef,
10
8
  stop,
9
+ computed as vueComputed,
11
10
  effect as vueEffect,
11
+ ref as vueRef,
12
+ shallowReactive as vueShallowReactive,
13
+ shallowRef as vueShallowRef,
14
+ toRef as vueToRef,
15
+ toRefs as vueToRefs,
12
16
  } from "@vue/reactivity";
13
- import type { RenderedTextTree } from "./render.js";
14
- import type { Children, ComponentCreator } from "./runtime/component.js";
15
- import { scheduler } from "./scheduler.js";
17
+ import { captureSourceLocation, debug, isDebugEnabled } from "./debug/index.js";
18
+ import { RenderedTextTree } from "./render.js";
19
+ import { Children, ComponentCreator } from "./runtime/component.js";
20
+ import { scheduler, setLastTriggerRef } from "./scheduler.js";
16
21
  import type { OutputSymbol } from "./symbols/output-symbol.js";
17
- import { trace, TracePhase } from "./tracer.js";
18
22
 
19
- // check for multiple versions of alloy here.
20
23
  if ((globalThis as any).__ALLOY__) {
21
24
  throw new Error(
22
25
  "Multiple versions of Alloy are loaded for this project. This will likely cause undesirable behavior.",
@@ -25,7 +28,8 @@ if ((globalThis as any).__ALLOY__) {
25
28
  (globalThis as any).__ALLOY__ = true;
26
29
 
27
30
  export function getElementCache() {
28
- return getContext()!.elementCache;
31
+ const ctx = getContext()!;
32
+ return (ctx.elementCache ??= new Map());
29
33
  }
30
34
 
31
35
  export type ElementCacheKey =
@@ -39,8 +43,12 @@ export interface Disposable {
39
43
  (): void;
40
44
  }
41
45
 
46
+ let contextIdCounter = 0;
47
+
42
48
  export interface Context {
43
- disposables: Disposable[];
49
+ /** Monotonic numeric ID for trace/debug correlation. */
50
+ id: number;
51
+ disposables?: Disposable[];
44
52
  owner: Context | null;
45
53
 
46
54
  // context providers
@@ -53,7 +61,7 @@ export interface Context {
53
61
  * A cache of RenderTextTree nodes created within this context,
54
62
  * indexed by the component or function which created them.
55
63
  */
56
- elementCache: ElementCache;
64
+ elementCache?: ElementCache;
57
65
  /**
58
66
  * When this context was created by a component, this will
59
67
  * be the component that created it.
@@ -78,9 +86,17 @@ export interface Context {
78
86
 
79
87
  /**
80
88
  * A ref that indicates whether the component is empty.
89
+ * Only allocated when reactively observed (ContentSlot, mapJoin).
81
90
  */
82
91
  isEmpty?: Ref<boolean>;
83
92
 
93
+ /**
94
+ * Cheap boolean tracking the last propagated empty state.
95
+ * Used by notifyContentState() for early-return optimization
96
+ * without requiring a reactive ref on every context.
97
+ */
98
+ _lastEmpty: boolean;
99
+
84
100
  /**
85
101
  * Whether this context is a root context
86
102
  */
@@ -92,21 +108,48 @@ export function getContext() {
92
108
  return globalContext;
93
109
  }
94
110
 
111
+ /**
112
+ * Walk up the owner chain to find the nearest ancestor context that
113
+ * corresponds to an effect (has meta.effectId). This bridges non-effect
114
+ * scopes (like createRoot iterations in For) so the owner chain always
115
+ * connects effect-to-effect.
116
+ */
117
+ function resolveOwnerEffectContextId(context: Context): number | null {
118
+ let owner = context.owner;
119
+ while (owner) {
120
+ if (owner.meta?.effectId !== undefined) {
121
+ return owner.id;
122
+ }
123
+ owner = owner.owner;
124
+ }
125
+ return context.owner?.id ?? null;
126
+ }
127
+
128
+ /**
129
+ * Ensure that a context has an isEmpty ref, creating one if needed.
130
+ * Only call this when you need to reactively observe isEmpty (e.g.,
131
+ * ContentSlot, mapJoin). Most contexts don't need an isEmpty ref.
132
+ */
133
+ export function ensureIsEmpty(context: Context): Ref<boolean> {
134
+ context.isEmpty ??= ref(context.childrenWithContent === 0, {
135
+ isInfrastructure: true,
136
+ });
137
+ return context.isEmpty;
138
+ }
139
+
95
140
  export interface RootOptions {
96
141
  componentOwner?: ComponentCreator<any>;
97
142
  }
98
143
 
99
144
  export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
100
145
  const context: Context = {
146
+ id: contextIdCounter++,
101
147
  componentOwner: options?.componentOwner,
102
- disposables: [],
103
148
  owner: globalContext,
104
- context: {},
105
- elementCache: new Map(),
106
149
  takesSymbols: false,
107
150
  takenSymbols: undefined,
108
151
  childrenWithContent: 0,
109
- isEmpty: ref(true),
152
+ _lastEmpty: true,
110
153
  isRoot: true,
111
154
  };
112
155
 
@@ -115,8 +158,8 @@ export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
115
158
  try {
116
159
  ret = untrack(() =>
117
160
  fn(() => {
118
- for (const d of context!.disposables) {
119
- d();
161
+ for (const d of context!.disposables ?? []) {
162
+ untrack(d);
120
163
  }
121
164
  }),
122
165
  );
@@ -127,6 +170,15 @@ export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
127
170
  return ret;
128
171
  }
129
172
 
173
+ export interface EffectDebugOptions {
174
+ name?: string;
175
+ type?: string;
176
+ }
177
+
178
+ export interface EffectOptions {
179
+ debug?: EffectDebugOptions;
180
+ }
181
+
130
182
  export function untrack<T>(fn: () => T): T {
131
183
  pauseTracking();
132
184
  const v = fn();
@@ -134,68 +186,201 @@ export function untrack<T>(fn: () => T): T {
134
186
  return v;
135
187
  }
136
188
 
137
- export function memo<T>(fn: () => T, equal?: boolean): () => T {
138
- const o = shallowRef();
139
- effect((prev) => {
140
- const res = fn();
141
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
142
- (!equal || prev !== res) && (o.value = res);
143
- return res;
144
- }, undefined as T);
145
- return () => o.value;
189
+ /**
190
+ * Walk up the context owner chain to find the nearest effect ID.
191
+ * Used to attribute reactive mutations to the effect that caused them.
192
+ */
193
+ export function findCurrentEffectId(): number | undefined {
194
+ let ctx = globalContext;
195
+ while (ctx) {
196
+ const id = ctx.meta?.effectId;
197
+ if (id !== undefined && id !== -1) return id;
198
+ ctx = ctx.owner;
199
+ }
200
+ return undefined;
146
201
  }
147
202
 
148
- export function effect<T>(fn: (prev?: T) => T, current?: T) {
203
+ export function memo<T>(fn: () => T, equal?: boolean, name?: string): () => T {
204
+ const memoLabel = name ? `memo:${name}` : "memo";
205
+ const o = shallowRef<T>(undefined as T, {
206
+ label: memoLabel,
207
+ });
208
+ effect(
209
+ (prev) => {
210
+ const res = fn();
211
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
212
+ (!equal || prev !== res) && (o.value = res);
213
+ return res;
214
+ },
215
+ undefined as T,
216
+ {
217
+ debug: { name: name ? `memo:${name}` : "memo" },
218
+ },
219
+ );
220
+ const getter = (() => o.value as T) as () => T;
221
+ if (name) {
222
+ Object.defineProperty(getter, "name", { value: name, configurable: true });
223
+ }
224
+ return getter;
225
+ }
226
+
227
+ export function effect<T>(
228
+ fn: (prev?: T) => T,
229
+ current?: T,
230
+ options?: EffectOptions,
231
+ ) {
149
232
  const context: Context = {
150
- context: {},
151
- disposables: [] as (() => void)[],
233
+ id: contextIdCounter++,
152
234
  owner: globalContext,
153
- elementCache: new Map(),
154
235
  takesSymbols: false,
155
236
  takenSymbols: undefined,
156
237
  childrenWithContent: 0,
238
+ _lastEmpty: true,
157
239
  isRoot: false,
158
240
  };
159
241
 
242
+ const debugInfo = options?.debug;
243
+ const effectId = debug.effect.register({
244
+ name: debugInfo?.name ?? fn.name,
245
+ type: debugInfo?.type,
246
+ createdAt: captureSourceLocation(),
247
+ contextId: context.id,
248
+ ownerContextId: resolveOwnerEffectContextId(context),
249
+ });
250
+
251
+ if (effectId !== -1) {
252
+ context.meta ??= {};
253
+ context.meta.effectId = effectId;
254
+ }
255
+
160
256
  const cleanupFn = (final: boolean) => {
161
257
  const d = context.disposables;
162
- context.disposables = [];
163
- for (let k = 0, len = d.length; k < len; k++) d[k]();
258
+ context.disposables = undefined;
259
+ if (d) {
260
+ for (let k = 0, len = d.length; k < len; k++) untrack(d[k]);
261
+ }
164
262
 
165
263
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
166
264
  final && stop(runner);
167
265
  };
168
266
 
169
267
  onCleanup(() => cleanupFn(true));
170
- const runner: ReactiveEffectRunner<void> = vueEffect(
171
- () => {
172
- cleanupFn(false);
173
-
174
- const oldContext = globalContext;
175
- globalContext = context;
176
- try {
177
- current = fn(current);
178
- } finally {
179
- globalContext = oldContext;
268
+ const effectOpts: Record<string, unknown> = {
269
+ // allow recursive effects with 32, 1 and 4 are default flags
270
+ flags: 1 | 4 | 32,
271
+ scheduler: scheduler(),
272
+ };
273
+
274
+ if (effectId !== -1) {
275
+ effectOpts.onTrack = (event: any) => {
276
+ const targetKey =
277
+ typeof event.key === "symbol" ? event.key.toString() : event.key;
278
+ if (isRef(event.target)) {
279
+ const id = refId(event.target);
280
+ debug.effect.ensureRef({ id, kind: "ref" });
281
+ debug.effect.track({
282
+ effectId,
283
+ target: event.target,
284
+ refId: id,
285
+ targetKey,
286
+ });
287
+ } else if (
288
+ typeof event.target === "object" &&
289
+ event.target !== null &&
290
+ targetKey !== undefined
291
+ ) {
292
+ const id = reactivePropertyRefId(event.target, targetKey);
293
+ debug.effect.ensureReactivePropertyRef({
294
+ id,
295
+ target: event.target,
296
+ key: targetKey,
297
+ });
298
+ debug.effect.track({
299
+ effectId,
300
+ target: event.target,
301
+ refId: id,
302
+ targetKey,
303
+ });
304
+ } else {
305
+ debug.effect.track({
306
+ effectId,
307
+ target: event.target,
308
+ targetKey,
309
+ });
180
310
  }
181
- },
182
- {
183
- // allow recursive effects with 32, 1 and 4 are default flags
184
- // @ts-expect-error flags is a vue internal thing
185
- flags: 1 | 4 | 32,
186
- scheduler: scheduler(),
187
- onTrack(event) {
188
- trace(TracePhase.effect.track, () => {
189
- return `tracking ${isRef(event.target) ? `Ref:${refId(event.target)}` : event.target}, ${String(event.key)}`;
311
+ };
312
+ effectOpts.onTrigger = (event: any) => {
313
+ if (!("target" in event)) {
314
+ // Vue Dep.notify() chain propagation no actual reactive target.
315
+ // Skip recording: these are computed→subscriber notifications without
316
+ // a meaningful target reference.
317
+ return;
318
+ }
319
+ const targetKey =
320
+ typeof event.key === "symbol" ? event.key.toString() : event.key;
321
+ // findCurrentEffectId() works because onTrigger fires synchronously
322
+ // during the mutation, so globalContext still points to the producer.
323
+ const producerEffectId = findCurrentEffectId();
324
+ const causedBy =
325
+ producerEffectId !== undefined && producerEffectId !== effectId ?
326
+ producerEffectId
327
+ : undefined;
328
+ if (isRef(event.target)) {
329
+ const id = refId(event.target);
330
+ debug.effect.ensureRef({ id, kind: "ref" });
331
+ debug.effect.trigger({
332
+ effectId,
333
+ target: event.target,
334
+ refId: id,
335
+ targetKey,
336
+ causedBy,
190
337
  });
191
- },
192
- onTrigger(event) {
193
- trace(TracePhase.effect.trigger, () => {
194
- return `triggering ${isRef(event.target) ? `Ref:${refId(event.target)}` : event.target}, ${String(event.key)}`;
338
+ setLastTriggerRef(effectId, id);
339
+ } else if (
340
+ typeof event.target === "object" &&
341
+ event.target !== null &&
342
+ targetKey !== undefined
343
+ ) {
344
+ const id = reactivePropertyRefId(event.target, targetKey);
345
+ debug.effect.ensureReactivePropertyRef({
346
+ id,
347
+ target: event.target,
348
+ key: targetKey,
195
349
  });
196
- },
197
- },
198
- );
350
+ debug.effect.trigger({
351
+ effectId,
352
+ target: event.target,
353
+ refId: id,
354
+ targetKey,
355
+ causedBy,
356
+ });
357
+ setLastTriggerRef(effectId, id);
358
+ } else {
359
+ debug.effect.trigger({
360
+ effectId,
361
+ target: event.target,
362
+ targetKey,
363
+ causedBy,
364
+ });
365
+ }
366
+ };
367
+ }
368
+
369
+ const runner: ReactiveEffectRunner<void> = vueEffect(() => {
370
+ cleanupFn(false);
371
+
372
+ const oldContext = globalContext;
373
+ globalContext = context;
374
+ try {
375
+ current = fn(current);
376
+ } finally {
377
+ globalContext = oldContext;
378
+ }
379
+ }, effectOpts as any);
380
+
381
+ if (effectId !== -1) {
382
+ effectIdMap.set(runner.effect, effectId);
383
+ }
199
384
  }
200
385
 
201
386
  /**
@@ -218,7 +403,7 @@ export function effect<T>(fn: (prev?: T) => T, current?: T) {
218
403
  */
219
404
  export function onCleanup(fn: Disposable) {
220
405
  if (globalContext != null) {
221
- globalContext.disposables.push(fn);
406
+ (globalContext.disposables ??= []).push(fn);
222
407
  }
223
408
  }
224
409
 
@@ -253,8 +438,105 @@ export function isCustomContext(child: Children): child is CustomContext {
253
438
  );
254
439
  }
255
440
 
441
+ export function ref<T>(
442
+ value?: T,
443
+ options?: { isInfrastructure?: boolean },
444
+ ): Ref<T> {
445
+ const result = vueRef(value) as Ref<T>;
446
+ debug.effect.registerRef({
447
+ id: refId(result),
448
+ kind: "ref",
449
+ createdAt: captureSourceLocation(),
450
+ createdByEffectId: globalContext?.meta?.effectId,
451
+ isInfrastructure: options?.isInfrastructure,
452
+ });
453
+ return result;
454
+ }
455
+
456
+ // Stores creation location for shallowReactive objects so registerNonRefTarget
457
+ // can look it up later (since targets are lazily registered on first track/trigger).
458
+ const reactiveCreationLocations = new WeakMap<
459
+ object,
460
+ ReturnType<typeof captureSourceLocation>
461
+ >();
462
+
463
+ export function getReactiveCreationLocation(target: object) {
464
+ return reactiveCreationLocations.get(target);
465
+ }
466
+
467
+ export function shallowReactive<T extends object>(
468
+ target: T,
469
+ ): ShallowReactive<T> {
470
+ const result = vueShallowReactive(target);
471
+ if (isDebugEnabled()) {
472
+ // Store by raw target — Vue's onTrack/onTrigger events pass the raw object, not the proxy.
473
+ reactiveCreationLocations.set(target, captureSourceLocation());
474
+ }
475
+ return result;
476
+ }
477
+
478
+ export function shallowRef<T>(value?: T, options?: { label?: string }): Ref<T> {
479
+ const result = vueShallowRef(value) as Ref<T>;
480
+ debug.effect.registerRef({
481
+ id: refId(result),
482
+ kind: "shallowRef",
483
+ label: options?.label,
484
+ createdAt: captureSourceLocation(),
485
+ createdByEffectId: globalContext?.meta?.effectId,
486
+ });
487
+ return result;
488
+ }
489
+
490
+ export function computed<T>(getter: () => T): Ref<T> {
491
+ const result = vueComputed(getter) as Ref<T>;
492
+ debug.effect.registerRef({
493
+ id: refId(result),
494
+ kind: "computed",
495
+ createdAt: captureSourceLocation(),
496
+ createdByEffectId: globalContext?.meta?.effectId,
497
+ });
498
+ return result;
499
+ }
500
+
501
+ export function toRef<T extends object, K extends keyof T>(
502
+ object: T,
503
+ key: K,
504
+ defaultValue?: T[K],
505
+ ): Ref<T[K]> {
506
+ const result =
507
+ defaultValue === undefined ?
508
+ (vueToRef(object, key) as Ref<T[K]>)
509
+ : (vueToRef(object, key, defaultValue) as Ref<T[K]>);
510
+ debug.effect.registerRef({
511
+ id: refId(result),
512
+ kind: "toRef",
513
+ createdAt: captureSourceLocation(),
514
+ createdByEffectId: globalContext?.meta?.effectId,
515
+ });
516
+ return result;
517
+ }
518
+
519
+ export function toRefs<T extends object>(
520
+ object: T,
521
+ ): { [K in keyof T]: Ref<T[K]> } {
522
+ const result = vueToRefs(object) as { [K in keyof T]: Ref<T[K]> };
523
+ for (const refValue of Object.values(result) as Ref<unknown>[]) {
524
+ debug.effect.registerRef({
525
+ id: refId(refValue),
526
+ kind: "toRef",
527
+ createdAt: captureSourceLocation(),
528
+ createdByEffectId: globalContext?.meta?.effectId,
529
+ });
530
+ }
531
+ return result;
532
+ }
533
+
256
534
  const seenRefs = new WeakMap<Ref<unknown>, number>();
257
535
  let refIdCounter = 1;
536
+ const effectIdMap = new WeakMap<object, number>();
537
+
538
+ // Stable ID mapping for (reactive, key) pairs — each property acts as a virtual ref.
539
+ const reactivePropertyIds = new WeakMap<object, Map<string | number, number>>();
258
540
 
259
541
  export function refId(ref: Ref<unknown>): number {
260
542
  let id = seenRefs.get(ref);
@@ -264,3 +546,59 @@ export function refId(ref: Ref<unknown>): number {
264
546
  }
265
547
  return id;
266
548
  }
549
+
550
+ /**
551
+ * Get a stable ref ID for a property of a reactive object.
552
+ * Each (target, key) pair gets a unique positive ID from the same counter as refs.
553
+ */
554
+ export function reactivePropertyRefId(
555
+ target: object,
556
+ key: string | number,
557
+ ): number {
558
+ let keys = reactivePropertyIds.get(target);
559
+ if (!keys) {
560
+ keys = new Map();
561
+ reactivePropertyIds.set(target, keys);
562
+ }
563
+ let id = keys.get(key);
564
+ if (id === undefined) {
565
+ id = refIdCounter++;
566
+ keys.set(key, id);
567
+ }
568
+ return id;
569
+ }
570
+
571
+ /**
572
+ * Build a human-readable label for a reactive property like `symbolName.prop`.
573
+ */
574
+ export function formatReactivePropertyLabel(
575
+ target: object,
576
+ key: string | number,
577
+ ): string {
578
+ let ownerLabel: string;
579
+ try {
580
+ const str = String(target);
581
+ // OutputSymbol toString() returns something like: TSOutputSymbol "MyInterface"[42]
582
+ // Trim to just the meaningful part
583
+ ownerLabel = str.length > 60 ? str.slice(0, 57) + "..." : str;
584
+ } catch {
585
+ ownerLabel = "reactive";
586
+ }
587
+ if (Array.isArray(target)) {
588
+ ownerLabel = "[]";
589
+ }
590
+ return `${ownerLabel}.${key}`;
591
+ }
592
+
593
+ /** Allocate a unique reactive target ID from the same counter space as ref IDs. */
594
+ export function nextReactiveId(): number {
595
+ return refIdCounter++;
596
+ }
597
+
598
+ export function resetRefIdCounter(): void {
599
+ refIdCounter = 1;
600
+ }
601
+
602
+ export function getEffectDebugId(effect: object): number | undefined {
603
+ return effectIdMap.get(effect);
604
+ }
@@ -13,6 +13,14 @@ const renderStack: Array<{
13
13
  source?: SourceLocation;
14
14
  }> = [];
15
15
 
16
+ export interface RenderStackSnapshotEntry {
17
+ component: Component<any>;
18
+ props: Props;
19
+ context: Context | null;
20
+ source?: SourceLocation;
21
+ displayName: string;
22
+ }
23
+
16
24
  export function pushStack(
17
25
  component: Component<any>,
18
26
  props: Props,
@@ -25,10 +33,51 @@ export function popStack() {
25
33
  renderStack.pop();
26
34
  }
27
35
 
36
+ export function currentComponentName(): string | undefined {
37
+ const entry = renderStack[renderStack.length - 1];
38
+ return entry?.component.name || undefined;
39
+ }
40
+
28
41
  export function clearRenderStack() {
29
42
  renderStack.length = 0;
30
43
  }
31
44
 
45
+ /**
46
+ * Get the current rendering path from the render stack context.
47
+ * Prefers SourceFileContext over SourceDirectoryContext.
48
+ */
49
+ export function getCurrentRenderPath(): string | undefined {
50
+ let currentPath: string | undefined;
51
+ for (let i = renderStack.length - 1; i >= 0; i--) {
52
+ const { context } = renderStack[i];
53
+ // Prefer SourceFileContext over SourceDirectoryContext
54
+ if (context?.context?.[SourceFileContext.id]) {
55
+ const fileContext = context.context[
56
+ SourceFileContext.id
57
+ ] as SourceFileContext;
58
+ return fileContext.path;
59
+ }
60
+ if (!currentPath && context?.context?.[SourceDirectoryContext.id]) {
61
+ const dirContext = context.context[
62
+ SourceDirectoryContext.id
63
+ ] as SourceDirectoryContext;
64
+ currentPath = dirContext.path;
65
+ // Don't break - keep looking for a SourceFileContext
66
+ }
67
+ }
68
+ return currentPath;
69
+ }
70
+
71
+ export function getRenderStackSnapshot(): RenderStackSnapshotEntry[] {
72
+ return renderStack.map((entry) => ({
73
+ component: entry.component,
74
+ props: entry.props,
75
+ context: entry.context,
76
+ source: entry.source,
77
+ displayName: getComponentDisplayName(entry.component, entry.props),
78
+ }));
79
+ }
80
+
32
81
  // Helper functions
33
82
  function getComponentDisplayName(
34
83
  component: Component<any>,
@@ -93,8 +142,10 @@ function formatSourceLocation(source: SourceLocation): string {
93
142
  * This differs from debug.component.stack in that this uses a purpose-built
94
143
  * stack rather than walking the context chain. When this is called, the context
95
144
  * chain has been restored. In the future this can probably be unified nicely.
145
+ *
146
+ * @param error - Optional error to print the stack trace from
96
147
  */
97
- export function printRenderStack() {
148
+ export function printRenderStack(error?: unknown) {
98
149
  // Find the nearest SourceFileContext or SourceDirectoryContext from the render stack
99
150
  let currentPath: string | undefined;
100
151
  for (let i = renderStack.length - 1; i >= 0; i--) {
@@ -124,6 +175,27 @@ export function printRenderStack() {
124
175
  console.error(pc.red("Error rendering:"));
125
176
  }
126
177
 
178
+ // Print the error message and stack if provided
179
+ if (error) {
180
+ if (error instanceof Error) {
181
+ // eslint-disable-next-line no-console
182
+ console.error(pc.red(` ${error.message}`));
183
+ if (error.stack) {
184
+ // Print stack lines (skip the first line which is the error message)
185
+ const stackLines = error.stack.split("\n").slice(1);
186
+ for (const line of stackLines) {
187
+ // eslint-disable-next-line no-console
188
+ console.error(pc.gray(line));
189
+ }
190
+ }
191
+ } else {
192
+ // eslint-disable-next-line no-console
193
+ console.error(pc.red(` ${String(error)}`));
194
+ }
195
+ // eslint-disable-next-line no-console
196
+ console.error(); // Empty line before component stack
197
+ }
198
+
127
199
  // First pass: determine which providers should be nested vs standalone
128
200
  // A provider should be nested under its parent if it's from a different file
129
201
  // (i.e., it's part of the component's implementation, not user-provided)