sygnal 5.3.7 → 6.0.0

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 (438) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +77 -47
  3. package/dist/astro/client.cjs.js +33 -8114
  4. package/dist/astro/client.cjs.js.map +1 -0
  5. package/dist/astro/client.mjs +33 -8114
  6. package/dist/astro/client.mjs.map +1 -0
  7. package/dist/astro/index.cjs.js +1039 -9
  8. package/dist/astro/index.cjs.js.map +1 -0
  9. package/dist/astro/index.mjs +1038 -9
  10. package/dist/astro/index.mjs.map +1 -0
  11. package/dist/astro/server.cjs.js +3400 -166
  12. package/dist/astro/server.cjs.js.map +1 -0
  13. package/dist/astro/server.mjs +3400 -166
  14. package/dist/astro/server.mjs.map +1 -0
  15. package/dist/devtools.cjs.js +1431 -0
  16. package/dist/devtools.cjs.js.map +1 -0
  17. package/dist/devtools.esm.js +1419 -0
  18. package/dist/devtools.esm.js.map +1 -0
  19. package/dist/diagnostics.cjs.js +4033 -0
  20. package/dist/diagnostics.cjs.js.map +1 -0
  21. package/dist/diagnostics.esm.js +4003 -0
  22. package/dist/diagnostics.esm.js.map +1 -0
  23. package/dist/element.cjs.js +230 -0
  24. package/dist/element.cjs.js.map +1 -0
  25. package/dist/element.esm.js +228 -0
  26. package/dist/element.esm.js.map +1 -0
  27. package/dist/guide/accessibility.md +257 -0
  28. package/dist/guide/adapters.md +221 -0
  29. package/dist/guide/behaviors.md +312 -0
  30. package/dist/guide/browser-sources.md +203 -0
  31. package/dist/guide/drag-and-drop.md +342 -0
  32. package/dist/guide/element-commands.md +306 -0
  33. package/dist/guide/error-boundaries.md +169 -0
  34. package/dist/guide/forms-reference.md +381 -0
  35. package/dist/guide/forms.md +250 -0
  36. package/dist/guide/http.md +299 -0
  37. package/dist/guide/inputs.md +173 -0
  38. package/dist/guide/persistence.md +245 -0
  39. package/dist/guide/recipes/carousel.md +155 -0
  40. package/dist/guide/recipes/charts.md +177 -0
  41. package/dist/guide/recipes/code-editor.md +135 -0
  42. package/dist/guide/recipes/data-grid.md +155 -0
  43. package/dist/guide/recipes/data-table.md +196 -0
  44. package/dist/guide/recipes/i18n.md +222 -0
  45. package/dist/guide/recipes/icons.md +115 -0
  46. package/dist/guide/recipes/overview.md +33 -0
  47. package/dist/guide/recipes/rich-text.md +154 -0
  48. package/dist/guide/resources.md +471 -0
  49. package/dist/guide/ssr.md +255 -0
  50. package/dist/guide/timers.md +178 -0
  51. package/dist/guide/ui/accordion.md +93 -0
  52. package/dist/guide/ui/combobox.md +144 -0
  53. package/dist/guide/ui/dialog.md +124 -0
  54. package/dist/guide/ui/disclosure.md +60 -0
  55. package/dist/guide/ui/menu.md +125 -0
  56. package/dist/guide/ui/overview.md +166 -0
  57. package/dist/guide/ui/popover.md +101 -0
  58. package/dist/guide/ui/select.md +114 -0
  59. package/dist/guide/ui/tabs.md +161 -0
  60. package/dist/guide/ui/toaster.md +152 -0
  61. package/dist/guide/ui/tooltip.md +103 -0
  62. package/dist/guide/undo.md +178 -0
  63. package/dist/guide/virtual-collections.md +183 -0
  64. package/dist/guide/web-components.md +179 -0
  65. package/dist/guide/widgets.md +217 -0
  66. package/dist/index.cjs.js +15283 -6279
  67. package/dist/index.cjs.js.map +1 -0
  68. package/dist/index.d.ts +3739 -370
  69. package/dist/index.esm.js +15230 -6274
  70. package/dist/index.esm.js.map +1 -0
  71. package/dist/jsx-dev-runtime.cjs.js +27 -329
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -0
  73. package/dist/jsx-dev-runtime.esm.js +20 -322
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -0
  75. package/dist/jsx-runtime.cjs.js +27 -329
  76. package/dist/jsx-runtime.cjs.js.map +1 -0
  77. package/dist/jsx-runtime.esm.js +20 -322
  78. package/dist/jsx-runtime.esm.js.map +1 -0
  79. package/dist/jsx.cjs.js +11 -309
  80. package/dist/jsx.cjs.js.map +1 -0
  81. package/dist/jsx.esm.js +9 -310
  82. package/dist/jsx.esm.js.map +1 -0
  83. package/dist/react.cjs.js +117 -0
  84. package/dist/react.cjs.js.map +1 -0
  85. package/dist/react.esm.js +115 -0
  86. package/dist/react.esm.js.map +1 -0
  87. package/dist/shims/globalthis.cjs +20 -0
  88. package/dist/sygnal.min.js +2 -1
  89. package/dist/sygnal.min.js.map +1 -0
  90. package/dist/ui-combobox.cjs.js +169 -0
  91. package/dist/ui-combobox.cjs.js.map +1 -0
  92. package/dist/ui-combobox.esm.js +148 -0
  93. package/dist/ui-combobox.esm.js.map +1 -0
  94. package/dist/ui-menu.cjs.js +87 -0
  95. package/dist/ui-menu.cjs.js.map +1 -0
  96. package/dist/ui-menu.esm.js +66 -0
  97. package/dist/ui-menu.esm.js.map +1 -0
  98. package/dist/ui-select.cjs.js +104 -0
  99. package/dist/ui-select.cjs.js.map +1 -0
  100. package/dist/ui-select.esm.js +83 -0
  101. package/dist/ui-select.esm.js.map +1 -0
  102. package/dist/ui.cjs.js +738 -0
  103. package/dist/ui.cjs.js.map +1 -0
  104. package/dist/ui.esm.js +727 -0
  105. package/dist/ui.esm.js.map +1 -0
  106. package/dist/vike/ClientOnly.cjs.js +5 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -0
  108. package/dist/vike/ClientOnly.mjs +5 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -0
  110. package/dist/vike/{+config.js → config/+config.js} +10 -1
  111. package/dist/vike/config/+config.js.map +1 -0
  112. package/dist/vike/config/package.json +6 -0
  113. package/dist/vike/onRenderClient.cjs.js +166 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -0
  115. package/dist/vike/onRenderClient.mjs +166 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -0
  117. package/dist/vike/onRenderHtml.cjs.js +60 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -0
  119. package/dist/vike/onRenderHtml.mjs +61 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -0
  121. package/dist/vite/plugin.cjs.js +926 -65
  122. package/dist/vite/plugin.cjs.js.map +1 -0
  123. package/dist/vite/plugin.mjs +925 -65
  124. package/dist/vite/plugin.mjs.map +1 -0
  125. package/dist/zag.cjs.js +408 -0
  126. package/dist/zag.cjs.js.map +1 -0
  127. package/dist/zag.esm.js +405 -0
  128. package/dist/zag.esm.js.map +1 -0
  129. package/llms.txt +313 -0
  130. package/package.json +109 -16
  131. package/src/astro/client.ts +40 -18
  132. package/src/astro/index.d.ts +48 -1
  133. package/src/astro/index.ts +106 -9
  134. package/src/astro/server.ts +11 -3
  135. package/src/collection.ts +6 -90
  136. package/src/core/actions.ts +134 -0
  137. package/src/core/cell.ts +202 -0
  138. package/src/core/debug.ts +23 -0
  139. package/src/core/define.ts +242 -0
  140. package/src/core/hooks.ts +314 -0
  141. package/src/core/hosts/collection.ts +325 -0
  142. package/src/core/hosts/switchable.ts +146 -0
  143. package/src/core/instance.ts +592 -0
  144. package/src/core/markers/clientonly.ts +13 -0
  145. package/src/core/markers/lazy.ts +40 -0
  146. package/src/core/markers/portal.ts +96 -0
  147. package/src/core/markers/suspense.ts +41 -0
  148. package/src/core/markers/transition.ts +70 -0
  149. package/src/core/registry.ts +25 -0
  150. package/src/core/runtime.ts +573 -0
  151. package/src/core/statics.ts +104 -0
  152. package/src/core/teardown.ts +60 -0
  153. package/src/core/view.ts +37 -0
  154. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  155. package/src/cycle/dom/ElementFinder.ts +7 -9
  156. package/src/cycle/dom/EventDelegator.ts +174 -223
  157. package/src/cycle/dom/IsolateModule.ts +65 -35
  158. package/src/cycle/dom/MainDOMSource.ts +13 -2
  159. package/src/cycle/dom/PriorityQueue.ts +4 -2
  160. package/src/cycle/dom/SymbolTree.ts +18 -47
  161. package/src/cycle/dom/classNameModule.ts +76 -0
  162. package/src/cycle/dom/controlledInputModule.ts +50 -0
  163. package/src/cycle/dom/enrichEventStream.ts +29 -45
  164. package/src/cycle/dom/fragment.ts +7 -0
  165. package/src/cycle/dom/isolate.ts +30 -27
  166. package/src/cycle/dom/makeDOMDriver.ts +115 -57
  167. package/src/cycle/dom/mockDOMSource.ts +83 -9
  168. package/src/cycle/dom/modules.ts +21 -4
  169. package/src/cycle/dom/propsModule.ts +65 -0
  170. package/src/cycle/dom/selectModule.ts +22 -17
  171. package/src/cycle/dom/snabbdom.ts +1 -6
  172. package/src/cycle/dom/thunk.ts +3 -0
  173. package/src/cycle/dom/utils.ts +98 -0
  174. package/src/cycle/dom/viewTransition.ts +43 -0
  175. package/src/cycle/state/StateSource.ts +20 -4
  176. package/src/cycle/state/objIsEqual.ts +50 -0
  177. package/src/cycle/state/types.ts +0 -10
  178. package/src/defineComponent.ts +34 -0
  179. package/src/devtools.d.ts +151 -0
  180. package/src/devtools.ts +34 -0
  181. package/src/element.d.ts +59 -0
  182. package/src/element.ts +235 -0
  183. package/src/extra/backoff.ts +9 -0
  184. package/src/extra/behaviors.ts +169 -0
  185. package/src/extra/browserSignals.ts +23 -0
  186. package/src/extra/browserSources.ts +326 -0
  187. package/src/extra/command.ts +3 -3
  188. package/src/extra/controls.ts +47 -0
  189. package/src/extra/copyAsTest.ts +286 -0
  190. package/src/extra/devtools.ts +109 -31
  191. package/src/extra/devtoolsActions.ts +379 -0
  192. package/src/extra/devtoolsHook.ts +9 -0
  193. package/src/extra/devtoolsNext.ts +83 -0
  194. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  195. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  196. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  197. package/src/extra/diagnostics/checks/collections.ts +67 -0
  198. package/src/extra/diagnostics/checks/controls.ts +148 -0
  199. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  200. package/src/extra/diagnostics/checks/dom.ts +250 -0
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +83 -0
  203. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  204. package/src/extra/diagnostics/checks/forms.ts +163 -0
  205. package/src/extra/diagnostics/checks/index.ts +202 -0
  206. package/src/extra/diagnostics/checks/inspect.ts +427 -0
  207. package/src/extra/diagnostics/checks/next.ts +417 -0
  208. package/src/extra/diagnostics/checks/persist.ts +54 -0
  209. package/src/extra/diagnostics/checks/props.ts +56 -0
  210. package/src/extra/diagnostics/checks/public.d.ts +302 -0
  211. package/src/extra/diagnostics/checks/replies.ts +128 -0
  212. package/src/extra/diagnostics/checks/router.ts +100 -0
  213. package/src/extra/diagnostics/checks/rxjsHints.ts +88 -0
  214. package/src/extra/diagnostics/checks/shared.ts +203 -0
  215. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  216. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  217. package/src/extra/diagnostics/checks/state.ts +148 -0
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +55 -0
  220. package/src/extra/diagnostics/checks/timers.ts +80 -0
  221. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  222. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  223. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  224. package/src/extra/diagnostics/checks/wiring.ts +121 -0
  225. package/src/extra/diagnostics/codes.ts +477 -0
  226. package/src/extra/diagnostics/index.ts +353 -0
  227. package/src/extra/diagnostics/legacy.ts +76 -0
  228. package/src/extra/driverFactories.ts +172 -60
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/eventDriver.ts +4 -0
  231. package/src/extra/fetchDriver.ts +519 -0
  232. package/src/extra/flatten.ts +75 -0
  233. package/src/extra/focusWithin.ts +27 -0
  234. package/src/extra/form.ts +268 -0
  235. package/src/extra/formHelpers.ts +139 -0
  236. package/src/extra/head.ts +106 -0
  237. package/src/extra/hmr.ts +2 -3
  238. package/src/extra/owned.ts +24 -0
  239. package/src/extra/pager.ts +50 -0
  240. package/src/extra/persist.ts +133 -0
  241. package/src/extra/pwa.ts +1 -1
  242. package/src/extra/queryCache.ts +107 -0
  243. package/src/extra/reducers.ts +13 -6
  244. package/src/extra/reduxDevtools.ts +92 -0
  245. package/src/extra/replies.ts +54 -0
  246. package/src/extra/router.ts +323 -0
  247. package/src/extra/run.ts +94 -210
  248. package/src/extra/selection.ts +76 -0
  249. package/src/extra/socketDriver.ts +265 -0
  250. package/src/extra/sortable.ts +377 -0
  251. package/src/extra/ssr.ts +427 -160
  252. package/src/extra/standardSchema.ts +27 -0
  253. package/src/extra/testing.ts +3158 -190
  254. package/src/extra/timers.ts +95 -0
  255. package/src/extra/undo.ts +261 -0
  256. package/src/extra/viewTransitions.ts +38 -0
  257. package/src/extra/virtual.ts +513 -0
  258. package/src/extra/widget.ts +201 -0
  259. package/src/extra/xstreamExtras.ts +269 -0
  260. package/src/index.d.ts +3012 -87
  261. package/src/index.ts +32 -10
  262. package/src/jsx-runtime.ts +15 -1
  263. package/src/jsx.ts +2 -1
  264. package/src/lazy.ts +50 -7
  265. package/src/portal.ts +3 -1
  266. package/src/pragma/index.ts +262 -134
  267. package/src/react-peers.d.ts +5 -0
  268. package/src/react.d.ts +43 -0
  269. package/src/react.ts +110 -0
  270. package/src/shared.ts +48 -0
  271. package/src/slot.ts +1 -1
  272. package/src/suspense.ts +3 -1
  273. package/src/switchable.ts +6 -119
  274. package/src/transition.ts +3 -1
  275. package/src/ui/accordion.ts +64 -0
  276. package/src/ui/dialog.ts +129 -0
  277. package/src/ui/disclosure.ts +35 -0
  278. package/src/ui/popover.ts +45 -0
  279. package/src/ui/shared.ts +94 -0
  280. package/src/ui/tabs.ts +82 -0
  281. package/src/ui/toaster.ts +198 -0
  282. package/src/ui/tooltip.ts +70 -0
  283. package/src/ui/zag/combobox.ts +115 -0
  284. package/src/ui/zag/menu.ts +40 -0
  285. package/src/ui/zag/select.ts +54 -0
  286. package/src/ui/zag/shared.ts +43 -0
  287. package/src/ui-combobox.d.ts +20 -0
  288. package/src/ui-combobox.ts +6 -0
  289. package/src/ui-menu.d.ts +30 -0
  290. package/src/ui-menu.ts +6 -0
  291. package/src/ui-select.d.ts +17 -0
  292. package/src/ui-select.ts +6 -0
  293. package/src/ui-zag-types.d.ts +42 -0
  294. package/src/ui.d.ts +208 -0
  295. package/src/ui.ts +15 -0
  296. package/src/vike/+config.ts +9 -1
  297. package/src/vike/ClientOnly.ts +1 -1
  298. package/src/vike/onRenderClient.ts +144 -96
  299. package/src/vike/onRenderHtml.ts +69 -26
  300. package/src/vike/types.ts +27 -4
  301. package/src/vite/globalthis-shim.ts +18 -0
  302. package/src/vite/globalthis.ts +56 -0
  303. package/src/vite/plugin.d.ts +114 -2
  304. package/src/vite/plugin.ts +920 -56
  305. package/src/zag.d.ts +64 -0
  306. package/src/zag.ts +238 -0
  307. package/dist/astro/astro/client.d.ts +0 -5
  308. package/dist/astro/astro/index.d.ts +0 -16
  309. package/dist/astro/astro/server.d.ts +0 -12
  310. package/dist/astro/client.d.ts +0 -5
  311. package/dist/astro/client.esm.js +0 -4665
  312. package/dist/astro/collection.d.ts +0 -12
  313. package/dist/astro/component.d.ts +0 -22
  314. package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
  315. package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
  316. package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
  317. package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
  318. package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
  319. package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
  320. package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
  321. package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
  322. package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
  323. package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
  324. package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
  325. package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
  326. package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
  327. package/dist/astro/cycle/dom/index.d.ts +0 -8
  328. package/dist/astro/cycle/dom/isolate.d.ts +0 -9
  329. package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
  330. package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
  331. package/dist/astro/cycle/dom/modules.d.ts +0 -5
  332. package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
  333. package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
  334. package/dist/astro/cycle/dom/thunk.d.ts +0 -11
  335. package/dist/astro/cycle/dom/utils.d.ts +0 -8
  336. package/dist/astro/cycle/isolate/index.d.ts +0 -42
  337. package/dist/astro/cycle/run/adapt.d.ts +0 -6
  338. package/dist/astro/cycle/run/index.d.ts +0 -36
  339. package/dist/astro/cycle/run/internals.d.ts +0 -8
  340. package/dist/astro/cycle/run/types.d.ts +0 -49
  341. package/dist/astro/cycle/state/Collection.d.ts +0 -29
  342. package/dist/astro/cycle/state/StateSource.d.ts +0 -20
  343. package/dist/astro/cycle/state/index.d.ts +0 -5
  344. package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
  345. package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
  346. package/dist/astro/cycle/state/types.d.ts +0 -18
  347. package/dist/astro/cycle/state/withState.d.ts +0 -17
  348. package/dist/astro/extra/classes.d.ts +0 -7
  349. package/dist/astro/extra/devtools.d.ts +0 -78
  350. package/dist/astro/extra/dragDriver.d.ts +0 -20
  351. package/dist/astro/extra/driverFactories.d.ts +0 -12
  352. package/dist/astro/extra/eventDriver.d.ts +0 -9
  353. package/dist/astro/extra/exactState.d.ts +0 -1
  354. package/dist/astro/extra/hmr.d.ts +0 -17
  355. package/dist/astro/extra/logDriver.d.ts +0 -2
  356. package/dist/astro/extra/processDrag.d.ts +0 -19
  357. package/dist/astro/extra/processForm.d.ts +0 -15
  358. package/dist/astro/extra/run.d.ts +0 -13
  359. package/dist/astro/extra/xstreamCompat.d.ts +0 -5
  360. package/dist/astro/index.d.ts +0 -19
  361. package/dist/astro/index.esm.js +0 -27
  362. package/dist/astro/jsx-dev-runtime.d.ts +0 -1
  363. package/dist/astro/jsx-runtime.d.ts +0 -4
  364. package/dist/astro/jsx.d.ts +0 -2
  365. package/dist/astro/pragma/fn.d.ts +0 -7
  366. package/dist/astro/pragma/index.d.ts +0 -7
  367. package/dist/astro/pragma/is.d.ts +0 -10
  368. package/dist/astro/server.d.ts +0 -12
  369. package/dist/astro/server.esm.js +0 -25
  370. package/dist/astro/switchable.d.ts +0 -7
  371. package/dist/collection.d.ts +0 -12
  372. package/dist/component.d.ts +0 -22
  373. package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
  374. package/dist/cycle/dom/DOMSource.d.ts +0 -11
  375. package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
  376. package/dist/cycle/dom/ElementFinder.d.ts +0 -8
  377. package/dist/cycle/dom/EventDelegator.d.ts +0 -34
  378. package/dist/cycle/dom/IsolateModule.d.ts +0 -23
  379. package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
  380. package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
  381. package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
  382. package/dist/cycle/dom/SymbolTree.d.ts +0 -9
  383. package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
  384. package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
  385. package/dist/cycle/dom/fromEvent.d.ts +0 -6
  386. package/dist/cycle/dom/index.d.ts +0 -8
  387. package/dist/cycle/dom/isolate.d.ts +0 -9
  388. package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
  389. package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
  390. package/dist/cycle/dom/modules.d.ts +0 -5
  391. package/dist/cycle/dom/snabbdom.d.ts +0 -22
  392. package/dist/cycle/dom/styleModule.d.ts +0 -13
  393. package/dist/cycle/dom/thunk.d.ts +0 -11
  394. package/dist/cycle/dom/utils.d.ts +0 -8
  395. package/dist/cycle/isolate/index.d.ts +0 -42
  396. package/dist/cycle/run/adapt.d.ts +0 -6
  397. package/dist/cycle/run/index.d.ts +0 -36
  398. package/dist/cycle/run/internals.d.ts +0 -8
  399. package/dist/cycle/run/types.d.ts +0 -49
  400. package/dist/cycle/state/Collection.d.ts +0 -29
  401. package/dist/cycle/state/StateSource.d.ts +0 -20
  402. package/dist/cycle/state/index.d.ts +0 -5
  403. package/dist/cycle/state/pickCombine.d.ts +0 -3
  404. package/dist/cycle/state/pickMerge.d.ts +0 -3
  405. package/dist/cycle/state/types.d.ts +0 -18
  406. package/dist/cycle/state/withState.d.ts +0 -17
  407. package/dist/extra/classes.d.ts +0 -7
  408. package/dist/extra/devtools.d.ts +0 -78
  409. package/dist/extra/dragDriver.d.ts +0 -20
  410. package/dist/extra/driverFactories.d.ts +0 -12
  411. package/dist/extra/eventDriver.d.ts +0 -9
  412. package/dist/extra/exactState.d.ts +0 -1
  413. package/dist/extra/hmr.d.ts +0 -17
  414. package/dist/extra/logDriver.d.ts +0 -2
  415. package/dist/extra/processDrag.d.ts +0 -19
  416. package/dist/extra/processForm.d.ts +0 -15
  417. package/dist/extra/run.d.ts +0 -13
  418. package/dist/extra/xstreamCompat.d.ts +0 -5
  419. package/dist/jsx-dev-runtime.d.ts +0 -1
  420. package/dist/jsx-dev-runtime.js +0 -224
  421. package/dist/jsx-runtime.d.ts +0 -4
  422. package/dist/jsx-runtime.js +0 -224
  423. package/dist/jsx.d.ts +0 -2
  424. package/dist/pragma/fn.d.ts +0 -7
  425. package/dist/pragma/index.d.ts +0 -7
  426. package/dist/pragma/is.d.ts +0 -10
  427. package/dist/switchable.d.ts +0 -7
  428. package/dist/vike/+config.cjs.js +0 -65
  429. package/src/component.ts +0 -2256
  430. package/src/cycle/isolate/index.ts +0 -196
  431. package/src/cycle/run/index.ts +0 -151
  432. package/src/cycle/run/internals.ts +0 -143
  433. package/src/cycle/state/Collection.ts +0 -174
  434. package/src/cycle/state/index.ts +0 -5
  435. package/src/cycle/state/pickCombine.ts +0 -167
  436. package/src/cycle/state/pickMerge.ts +0 -122
  437. package/src/cycle/state/withState.ts +0 -47
  438. package/src/pragma/fn.ts +0 -55
@@ -0,0 +1,1419 @@
1
+ import { getDiagnostics } from 'sygnal';
2
+
3
+ /*
4
+ * Tiny helpers shared by the core and the helper modules (behaviors, undo, the dev entry), kept
5
+ * out of the core so importing them never pulls it in.
6
+ */
7
+ /** The "no change / send nothing" sentinel (a registered symbol: the same in every copy) */
8
+ /**
9
+ * Whether a value is ABORT. Compares the description, not the identity, in case bundlers (e.g.
10
+ * Vite) create duplicate module instances with separate Symbol.for() registries. (G-214: the
11
+ * one copy, for the core, behaviors, undo and the action log)
12
+ */
13
+ const isAbort = (v) => typeof v == 'symbol' && v.description == 'sygnal.ABORT';
14
+ /**
15
+ * Set on a dev-only wrapper of a model function (SYG222's) to the function it wraps, so the
16
+ * action log and t.explain() show the user's reducer (G-214 follow-up)
17
+ */
18
+ const ORIGINAL = Symbol.for('sygnal.original');
19
+
20
+ /**
21
+ * PLAN-4 2-C (GS-10): the action log. Records every action a component instance runs as
22
+ * { type, data, component, instance, sinks, cause, time }
23
+ * for renderComponent's t.actions / t.explain() (src/extra/testing.ts), inspect({ actions }) in
24
+ * the 'sygnal/diagnostics' dev entry (./inspect.ts) and the DevTools action log
25
+ * (src/extra/devtoolsActions.ts).
26
+ *
27
+ * 0 B in apps: nothing in the core calls this. actionHooks() is a hook layer of the core
28
+ * (04-hooks-contract §3.3): onAction opens the record (the core passes the cause: 'intent',
29
+ * 'next', 'reply', 'built-in', 'simulateAction'; an intent action a behavior owns is
30
+ * 'behavior'), wrapHandler sees which sinks produced a value. (The old core's version patched
31
+ * each instance; R5 removed it.)
32
+ *
33
+ * Sinks: a function reducer counts when it returns a value: not ABORT (or another symbol, which
34
+ * the core drops), and for STATE not the state it was given (GS-4: no change); EFFECT counts when
35
+ * the handler ran without ABORT. A constant / `true` entry always sends. A throwing reducer
36
+ * sends nothing.
37
+ */
38
+ const BUILT_IN = /^(BOOTSTRAP|INITIALIZE|DISPOSE|RESOURCE)$/;
39
+ /** now on the test's fake clock when there is one (vi.useFakeTimers()), else Date.now() */
40
+ const clockNow = () => {
41
+ const c = setTimeout.clock;
42
+ return c ? c.now : Date.now();
43
+ };
44
+ /**
45
+ * PLAN-4.6 R4: the same log on the core, from its hooks (04 §3.3) instead of instance
46
+ * patches: onAction opens the record (the core passes the cause: 'intent', 'next', 'reply',
47
+ * 'built-in', 'simulateAction'), wrapHandler sees which sinks produced a value. Same records,
48
+ * same cause rules: a built-in type is 'built-in' unless simulated; an intent action a behavior
49
+ * owns is 'behavior'. `only(inst)`: the instances to record (renderComponent: its own tree).
50
+ */
51
+ function actionHooks(listener, only) {
52
+ const cur = new WeakMap();
53
+ // INITIALIZE: the core writes the initial state at creation (no action, unless the model
54
+ // has an INITIALIZE entry); today's log has an INITIALIZE record with STATE, so it is opened here
55
+ const init = new WeakMap();
56
+ return {
57
+ onCreate(inst) {
58
+ const d = inst.def;
59
+ if ((only && !only(inst)) || d.initialState === undefined || !(inst.isRoot || d.isolated))
60
+ return;
61
+ const rec = { type: 'INITIALIZE', data: d.initialState, component: inst.name, instance: String(inst.id), sinks: [], cause: 'built-in', time: clockNow() };
62
+ init.set(inst, rec);
63
+ try {
64
+ listener.action(rec, inst);
65
+ }
66
+ catch (_) { /* ignore */ }
67
+ addSink(listener, rec, ['STATE'], 'STATE', undefined);
68
+ },
69
+ onAction(inst, a) {
70
+ if (only && !only(inst))
71
+ return;
72
+ const type = String(a.type), owned = inst.def.behaviorActions;
73
+ const hs = inst.def.handlers.get(type) || [], order = hs.map((h) => h[0]);
74
+ const opened = type == 'INITIALIZE' && init.get(inst);
75
+ if (opened) {
76
+ init.delete(inst);
77
+ cur.set(inst, [opened, order]);
78
+ return;
79
+ }
80
+ const cause = a.cause == 'simulateAction' ? a.cause : BUILT_IN.test(type) ? 'built-in'
81
+ : a.cause == 'next' || a.cause == 'reply' ? a.cause
82
+ : owned && Object.prototype.hasOwnProperty.call(owned, type) ? 'behavior' : 'intent';
83
+ const rec = { type, data: a.data, component: inst.name, instance: String(inst.id), sinks: [], cause, time: clockNow(), ...(a.source !== undefined && { source: a.source }) };
84
+ cur.set(inst, [rec, order]);
85
+ try {
86
+ listener.action(rec, inst);
87
+ }
88
+ catch (_) { /* a listener never breaks the app */ }
89
+ // a constant / `true` entry always sends (EFFECT runs functions only)
90
+ for (const [sink, h] of hs)
91
+ if (typeof h != 'function' && sink != 'EFFECT')
92
+ addSink(listener, rec, order, sink, undefined);
93
+ },
94
+ wrapHandler(inst, type, sink, fn) {
95
+ const c = cur.get(inst);
96
+ if (!c || c[0].type !== type || typeof fn != 'function')
97
+ return;
98
+ const [rec, order] = c, isState = sink == 'STATE', isEffect = sink == 'EFFECT';
99
+ return function (state) {
100
+ const r = fn.apply(this, arguments);
101
+ if (!isAbort(r) && (isEffect || typeof r != 'symbol') && !(isState && r === state))
102
+ addSink(listener, rec, order, sink, fn[ORIGINAL] || fn);
103
+ return r;
104
+ };
105
+ },
106
+ };
107
+ }
108
+ function addSink(l, rec, order, sink, reducer) {
109
+ if (!rec.sinks.includes(sink)) {
110
+ rec.sinks.push(sink);
111
+ rec.sinks.sort((a, b) => order.indexOf(a) - order.indexOf(b));
112
+ }
113
+ try {
114
+ l.sink?.(rec, sink, reducer);
115
+ }
116
+ catch (_) { /* ignore */ }
117
+ }
118
+
119
+ /**
120
+ * PLAN-4.6 R4: 'sygnal/devtools' on the next component core (04 §3.5).
121
+ *
122
+ * The core calls no `window.__SYGNAL_DEVTOOLS__` method itself: each app reads the dev
123
+ * entries' hook layers once (`__SYGNAL_DIAGNOSTICS__.layers`), and this file's layer maps the
124
+ * hooks onto the bridge's existing methods (onComponentCreated, onStateChanged, ...). The bridge
125
+ * keeps a facade per instance with the fields it reads from today's instances (currentState,
126
+ * model, sourceNames, ...); time travel and the debug toggle go through the app's runtime API
127
+ * (`setState`, `setDebug`) instead of `sinks.STATE.shamefullySendNext` and `instance._debug`.
128
+ */
129
+ const facades = new WeakMap();
130
+ /** the bridge's view of a core instance (an InstanceView + its app's runtime API) */
131
+ function devFacade(iv, api) {
132
+ let f = facades.get(iv);
133
+ if (f)
134
+ return f;
135
+ f = {
136
+ __next: iv,
137
+ __api: api,
138
+ get _componentNumber() { return iv.id; },
139
+ get name() { return iv.name; },
140
+ get isSubComponent() { return !iv.isRoot; },
141
+ get parentId() { return iv.parentId; },
142
+ get view() { return iv.def.view; },
143
+ get model() {
144
+ const m = {};
145
+ iv.def.handlers.forEach((hs, a) => { const e = m[a] = {}; for (const [s, fn] of hs)
146
+ e[s] = fn; });
147
+ return m;
148
+ },
149
+ get intent() { return iv.def.intent; },
150
+ get context() { return iv.def.view.context; },
151
+ get calculated() { return iv.def.view.calculated; },
152
+ get _behaviorActions() { return iv.def.behaviorActions; },
153
+ get currentState() { return iv.state; },
154
+ get currentContext() { return iv.context; },
155
+ get currentProps() { return iv.props; },
156
+ get sources() { return iv.sources; },
157
+ get sourceNames() { return Object.keys(iv.sources); },
158
+ stateSourceName: 'STATE',
159
+ get _debug() { return false; },
160
+ /** the state without its calculated fields (as today's cleanupCalculated) */
161
+ cleanupCalculated(s) {
162
+ const c = iv.def.calcNames;
163
+ if (!c || !s || typeof s != 'object' || Array.isArray(s))
164
+ return s;
165
+ const out = { ...s };
166
+ c.forEach((k) => { delete out[k]; });
167
+ return out;
168
+ },
169
+ };
170
+ facades.set(iv, f);
171
+ return f;
172
+ }
173
+ /** the hook layer of the DevTools bridge `dt` for one app */
174
+ function devtoolsHooks(dt, api) {
175
+ const mounted = new WeakSet();
176
+ return {
177
+ onCreate(iv) {
178
+ const f = devFacade(iv, api);
179
+ dt.onComponentCreated(iv.id, iv.name, f);
180
+ if (iv.parentId !== undefined)
181
+ dt.onSubComponentRegistered(iv.parentId, iv.id);
182
+ // a Collection's first item (today's onCollectionMounted)
183
+ if (iv.kind == 'item' && iv.parentId !== undefined) {
184
+ const p = api.get(iv.parentId);
185
+ if (p && !mounted.has(p)) {
186
+ mounted.add(p);
187
+ dt.onCollectionMounted(p.id, p.name, iv.name, null);
188
+ }
189
+ }
190
+ },
191
+ onStateChanged(iv, s) { dt.connected && dt.onStateChanged(iv.id, iv.name, s); },
192
+ onPropsChanged(iv, p) { dt.connected && dt.onPropsChanged(iv.id, iv.name, p); },
193
+ onContextChanged(iv, c) { dt.connected && dt.onContextChanged(iv.id, iv.name, c); },
194
+ onAction(iv, a) { dt.connected && dt.onActionDispatched(iv.id, iv.name, a.type, a.data); },
195
+ onReady(p, c, ready) { dt.connected && dt.onReadyChanged(p.id, p.name, c.name, ready); },
196
+ onDispose(iv) { dt.onComponentDisposed(iv.id, iv.name); },
197
+ };
198
+ }
199
+ /** register a hook layer factory with the core (read by each app at its start) */
200
+ function addLayer(f) {
201
+ const core = globalThis.__SYGNAL_DIAGNOSTICS__;
202
+ if (!core)
203
+ return () => { };
204
+ (core.layers || (core.layers = new Set())).add(f);
205
+ return () => { core.layers?.delete(f); };
206
+ }
207
+
208
+ /**
209
+ * PLAN-4 3-E (GS-10): the DevTools action log, in the dev-only 'sygnal/devtools' entry (0 B in
210
+ * apps: nothing in the core calls it).
211
+ *
212
+ * It records every action of every component instance through 2-C's recorder
213
+ * (./diagnostics/checks/actionLog: actionHooks), a hook layer every app reads from the core's
214
+ * diagnostics bridge (globalThis.__SYGNAL_DIAGNOSTICS__.layers), so it records with diagnostics
215
+ * off too. On top of 2-C's record it keeps:
216
+ * seq, at a sequence number, ms since the session started
217
+ * parent the parent instance id (null: a root)
218
+ * before, after the instance's state around the action's STATE reducer (onReducer), when it
219
+ * changed the state (`after` without calculated fields)
220
+ * replySink for a reply action (and RESOURCE): the source that delivered it, and
221
+ * replyKind 'fetch' when that source is makeFetchDriver's (renderComponent fakes it)
222
+ *
223
+ * A session runs from the recorder's start (or the last clearActions()) to now. getSession()
224
+ * turns it into a SessionRecording for one instance (./copyAsTest generates the test).
225
+ */
226
+ const MAX = 5000;
227
+ const STANDARD = /^(DOM|EVENTS|LOG|CHILD|PARENT|READY|props\$|children\$|dispose\$)$/;
228
+ let actions = [];
229
+ let seq = 0;
230
+ let epoch = clockNow();
231
+ let truncated = false;
232
+ let created = 0;
233
+ const insts = new Map();
234
+ const byRec = new WeakMap();
235
+ /** instance -> the action whose STATE reducer just produced a value (its onReducer follows) */
236
+ const pendingState = new WeakMap();
237
+ const subscribers = new Set();
238
+ const notify = (a, kind) => {
239
+ for (const s of subscribers)
240
+ try {
241
+ s(a, kind);
242
+ }
243
+ catch (_) { /* a subscriber never breaks the app */ }
244
+ };
245
+ const idOf = (c) => String(c && c._componentNumber);
246
+ function ensure(c) {
247
+ const id = idOf(c);
248
+ let i = insts.get(id);
249
+ if (!i || i.c !== c) {
250
+ const p = c && c.__next && c.__next.parentId;
251
+ i = { c, id, name: c && c.name, parent: typeof p == 'number' ? String(p) : null, stateName: (c && c.stateSourceName) || 'STATE',
252
+ inSession: true, hasInitial: false, disposed: false };
253
+ i.order = ++created;
254
+ insts.set(id, i);
255
+ }
256
+ return i;
257
+ }
258
+ function clean(c, s) {
259
+ try {
260
+ return typeof c.cleanupCalculated == 'function' ? c.cleanupCalculated(s) : s;
261
+ }
262
+ catch (_) {
263
+ return s;
264
+ }
265
+ }
266
+ const listener = {
267
+ action(r, c) {
268
+ const info = ensure(c);
269
+ // the instance's state when its session started: INITIALIZE's data (its initial state), else
270
+ // the state it had at its first action
271
+ if (r.type == 'INITIALIZE' && info.inSession) {
272
+ info.initial = r.data;
273
+ info.hasInitial = true;
274
+ }
275
+ else if (!info.hasInitial) {
276
+ info.initial = c.currentState;
277
+ info.hasInitial = true;
278
+ }
279
+ const a = { seq: ++seq, type: r.type, data: r.data, component: r.component, instance: r.instance,
280
+ parent: info.parent, sinks: r.sinks, cause: r.cause, at: r.time - epoch };
281
+ const slot = r.source;
282
+ if (slot !== undefined && (r.cause == 'reply' || r.type == 'RESOURCE')) {
283
+ a.replySink = slot;
284
+ a.replyKind = c.sources && c.sources[slot] && c.sources[slot].__sygnalStatic === 'resources' ? 'fetch' : 'other';
285
+ }
286
+ if (r.cause == 'intent' && isStateEcho(r.data, c.currentState))
287
+ a.echo = true;
288
+ byRec.set(r, a);
289
+ actions.push(a);
290
+ if (actions.length > MAX) {
291
+ actions.shift();
292
+ truncated = true;
293
+ }
294
+ notify(a, 'add');
295
+ },
296
+ sink(r, sink) {
297
+ const a = byRec.get(r);
298
+ if (!a)
299
+ return;
300
+ const info = insts.get(r.instance);
301
+ if (info && info.c && sink == info.stateName)
302
+ pendingState.set(info.c, a);
303
+ notify(a, 'update');
304
+ },
305
+ };
306
+ function onReducer(c, name, prev, next) {
307
+ const a = pendingState.get(c);
308
+ if (!a || a.type !== name || 'after' in a)
309
+ return;
310
+ pendingState.delete(c);
311
+ a.before = prev;
312
+ a.after = clean(c, next);
313
+ notify(a, 'update');
314
+ }
315
+ function onDispose(c) {
316
+ const i = insts.get(idOf(c));
317
+ if (i && i.c === c) {
318
+ i.disposed = true;
319
+ i.c = undefined;
320
+ }
321
+ }
322
+ let unregister;
323
+ /** PLAN-4.6 R4: the same recorder on the core, from its hooks (per app; a facade per instance) */
324
+ function nextLayer(api) {
325
+ const L = {
326
+ action: (r, iv) => listener.action(r, devFacade(iv, api)),
327
+ sink: (r, sink, reducer) => listener.sink(r, sink, reducer),
328
+ };
329
+ const log = actionHooks(L);
330
+ return {
331
+ ...log,
332
+ onCreate(iv) { ensure(devFacade(iv, api)); log.onCreate(iv); },
333
+ onReducer(iv, name, prev, next) { onReducer(devFacade(iv, api), name, prev, next); },
334
+ onDispose(iv) { onDispose(devFacade(iv, api)); },
335
+ };
336
+ }
337
+ /**
338
+ * Start recording actions (idempotent; 'sygnal/devtools' does it on import in a browser). Only
339
+ * components created afterwards are recorded. Returns a function that stops recording.
340
+ */
341
+ function recordActions() {
342
+ if (!unregister) {
343
+ const core = globalThis.__SYGNAL_DIAGNOSTICS__;
344
+ if (!core)
345
+ return () => { };
346
+ unregister = addLayer(nextLayer);
347
+ }
348
+ return stopRecording;
349
+ }
350
+ function stopRecording() {
351
+ if (unregister) {
352
+ unregister();
353
+ unregister = undefined;
354
+ }
355
+ }
356
+ const isRecording = () => !!unregister;
357
+ /** Start a new session: forget the recorded actions; each live instance's state is its new initial state. */
358
+ function clearActions() {
359
+ actions = [];
360
+ epoch = clockNow();
361
+ truncated = false;
362
+ for (const [id, i] of insts) {
363
+ if (i.disposed) {
364
+ insts.delete(id);
365
+ continue;
366
+ }
367
+ i.inSession = false;
368
+ i.initial = i.c && i.c.currentState;
369
+ i.hasInitial = true;
370
+ }
371
+ notify(null, 'reset');
372
+ }
373
+ const matches = (a, f) => (f.component === undefined || a.component === f.component) &&
374
+ (f.instance === undefined || a.instance === String(f.instance)) &&
375
+ (f.type === undefined || (typeof f.type == 'string' ? a.type === f.type : f.type.test(a.type))) &&
376
+ (f.cause === undefined || [].concat(f.cause).includes(a.cause));
377
+ /** The session's actions (oldest first), optionally filtered by component, instance, type or cause. */
378
+ function getActions(filter) {
379
+ return filter ? actions.filter(a => matches(a, filter)) : actions.slice();
380
+ }
381
+ /** Subscribe to the log: (action, 'add' | 'update') and (null, 'reset'). Returns unsubscribe. */
382
+ function onAction(fn) {
383
+ subscribers.add(fn);
384
+ return () => { subscribers.delete(fn); };
385
+ }
386
+ /** the live instances, newest first */
387
+ const live = () => [...insts.values()].filter(i => !i.disposed && i.c).sort((a, b) => b.order - a.order);
388
+ /**
389
+ * The instance a session target names: undefined (the newest live root), an instance id, a
390
+ * component function (its newest live instance) or run()'s result (its root, by DOM source).
391
+ */
392
+ function resolveInstance(target) {
393
+ const all = live();
394
+ if (target === undefined || target === null)
395
+ return all.find(i => i.parent === null);
396
+ if (typeof target == 'string' || typeof target == 'number')
397
+ return insts.get(String(target));
398
+ if (typeof target == 'function')
399
+ return all.find(i => i.c.view === target);
400
+ if (typeof target == 'object') {
401
+ const s = target.sources;
402
+ if (s && typeof s == 'object') {
403
+ return all.find(i => i.parent === null && Object.keys(s).some(k => k != 'STATE' && s[k] && i.c.sources && i.c.sources[k] === s[k])) ||
404
+ all.find(i => i.parent === null);
405
+ }
406
+ }
407
+ return undefined;
408
+ }
409
+ function descendants(id) {
410
+ const out = new Set();
411
+ let grew = true;
412
+ while (grew) {
413
+ grew = false;
414
+ for (const i of insts.values())
415
+ if (i.parent !== null && (i.parent === id || out.has(i.parent)) && !out.has(i.id)) {
416
+ out.add(i.id);
417
+ grew = true;
418
+ }
419
+ }
420
+ // instances already pruned: their actions still name their parent
421
+ for (const a of actions)
422
+ if (a.parent !== null && (a.parent === id || out.has(a.parent)))
423
+ out.add(a.instance);
424
+ return out;
425
+ }
426
+ const REPLAYED = new Set(['intent', 'simulateAction', 'behavior', 'reply']);
427
+ /**
428
+ * The session of one instance (see resolveInstance for `target`) as plain data: its initial
429
+ * and final state, its own actions, and the state changes of its descendants (a replay at this
430
+ * instance can't reproduce them).
431
+ */
432
+ function getSession(target) {
433
+ const info = resolveInstance(target);
434
+ if (!info)
435
+ throw new Error('[Sygnal DevTools] getSession: no live component instance matches' + (target === undefined ? ' (no root recorded: import sygnal/devtools before run())' : ''));
436
+ const c = info.c;
437
+ const desc = descendants(info.id);
438
+ const stateNameOf = (id) => insts.get(id)?.stateName || 'STATE';
439
+ const model = c.model && typeof c.model == 'object' ? Object.keys(c.model).map(k => k.split('|')[0].trim()) : undefined;
440
+ const behaviors = c._behaviorActions && typeof c._behaviorActions == 'object' ? Object.keys(c._behaviorActions) : [];
441
+ const sources = c.sources || {};
442
+ const drivers = (c.sourceNames || Object.keys(sources)).filter((n) => !STANDARD.test(n) && n !== info.stateName && n != 'STATE' && n != 'state' && !n.startsWith('__'));
443
+ return {
444
+ version: 1,
445
+ component: info.name,
446
+ instance: info.id,
447
+ initialState: info.hasInitial ? info.initial : undefined,
448
+ definitionInitialState: c.view ? c.view.initialState : undefined,
449
+ finalState: c.currentState,
450
+ actionNames: model ? [...model, ...behaviors] : undefined,
451
+ drivers,
452
+ fakeable: drivers.filter((n) => sources[n] && sources[n].__sygnalStatic === 'resources'),
453
+ actions: actions.filter(a => a.instance === info.id).map(a => ({
454
+ type: a.type, data: a.data, cause: a.cause, sinks: [...a.sinks], at: a.at,
455
+ ...(a.replySink !== undefined ? { replySink: a.replySink, replyKind: a.replyKind } : {}),
456
+ ...(a.echo ? { echo: true } : {}),
457
+ })),
458
+ foreign: actions.filter(a => desc.has(a.instance) && REPLAYED.has(a.cause) && a.sinks.includes(stateNameOf(a.instance)))
459
+ .map(a => ({ type: a.type, component: a.component, instance: a.instance, cause: a.cause })),
460
+ ...(truncated ? { truncated: true } : {}),
461
+ };
462
+ }
463
+ /** A JSON-safe copy for the panel: DOM elements and events become short descriptions. */
464
+ function preview(v) {
465
+ if (v === undefined)
466
+ return undefined;
467
+ const seen = new WeakSet();
468
+ try {
469
+ return JSON.parse(JSON.stringify(v, (_k, x) => {
470
+ if (typeof x == 'function')
471
+ return `[Function ${x.name || 'anonymous'}]`;
472
+ if (typeof x == 'symbol')
473
+ return String(x);
474
+ if (typeof x == 'bigint')
475
+ return `${x}n`;
476
+ if (x && typeof x == 'object') {
477
+ if (isElement(x))
478
+ return describeElement(x);
479
+ if (isEvent(x))
480
+ return `[${x.constructor && x.constructor.name || 'Event'} ${x.type}]`;
481
+ if (x instanceof Map)
482
+ return { '[Map]': [...x.entries()] };
483
+ if (x instanceof Set)
484
+ return { '[Set]': [...x.values()] };
485
+ if (seen.has(x))
486
+ return '[Circular]';
487
+ seen.add(x);
488
+ }
489
+ return x;
490
+ }));
491
+ }
492
+ catch (_) {
493
+ return '[unserializable]';
494
+ }
495
+ }
496
+ const isPlain = (x) => !!x && typeof x == 'object' && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null);
497
+ function deepEqual(a, b) {
498
+ if (Object.is(a, b))
499
+ return true;
500
+ if (!a || !b || typeof a != 'object' || typeof b != 'object')
501
+ return false;
502
+ if (Array.isArray(a) !== Array.isArray(b))
503
+ return false;
504
+ const ka = Object.keys(a).filter(k => a[k] !== undefined), kb = Object.keys(b).filter(k => b[k] !== undefined);
505
+ return ka.length == kb.length && ka.every(k => deepEqual(a[k], b[k]));
506
+ }
507
+ /**
508
+ * The data is the state itself (STATE.stream in the intent; calculated fields may be added): the
509
+ * replay sends it again by itself, so it isn't replayed.
510
+ */
511
+ function isStateEcho(data, state) {
512
+ if (!isPlain(data) || !isPlain(state))
513
+ return false;
514
+ const keys = Object.keys(state);
515
+ return keys.length > 0 && (data === state || keys.every(k => deepEqual(data[k], state[k])));
516
+ }
517
+ const isElement = (x) => !!x && typeof x == 'object' && x.nodeType === 1 && typeof x.tagName == 'string';
518
+ const isEvent = (x) => !!x && typeof x == 'object' && typeof x.type == 'string' && typeof x.preventDefault == 'function' && 'target' in x;
519
+ const describeElement = (el) => {
520
+ const cls = typeof el.className == 'string' && el.className.trim() ? '.' + el.className.trim().split(/\s+/).join('.') : '';
521
+ return `<${String(el.tagName).toLowerCase()}${el.id ? '#' + el.id : ''}${cls}>`;
522
+ };
523
+
524
+ const REPLAY = new Set(['intent', 'simulateAction', 'behavior']);
525
+ const IDENT = /^[A-Za-z_$][\w$]*$/;
526
+ class Unserializable extends Error {
527
+ }
528
+ /** a single-quoted JS string literal */
529
+ const str = (s) => `'${JSON.stringify(s).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'")}'`;
530
+ /** `v` as a JS literal. mode 'data' turns DOM events / elements into stubs; 'state' refuses them. */
531
+ function literal(v, mode = 'data', indent = '') {
532
+ const flag = { stubbed: false };
533
+ const code = lit(v, mode, indent, new Set(), flag);
534
+ return { code, stubbed: flag.stubbed };
535
+ }
536
+ function elementStub(el) {
537
+ const out = {};
538
+ const ds = el.dataset && typeof el.dataset == 'object' ? { ...el.dataset } : {};
539
+ if (Object.keys(ds).length)
540
+ out.dataset = ds;
541
+ if (el.id)
542
+ out.id = el.id;
543
+ const tag = String(el.tagName).toUpperCase();
544
+ if (/^(INPUT|SELECT|TEXTAREA)$/.test(tag) && typeof el.value == 'string')
545
+ out.value = el.value;
546
+ if (tag == 'INPUT' && /^(checkbox|radio)$/i.test(el.type || ''))
547
+ out.checked = !!el.checked;
548
+ return out;
549
+ }
550
+ function eventStub(e) {
551
+ const out = { type: e.type };
552
+ if (typeof e.key == 'string')
553
+ out.key = e.key;
554
+ if (isElement(e.target)) {
555
+ const t = elementStub(e.target);
556
+ if (Object.keys(t).length)
557
+ out.target = t;
558
+ }
559
+ return out;
560
+ }
561
+ function lit(v, mode, ind, seen, flag) {
562
+ if (v === null)
563
+ return 'null';
564
+ switch (typeof v) {
565
+ case 'undefined': return 'undefined';
566
+ case 'boolean': return String(v);
567
+ case 'string': return str(v);
568
+ case 'number': return Object.is(v, -0) ? '-0' : String(v);
569
+ case 'bigint': return `${v}n`;
570
+ case 'function': throw new Unserializable(`a function (${v.name || 'anonymous'})`);
571
+ case 'symbol': throw new Unserializable(`a symbol (${String(v)})`);
572
+ }
573
+ if (seen.has(v))
574
+ throw new Unserializable('a circular reference');
575
+ if (isEvent(v) || isElement(v)) {
576
+ if (mode == 'state')
577
+ throw new Unserializable(isEvent(v) ? `a DOM ${v.type} event` : 'a DOM element');
578
+ flag.stubbed = true;
579
+ return lit(isEvent(v) ? eventStub(v) : elementStub(v), mode, ind, seen, flag);
580
+ }
581
+ seen.add(v);
582
+ try {
583
+ const inner = ind + ' ';
584
+ const list = (items, open, close) => {
585
+ if (!items.length)
586
+ return open + close;
587
+ const one = open + (open == '{' ? ' ' : '') + items.join(', ') + (open == '{' ? ' ' : '') + close;
588
+ if (one.length + ind.length <= 100 && !one.includes('\n'))
589
+ return one;
590
+ return open + '\n' + items.map(s => inner + s).join(',\n') + ',\n' + ind + close;
591
+ };
592
+ if (Array.isArray(v))
593
+ return list(Array.from(v, x => lit(x, mode, inner, seen, flag)), '[', ']');
594
+ if (v instanceof Date)
595
+ return `new Date(${str(isNaN(+v) ? 'Invalid Date' : v.toISOString())})`;
596
+ if (v instanceof Map)
597
+ return `new Map(${list([...v].map(([k, x]) => list([lit(k, mode, inner + ' ', seen, flag), lit(x, mode, inner + ' ', seen, flag)], '[', ']')), '[', ']')})`;
598
+ if (v instanceof Set)
599
+ return `new Set(${list([...v].map(x => lit(x, mode, inner, seen, flag)), '[', ']')})`;
600
+ const proto = Object.getPrototypeOf(v);
601
+ // G-265: a vnode of the JSX pragma (its own constructor, flagged $p) is a plain object
602
+ if (proto !== Object.prototype && proto !== null && !v.$p)
603
+ throw new Unserializable(`a ${(v.constructor && v.constructor.name) || 'class instance'}`);
604
+ const items = [];
605
+ for (const k of Object.keys(v)) {
606
+ if (v[k] === undefined)
607
+ continue;
608
+ items.push(`${IDENT.test(k) ? k : str(k)}: ${lit(v[k], mode, inner, seen, flag)}`);
609
+ }
610
+ return list(items, '{', '}');
611
+ }
612
+ finally {
613
+ seen.delete(v);
614
+ }
615
+ }
616
+ const q = str;
617
+ const tryLit = (v, mode, ind) => {
618
+ try {
619
+ return literal(v, mode, ind);
620
+ }
621
+ catch (e) {
622
+ if (e instanceof Unserializable)
623
+ return { error: e.message };
624
+ throw e;
625
+ }
626
+ };
627
+ /** an error reply from makeFetchDriver: { error, request, status?, body? } for the request's `error` action */
628
+ const isErrorReply = (a) => a.data && typeof a.data == 'object' && 'error' in a.data && a.data.request && typeof a.data.request == 'object' && a.data.request.error === a.type;
629
+ /** The test for a recorded session (see the module comment), with what it left out. */
630
+ function sessionToTest(rec, options = {}) {
631
+ const name = options.componentName || (IDENT.test(rec.component) ? rec.component : 'Component');
632
+ const warnings = [];
633
+ const incomplete = [];
634
+ const body = [];
635
+ const I = ' ';
636
+ const drivers = options.drivers || {};
637
+ let replayed = 0;
638
+ if (rec.truncated)
639
+ incomplete.push('the session lost its oldest actions (clear the log, then record a shorter session)');
640
+ // render options
641
+ const opts = [];
642
+ if (rec.initialState !== undefined && !deepEqual(rec.initialState, rec.definitionInitialState)) {
643
+ const l = tryLit(rec.initialState, 'state', I);
644
+ if ('error' in l) {
645
+ warnings.push(`the initial state holds ${l.error}: rendering with the component's own initialState`);
646
+ incomplete.push('the initial state could not be written');
647
+ }
648
+ else
649
+ opts.push(`initialState: ${l.code}`);
650
+ }
651
+ const fakeable = new Set(rec.fakeable);
652
+ const driverNames = [...new Set([...rec.drivers.filter(n => !fakeable.has(n)), ...Object.keys(drivers)])];
653
+ const given = driverNames.filter(n => drivers[n] !== undefined);
654
+ const missing = driverNames.filter(n => drivers[n] === undefined);
655
+ if (given.length)
656
+ opts.push(`drivers: { ${given.map(n => `${IDENT.test(n) ? n : q(n)}: ${drivers[n]}`).join(', ')} }`);
657
+ if (missing.length)
658
+ warnings.push(`the app had ${missing.length == 1 ? 'a driver' : 'drivers'} for ${missing.join(', ')}: pass ${missing.length == 1 ? 'it' : 'them'} with copyAsTest(..., { drivers: { ${missing[0]}: '...' } }) if the component reads ${missing.length == 1 ? 'it' : 'them'}`);
659
+ if (options.renderOptions)
660
+ opts.push(options.renderOptions);
661
+ const names = rec.actionNames && new Set(rec.actionNames);
662
+ // (G-218: t.respond / t.fail right after queued simulateAction calls wait for the request
663
+ // those calls send, so no settle() is needed before them)
664
+ const answer = (line) => body.push(line);
665
+ for (const a of rec.actions) {
666
+ const changes = a.sinks.includes('STATE') || a.sinks.includes('state');
667
+ if (REPLAY.has(a.cause)) {
668
+ // an intent over the component's own STATE stream: the replay produces it again
669
+ if (a.echo)
670
+ continue;
671
+ // an intent action with no model entry does nothing (renderComponent can't send it)
672
+ if (names && !names.has(a.type) && a.cause != 'behavior')
673
+ continue;
674
+ replayed++;
675
+ if (a.data === undefined) {
676
+ body.push(`${I}t.simulateAction(${q(a.type)})`);
677
+ continue;
678
+ }
679
+ const l = tryLit(a.data, 'data', I);
680
+ if ('error' in l) {
681
+ body.push(`${I}t.simulateAction(${q(a.type)}) // data left out: it held ${l.error}`);
682
+ if (changes)
683
+ incomplete.push(`${a.type}'s data could not be written (${l.error})`);
684
+ continue;
685
+ }
686
+ if (l.stubbed)
687
+ body.push(`${I}// ${a.type}: DOM event/element data as a stub (type, key, target dataset/value/checked/id)`);
688
+ body.push(`${I}t.simulateAction(${q(a.type)}, ${l.code})`);
689
+ }
690
+ else if (a.cause == 'reply') {
691
+ const sink = a.replySink;
692
+ if (!sink || a.replyKind != 'fetch' || drivers[sink] !== undefined) {
693
+ body.push(`${I}// reply ${a.type}${sink ? ` from ${sink}` : ''}: not replayed (renderComponent fakes makeFetchDriver sources only)`);
694
+ if (changes)
695
+ incomplete.push(`the ${a.type} reply could not be replayed`);
696
+ continue;
697
+ }
698
+ if (isErrorReply(a)) {
699
+ const err = a.data.error, status = a.data.status ?? (err && err.status), errBody = a.data.body ?? (err && err.body);
700
+ if (typeof status == 'number') {
701
+ if (errBody === undefined)
702
+ answer(`${I}await t.fail(${q(sink)}, ${status}, ${q(a.type)})`);
703
+ else {
704
+ const l = tryLit(errBody, 'state', I);
705
+ if ('error' in l) {
706
+ answer(`${I}await t.fail(${q(sink)}, ${status}, ${q(a.type)}) // body left out: it held ${l.error}`);
707
+ if (changes)
708
+ incomplete.push(`the ${a.type} error body could not be written`);
709
+ }
710
+ else
711
+ answer(`${I}await t.fail(${q(sink)}, ${status}, { request: (r) => r.error === ${q(a.type)}, body: ${l.code} })`);
712
+ }
713
+ }
714
+ else if (a.data.issues) {
715
+ body.push(`${I}// reply ${a.type}: a validation failure, not replayed`);
716
+ if (changes)
717
+ incomplete.push(`the ${a.type} validation failure could not be replayed`);
718
+ continue;
719
+ }
720
+ else {
721
+ answer(`${I}await t.fail(${q(sink)}, new Error(${str(String(err && err.message || err))}), ${q(a.type)})`);
722
+ }
723
+ replayed++;
724
+ continue;
725
+ }
726
+ const l = tryLit(a.data, 'state', I);
727
+ if ('error' in l) {
728
+ body.push(`${I}// reply ${a.type} from ${sink}: its data held ${l.error}, not replayed`);
729
+ if (changes)
730
+ incomplete.push(`the ${a.type} reply's data could not be written`);
731
+ continue;
732
+ }
733
+ answer(`${I}await t.respond(${q(sink)}, ${l.code}, ${q(a.type)})`);
734
+ replayed++;
735
+ }
736
+ else if (a.type == 'RESOURCE' && a.data && (a.data.status == 'success' || a.data.status == 'error')) {
737
+ body.push(`${I}// RESOURCE ${a.data.name}: ${a.data.status}, not replayed (answer it with t.respond / t.fail)`);
738
+ if (changes)
739
+ incomplete.push(`resource '${a.data.name}' was answered`);
740
+ }
741
+ // 'next' and 'built-in': consequences, produced again by the replay
742
+ }
743
+ if (rec.foreign.length) {
744
+ const list = rec.foreign.slice(0, 5).map(f => `${f.component}#${f.instance} ${f.type}`).join(', ') + (rec.foreign.length > 5 ? ', ...' : '');
745
+ incomplete.push(`child components changed the state through their own actions (${list}): copy the test from that component instance instead`);
746
+ }
747
+ let assertion;
748
+ if (!incomplete.length) {
749
+ const l = tryLit(rec.finalState, 'state', I);
750
+ if ('error' in l)
751
+ incomplete.push(`the final state holds ${l.error}`);
752
+ else
753
+ assertion = [`${I}expect(t.state).toEqual(${l.code})`];
754
+ }
755
+ if (incomplete.length) {
756
+ warnings.push(...incomplete.map(s => 'no final-state assertion: ' + s));
757
+ assertion = [`${I}// No final-state assertion: ${incomplete.join('; ')}`];
758
+ }
759
+ const head = [];
760
+ if (options.environment)
761
+ head.push(`// @vitest-environment ${options.environment}`);
762
+ head.push(`// Copied from a sygnal/devtools session of ${rec.component} (${rec.actions.length} recorded actions, ${replayed} replayed)`);
763
+ for (const w of warnings)
764
+ head.push(`// NOTE: ${w}`);
765
+ head.push(`import { it, expect } from 'vitest'`, `import { renderComponent } from 'sygnal'`);
766
+ head.push(...(options.imports || []));
767
+ head.push(options.componentImport || `import ${name} from './${name}.js'`);
768
+ const testName = options.testName || `${rec.component} session (copied from sygnal/devtools)`;
769
+ const render = opts.length ? `renderComponent(${name}, {\n${opts.map(o => `${I}${o},`).join('\n')}\n })` : `renderComponent(${name})`;
770
+ const code = [
771
+ ...head,
772
+ '',
773
+ `it(${q(testName)}, async () => {`,
774
+ ` const t = ${render}`,
775
+ ' try {',
776
+ `${I}await t.ready()`,
777
+ ...body,
778
+ `${I}await t.settle()`,
779
+ ...assertion,
780
+ ' } finally {',
781
+ `${I}t.dispose()`,
782
+ ' }',
783
+ '})',
784
+ '',
785
+ ].join('\n');
786
+ return { code, complete: !incomplete.length, warnings, replayed };
787
+ }
788
+
789
+ const DEVTOOLS_SOURCE = '__SYGNAL_DEVTOOLS_PAGE__';
790
+ const EXTENSION_SOURCE = '__SYGNAL_DEVTOOLS_EXTENSION__';
791
+ const DEFAULT_MAX_HISTORY = 200;
792
+ class SygnalDevTools {
793
+ constructor() {
794
+ this._initialized = false;
795
+ /** PLAN-4 3-E: actions changed since the last ACTIONS post (flushed on a timeout) */
796
+ this._dirty = new Set();
797
+ this._flush = null;
798
+ this._copyOptions = {};
799
+ this._connected = false;
800
+ this._components = new Map();
801
+ this._stateHistory = [];
802
+ this._maxHistory = DEFAULT_MAX_HISTORY;
803
+ }
804
+ /** Diagnostics collected by the shared diagnostics core (see src/extra/diagnostics). */
805
+ getDiagnostics() {
806
+ return getDiagnostics();
807
+ }
808
+ get connected() {
809
+ return this._connected && typeof window !== 'undefined';
810
+ }
811
+ /** Publish the bridge on window and listen for the extension. Idempotent (D77: called once, by 'sygnal/devtools'). */
812
+ init() {
813
+ if (typeof window === 'undefined' || this._initialized)
814
+ return;
815
+ this._initialized = true;
816
+ window.__SYGNAL_DEVTOOLS__ = this;
817
+ // PLAN-4 3-E (GS-10): the action log (recorded from now on, connected or not)
818
+ recordActions();
819
+ // PLAN-4.6 R4: the apps read this bridge's hooks (devtoolsNext.ts)
820
+ addLayer((api) => devtoolsHooks(this, api));
821
+ onAction((a, kind) => {
822
+ if (!this.connected)
823
+ return;
824
+ if (kind == 'reset') {
825
+ this._dirty.clear();
826
+ this._post('ACTIONS_RESET', { actions: [] });
827
+ return;
828
+ }
829
+ this._dirty.add(a);
830
+ if (this._flush === null)
831
+ this._flush = setTimeout(() => this._flushActions(), 0);
832
+ });
833
+ window.addEventListener('message', (event) => {
834
+ if (event.source !== window)
835
+ return;
836
+ if (event.data?.source === EXTENSION_SOURCE) {
837
+ this._handleExtensionMessage(event.data);
838
+ }
839
+ });
840
+ }
841
+ _handleExtensionMessage(msg) {
842
+ switch (msg.type) {
843
+ case 'CONNECT':
844
+ this._connected = true;
845
+ if (msg.payload?.maxHistory)
846
+ this._maxHistory = msg.payload.maxHistory;
847
+ this._sendFullTree();
848
+ this._post('ACTIONS_RESET', { actions: getActions().slice(-500).map(a => this._serializeAction(a)) });
849
+ break;
850
+ case 'CLEAR_ACTIONS':
851
+ clearActions();
852
+ break;
853
+ case 'COPY_AS_TEST':
854
+ this._post('COPY_AS_TEST_RESULT', this._copyAsTest(msg.payload?.instance));
855
+ break;
856
+ case 'DISCONNECT':
857
+ this._connected = false;
858
+ break;
859
+ case 'SET_DEBUG':
860
+ this._setDebug(msg.payload);
861
+ break;
862
+ case 'TIME_TRAVEL':
863
+ this._timeTravel(msg.payload);
864
+ break;
865
+ case 'SNAPSHOT':
866
+ this._takeSnapshot();
867
+ break;
868
+ case 'RESTORE_SNAPSHOT':
869
+ this._restoreSnapshot(msg.payload);
870
+ break;
871
+ case 'GET_STATE':
872
+ this._sendComponentState(msg.payload.componentId);
873
+ break;
874
+ }
875
+ }
876
+ onComponentCreated(componentNumber, name, instance) {
877
+ const meta = {
878
+ id: componentNumber,
879
+ name: name,
880
+ isSubComponent: instance.isSubComponent,
881
+ hasModel: !!instance.model,
882
+ hasIntent: !!instance.intent,
883
+ hasContext: !!instance.context,
884
+ hasCalculated: !!instance.calculated,
885
+ components: [],
886
+ parentId: null,
887
+ children: [],
888
+ debug: instance._debug,
889
+ createdAt: Date.now(),
890
+ mviGraph: this._extractMviGraph(instance),
891
+ _instanceRef: new WeakRef(instance),
892
+ };
893
+ this._components.set(componentNumber, meta);
894
+ if (!this.connected)
895
+ return;
896
+ this._post('COMPONENT_CREATED', this._serializeMeta(meta));
897
+ }
898
+ onStateChanged(componentNumber, name, state) {
899
+ if (!this.connected)
900
+ return;
901
+ const entry = {
902
+ componentId: componentNumber,
903
+ componentName: name,
904
+ timestamp: Date.now(),
905
+ state: this._safeClone(state),
906
+ };
907
+ this._stateHistory.push(entry);
908
+ if (this._stateHistory.length > this._maxHistory) {
909
+ this._stateHistory.shift();
910
+ }
911
+ this._post('STATE_CHANGED', {
912
+ componentId: componentNumber,
913
+ componentName: name,
914
+ state: entry.state,
915
+ });
916
+ }
917
+ onActionDispatched(componentNumber, name, actionType, data) {
918
+ if (!this.connected)
919
+ return;
920
+ this._post('ACTION_DISPATCHED', {
921
+ componentId: componentNumber,
922
+ componentName: name,
923
+ actionType: actionType,
924
+ data: this._safeClone(data),
925
+ timestamp: Date.now(),
926
+ });
927
+ }
928
+ onSubComponentRegistered(parentNumber, childNumber) {
929
+ const parent = this._components.get(parentNumber);
930
+ const child = this._components.get(childNumber);
931
+ if (parent && child) {
932
+ child.parentId = parentNumber;
933
+ if (!parent.children.includes(childNumber)) {
934
+ parent.children.push(childNumber);
935
+ }
936
+ }
937
+ if (!this.connected)
938
+ return;
939
+ this._post('TREE_UPDATED', {
940
+ parentId: parentNumber,
941
+ childId: childNumber,
942
+ });
943
+ }
944
+ onContextChanged(componentNumber, name, context) {
945
+ if (!this.connected)
946
+ return;
947
+ this._post('CONTEXT_CHANGED', {
948
+ componentId: componentNumber,
949
+ componentName: name,
950
+ context: this._safeClone(context),
951
+ contextTrace: this._buildContextTrace(componentNumber, context),
952
+ });
953
+ }
954
+ onPropsChanged(componentNumber, name, props) {
955
+ if (!this.connected)
956
+ return;
957
+ this._post('PROPS_CHANGED', {
958
+ componentId: componentNumber,
959
+ componentName: name,
960
+ props: this._safeClone(props),
961
+ });
962
+ }
963
+ onBusEvent(event) {
964
+ if (!this.connected)
965
+ return;
966
+ this._post('BUS_EVENT', {
967
+ type: event.type,
968
+ data: this._safeClone(event.data),
969
+ componentId: event.__emitterId ?? null,
970
+ componentName: event.__emitterName ?? null,
971
+ timestamp: Date.now(),
972
+ });
973
+ }
974
+ onCommandSent(type, data, targetComponentId, targetComponentName) {
975
+ if (!this.connected)
976
+ return;
977
+ this._post('COMMAND_SENT', {
978
+ type,
979
+ data: this._safeClone(data),
980
+ targetComponentName: targetComponentName ?? null,
981
+ timestamp: Date.now(),
982
+ });
983
+ }
984
+ onReadyChanged(parentId, parentName, childKey, ready) {
985
+ if (!this.connected)
986
+ return;
987
+ this._post('READY_CHANGED', {
988
+ parentId,
989
+ parentName,
990
+ childKey,
991
+ ready,
992
+ timestamp: Date.now(),
993
+ });
994
+ }
995
+ onCollectionMounted(parentId, parentName, itemComponentName, stateField) {
996
+ const meta = this._components.get(parentId);
997
+ if (meta) {
998
+ meta.collection = { itemComponent: itemComponentName, stateField };
999
+ }
1000
+ if (!this.connected)
1001
+ return;
1002
+ this._post('COLLECTION_MOUNTED', {
1003
+ parentId,
1004
+ parentName,
1005
+ itemComponent: itemComponentName,
1006
+ stateField,
1007
+ });
1008
+ }
1009
+ onComponentDisposed(componentNumber, name) {
1010
+ const meta = this._components.get(componentNumber);
1011
+ if (meta) {
1012
+ meta.disposed = true;
1013
+ meta.disposedAt = Date.now();
1014
+ }
1015
+ if (!this.connected)
1016
+ return;
1017
+ this._post('COMPONENT_DISPOSED', {
1018
+ componentId: componentNumber,
1019
+ componentName: name,
1020
+ timestamp: Date.now(),
1021
+ });
1022
+ }
1023
+ onDebugLog(componentNumber, message) {
1024
+ if (!this.connected)
1025
+ return;
1026
+ this._post('DEBUG_LOG', {
1027
+ componentId: componentNumber,
1028
+ message: message,
1029
+ timestamp: Date.now(),
1030
+ });
1031
+ }
1032
+ /** PLAN-4 3-E: post the actions added or changed since the last flush */
1033
+ _flushActions() {
1034
+ this._flush = null;
1035
+ if (!this.connected || !this._dirty.size) {
1036
+ this._dirty.clear();
1037
+ return;
1038
+ }
1039
+ const list = [...this._dirty].sort((a, b) => a.seq - b.seq).map(a => this._serializeAction(a));
1040
+ this._dirty.clear();
1041
+ this._post('ACTIONS', { actions: list });
1042
+ }
1043
+ _serializeAction(a) {
1044
+ return {
1045
+ seq: a.seq, type: a.type, data: preview(a.data), component: a.component, instance: a.instance, parent: a.parent,
1046
+ sinks: [...a.sinks], cause: a.cause, at: a.at,
1047
+ ...('after' in a ? { before: this._safeClone(a.before), after: this._safeClone(a.after) } : {}),
1048
+ };
1049
+ }
1050
+ /** "Copy as test" for the panel: the session of `instance` (default: the root) */
1051
+ _copyAsTest(instance) {
1052
+ try {
1053
+ return sessionToTest(getSession(instance ?? undefined), this._copyOptions);
1054
+ }
1055
+ catch (e) {
1056
+ return { error: String(e?.message || e) };
1057
+ }
1058
+ }
1059
+ /** Defaults for "Copy as test" from the panel (componentImport, drivers, imports, ...) */
1060
+ configureCopyAsTest(options) {
1061
+ this._copyOptions = { ...this._copyOptions, ...options };
1062
+ }
1063
+ /** The recorded session of an instance (see sygnal/devtools getSession) */
1064
+ getSession(target) {
1065
+ return getSession(target);
1066
+ }
1067
+ _setDebug({ componentId, enabled }) {
1068
+ if (typeof componentId === 'undefined' || componentId === null) {
1069
+ if (typeof window !== 'undefined')
1070
+ window.SYGNAL_DEBUG = enabled ? 'true' : false;
1071
+ this._post('DEBUG_TOGGLED', { global: true, enabled });
1072
+ return;
1073
+ }
1074
+ const meta = this._components.get(componentId);
1075
+ if (meta && meta._instanceRef) {
1076
+ const instance = meta._instanceRef.deref();
1077
+ if (instance) {
1078
+ // (through the app's runtime API)
1079
+ instance.__api?.setDebug(componentId, enabled);
1080
+ meta.debug = enabled;
1081
+ this._post('DEBUG_TOGGLED', { componentId, enabled });
1082
+ }
1083
+ }
1084
+ }
1085
+ _timeTravel({ componentId, componentName, state }) {
1086
+ if (componentId == null || !state) {
1087
+ console.warn('[Sygnal DevTools] _timeTravel: missing componentId or state', { componentId, hasState: !!state });
1088
+ return;
1089
+ }
1090
+ if (typeof window === 'undefined')
1091
+ return;
1092
+ const newState = this._safeClone(state);
1093
+ // Per-component time travel through the app's runtime API
1094
+ const meta = this._components.get(componentId);
1095
+ if (meta) {
1096
+ const instance = meta._instanceRef?.deref();
1097
+ if (!instance) {
1098
+ console.warn(`[Sygnal DevTools] _timeTravel: WeakRef for component #${componentId} (${componentName}) has been GC'd`);
1099
+ }
1100
+ else if (instance.__api) {
1101
+ // the runtime's setState (a queued action, one patch)
1102
+ instance.__api.setState(componentId, () => ({ ...newState }));
1103
+ this._post('TIME_TRAVEL_APPLIED', { componentId, componentName, state: newState });
1104
+ return;
1105
+ }
1106
+ }
1107
+ else {
1108
+ console.warn(`[Sygnal DevTools] _timeTravel: no meta for componentId ${componentId}`);
1109
+ }
1110
+ // Fall back to the root's state
1111
+ const app = window.__SYGNAL_DEVTOOLS_APP__;
1112
+ if (app?.__runtime) {
1113
+ app.__runtime.setState('root', () => ({ ...newState }));
1114
+ this._post('TIME_TRAVEL_APPLIED', { componentId, componentName, state: newState });
1115
+ }
1116
+ else {
1117
+ console.warn(`[Sygnal DevTools] _timeTravel: no fallback root STATE sink available`);
1118
+ }
1119
+ }
1120
+ _takeSnapshot() {
1121
+ const entries = [];
1122
+ for (const [id, meta] of this._components) {
1123
+ if (meta.disposed)
1124
+ continue;
1125
+ const instance = meta._instanceRef?.deref();
1126
+ if (instance?.currentState != null) {
1127
+ entries.push({
1128
+ componentId: id,
1129
+ componentName: meta.name,
1130
+ state: this._safeClone(instance.currentState),
1131
+ });
1132
+ }
1133
+ }
1134
+ this._post('SNAPSHOT_TAKEN', {
1135
+ entries,
1136
+ timestamp: Date.now(),
1137
+ });
1138
+ }
1139
+ _restoreSnapshot(snapshot) {
1140
+ if (!snapshot?.entries)
1141
+ return;
1142
+ for (const entry of snapshot.entries) {
1143
+ this._timeTravel(entry);
1144
+ }
1145
+ }
1146
+ _sendComponentState(componentId) {
1147
+ const meta = this._components.get(componentId);
1148
+ if (meta && meta._instanceRef) {
1149
+ const instance = meta._instanceRef.deref();
1150
+ if (instance) {
1151
+ this._post('COMPONENT_STATE', {
1152
+ componentId,
1153
+ state: this._safeClone(instance.currentState),
1154
+ context: this._safeClone(instance.currentContext),
1155
+ contextTrace: this._buildContextTrace(componentId, instance.currentContext),
1156
+ props: this._safeClone(instance.currentProps),
1157
+ });
1158
+ }
1159
+ }
1160
+ }
1161
+ _buildContextTrace(componentId, context) {
1162
+ if (!context || typeof context !== 'object')
1163
+ return [];
1164
+ const trace = [];
1165
+ const fields = Object.keys(context);
1166
+ for (const field of fields) {
1167
+ // Walk up parent chain to find which component provides this field
1168
+ let currentId = componentId;
1169
+ let found = false;
1170
+ while (currentId != null) {
1171
+ const meta = this._components.get(currentId);
1172
+ if (!meta)
1173
+ break;
1174
+ if (meta.mviGraph?.contextProvides?.includes(field)) {
1175
+ trace.push({ field, providerId: meta.id, providerName: meta.name });
1176
+ found = true;
1177
+ break;
1178
+ }
1179
+ currentId = meta.parentId;
1180
+ }
1181
+ if (!found) {
1182
+ trace.push({ field, providerId: -1, providerName: 'unknown' });
1183
+ }
1184
+ }
1185
+ return trace;
1186
+ }
1187
+ _sendFullTree() {
1188
+ const tree = [];
1189
+ for (const [, meta] of this._components) {
1190
+ const instance = meta._instanceRef?.deref();
1191
+ tree.push({
1192
+ ...this._serializeMeta(meta),
1193
+ state: instance ? this._safeClone(instance.currentState) : null,
1194
+ context: instance ? this._safeClone(instance.currentContext) : null,
1195
+ });
1196
+ }
1197
+ this._post('FULL_TREE', {
1198
+ components: tree,
1199
+ history: this._stateHistory,
1200
+ });
1201
+ }
1202
+ _post(type, payload) {
1203
+ if (typeof window === 'undefined')
1204
+ return;
1205
+ window.postMessage({
1206
+ source: DEVTOOLS_SOURCE,
1207
+ type,
1208
+ payload,
1209
+ }, '*');
1210
+ }
1211
+ _safeClone(obj) {
1212
+ if (obj === undefined || obj === null)
1213
+ return obj;
1214
+ try {
1215
+ return JSON.parse(JSON.stringify(obj));
1216
+ }
1217
+ catch (e) {
1218
+ return '[unserializable]';
1219
+ }
1220
+ }
1221
+ _extractMviGraph(instance) {
1222
+ if (!instance.model)
1223
+ return null;
1224
+ try {
1225
+ const sources = instance.sourceNames ? [...instance.sourceNames] : [];
1226
+ const actions = [];
1227
+ const model = instance.model;
1228
+ for (const key of Object.keys(model)) {
1229
+ let actionName = key;
1230
+ let entry = model[key];
1231
+ // Handle shorthand 'ACTION | SINK'
1232
+ if (key.includes('|')) {
1233
+ const parts = key.split('|').map((s) => s.trim());
1234
+ if (parts.length === 2 && parts[0] && parts[1]) {
1235
+ actionName = parts[0];
1236
+ actions.push({ name: actionName, sinks: [parts[1]] });
1237
+ continue;
1238
+ }
1239
+ }
1240
+ // Function → targets STATE
1241
+ if (typeof entry === 'function') {
1242
+ actions.push({ name: actionName, sinks: [instance.stateSourceName || 'STATE'] });
1243
+ continue;
1244
+ }
1245
+ // Object → keys are sink names
1246
+ if (entry && typeof entry === 'object') {
1247
+ actions.push({ name: actionName, sinks: Object.keys(entry) });
1248
+ continue;
1249
+ }
1250
+ }
1251
+ const contextProvides = instance.context && typeof instance.context === 'object'
1252
+ ? Object.keys(instance.context) : [];
1253
+ return { sources, actions, contextProvides };
1254
+ }
1255
+ catch (e) {
1256
+ return null;
1257
+ }
1258
+ }
1259
+ _serializeMeta(meta) {
1260
+ const { _instanceRef, ...rest } = meta;
1261
+ return rest;
1262
+ }
1263
+ }
1264
+ let instance = null;
1265
+ /**
1266
+ * The bridge singleton of the 'sygnal/devtools' entry. Not part of the core
1267
+ * (D77): the core only calls the hooks of window.__SYGNAL_DEVTOOLS__ when it exists.
1268
+ */
1269
+ function getDevTools() {
1270
+ if (!instance)
1271
+ instance = new SygnalDevTools();
1272
+ return instance;
1273
+ }
1274
+ /**
1275
+ * Install the bridge (what run() did before 6.0): window.__SYGNAL_DEVTOOLS__ is
1276
+ * set and the extension can connect. A bridge already on window (e.g. a second
1277
+ * copy of this entry) is kept. Nothing happens outside a browser.
1278
+ */
1279
+ function installDevTools() {
1280
+ if (typeof window === 'undefined')
1281
+ return undefined;
1282
+ const existing = window.__SYGNAL_DEVTOOLS__;
1283
+ if (existing)
1284
+ return existing;
1285
+ const dt = getDevTools();
1286
+ dt.init();
1287
+ return dt;
1288
+ }
1289
+
1290
+ /**
1291
+ * PLAN-4 3-E (GS-10 stretch): a dev-only bridge to the Redux DevTools extension
1292
+ * (window.__REDUX_DEVTOOLS_EXTENSION__), in 'sygnal/devtools'. Sends each recorded action
1293
+ * (./devtoolsActions; built-in ones left out) with the root's state after it, as
1294
+ * `{ type: '<Component>/<ACTION>', payload, cause, instance }`. Jump to state / action in the
1295
+ * extension replaces the root's state (the Sygnal DevTools time-travel path). sygnal/vite calls it
1296
+ * in dev with sygnal({ devtools: { redux: true } }).
1297
+ */
1298
+ const clone = (s) => preview(s);
1299
+ let shared;
1300
+ /**
1301
+ * Connect the app's root (run()'s result, a component, an instance id; default: the newest root)
1302
+ * to the Redux DevTools extension. Returns a disconnect function; does nothing without the
1303
+ * extension.
1304
+ */
1305
+ function connectReduxDevtools(target, options) {
1306
+ // the default connection (sygnal/vite may inject the call in several files) is made once
1307
+ if (target === undefined && options === undefined) {
1308
+ if (!shared) {
1309
+ const off = connect(undefined, {});
1310
+ shared = () => { off(); shared = undefined; };
1311
+ }
1312
+ return shared;
1313
+ }
1314
+ return connect(target, options || {});
1315
+ }
1316
+ function connect(target, options) {
1317
+ const ext = typeof window != 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__;
1318
+ if (!ext || typeof ext.connect != 'function')
1319
+ return () => { };
1320
+ recordActions();
1321
+ const conn = ext.connect({ name: options.name || 'Sygnal', features: { jump: true, pause: true, export: true } });
1322
+ const keep = options.filter || ((a) => a.cause != 'built-in');
1323
+ let root, started = false, timer = null;
1324
+ const queue = [];
1325
+ const rootOf = () => {
1326
+ if (!root || root.disposed || !root.c)
1327
+ root = resolveInstance(target);
1328
+ return root;
1329
+ };
1330
+ const flush = () => {
1331
+ timer = null;
1332
+ const r = start();
1333
+ if (!r)
1334
+ return;
1335
+ for (const a of queue.splice(0)) {
1336
+ const state = a.instance === r.id && 'after' in a ? a.after : r.c.currentState;
1337
+ conn.send({ type: `${a.component}/${a.type}`, payload: preview(a.data), cause: a.cause, instance: a.instance }, clone(state));
1338
+ }
1339
+ };
1340
+ const start = () => {
1341
+ const r = rootOf();
1342
+ if (r && !started) {
1343
+ started = true;
1344
+ conn.init(clone(r.c.currentState));
1345
+ }
1346
+ return r;
1347
+ };
1348
+ start();
1349
+ const off = onAction((a, kind) => {
1350
+ if (kind == 'reset') {
1351
+ queue.length = 0;
1352
+ started = false;
1353
+ start();
1354
+ return;
1355
+ }
1356
+ if (kind != 'add')
1357
+ return;
1358
+ // connected before run() (sygnal/vite): the root's first action, its state still the initial one
1359
+ if (!started)
1360
+ start();
1361
+ if (!keep(a))
1362
+ return;
1363
+ queue.push(a);
1364
+ if (timer === null)
1365
+ timer = setTimeout(flush, 0);
1366
+ });
1367
+ const unsubscribe = conn.subscribe((msg) => {
1368
+ if (msg?.type !== 'DISPATCH' || !msg.state)
1369
+ return;
1370
+ const t = msg.payload?.type;
1371
+ if (t !== 'JUMP_TO_STATE' && t !== 'JUMP_TO_ACTION')
1372
+ return;
1373
+ const r = rootOf();
1374
+ const dt = window.__SYGNAL_DEVTOOLS__;
1375
+ if (!r || !dt || typeof dt._timeTravel != 'function')
1376
+ return;
1377
+ try {
1378
+ dt._timeTravel({ componentId: Number(r.id), componentName: r.name, state: JSON.parse(msg.state) });
1379
+ }
1380
+ catch (_) { /* a bad state from the extension */ }
1381
+ });
1382
+ return () => {
1383
+ off();
1384
+ if (timer !== null)
1385
+ clearTimeout(timer);
1386
+ if (typeof unsubscribe == 'function')
1387
+ unsubscribe();
1388
+ try {
1389
+ ext.disconnect?.();
1390
+ }
1391
+ catch (_) { /* ignore */ }
1392
+ };
1393
+ }
1394
+
1395
+ // 'sygnal/devtools' (D77): the DevTools bridge for the browser extension, a dev-only
1396
+ // entry. Importing it installs window.__SYGNAL_DEVTOOLS__ (run() did this before 6.0),
1397
+ // so import it before run(): components created earlier are not in the tree.
1398
+ // sygnal/vite injects it in dev (`vite`, not `vite build`); opt out with
1399
+ // sygnal({ devtools: false }). Production builds never contain it.
1400
+ // PLAN-4 3-E (GS-10): it also records the action log (in a browser on import; elsewhere
1401
+ // after recordActions()), and turns a session into a renderComponent test (copyAsTest).
1402
+ installDevTools();
1403
+ const isSession = (x) => !!x && typeof x == 'object' && x.version === 1 && Array.isArray(x.actions);
1404
+ /**
1405
+ * "Copy as test", with what was left out: the session of `target` (undefined: the newest root;
1406
+ * an instance id; a component; run()'s result; or a SessionRecording from getSession()).
1407
+ */
1408
+ function copyAsTestResult(target, options) {
1409
+ if (!isSession(target) && !isRecording())
1410
+ throw new Error('[Sygnal DevTools] copyAsTest: nothing was recorded: import sygnal/devtools before run() (in a browser), or call recordActions() first');
1411
+ return sessionToTest(isSession(target) ? target : getSession(target), options);
1412
+ }
1413
+ /** "Copy as test": a Vitest + renderComponent test replaying the recorded session (see copyAsTestResult). */
1414
+ function copyAsTest(target, options) {
1415
+ return copyAsTestResult(target, options).code;
1416
+ }
1417
+
1418
+ export { clearActions, connectReduxDevtools, copyAsTest, copyAsTestResult, getActions, getDevTools, getSession, installDevTools, isRecording, onAction, recordActions };
1419
+ //# sourceMappingURL=devtools.esm.js.map