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
@@ -1,15 +1,118 @@
1
1
  /**
2
2
  * Sygnal Vite Plugin
3
3
  *
4
- * Auto-configures JSX and wires up HMR with state preservation.
4
+ * Auto-configures JSX, wires up HMR with state preservation and, in dev,
5
+ * turns on Sygnal's diagnostics (runtime checks and the static checker).
6
+ * Nothing it adds in dev reaches a production build.
5
7
  *
6
8
  * Usage:
7
9
  * import sygnal from 'sygnal/vite'
8
10
  * export default defineConfig({ plugins: [sygnal()] })
9
11
  *
10
12
  * What it does:
11
- * 1. Configures OXC for automatic JSX transform with sygnal as the import source
12
- * 2. Detects files that call `run()` from sygnal and auto-injects HMR wiring
13
+ * 1. Configures the automatic JSX transform with sygnal as the import source:
14
+ * `oxc` (Vite 8), plus `esbuild` under Vite 7 and older
15
+ * 2. Detects files that call `run()` from sygnal and auto-injects HMR wiring.
16
+ * A module that calls `defineElement()` from 'sygnal/element' at the top level
17
+ * (and doesn't import run()) gets `if (import.meta.hot) import.meta.hot.accept()`
18
+ * appended: re-running it swaps the elements' component, keeping their state
19
+ * 3. Dev mode (`vite` / `vite dev` only; never `vite build`). Into every
20
+ * file that imports `run` from sygnal (including entries with manual HMR
21
+ * wiring or `disableHmr`) it inserts, on an existing line:
22
+ *
23
+ * if (globalThis.__SYGNAL_DEV__ === undefined) globalThis.__SYGNAL_DEV__ = true;
24
+ * import 'sygnal/diagnostics';import 'virtual:sygnal/dev';
25
+ *
26
+ * The flag turns on runtime diagnostics in 'warn' mode; 'sygnal/diagnostics'
27
+ * registers the runtime checks (SYG101-106, 201, 202, 301, 401, ...);
28
+ * 'virtual:sygnal/dev' logs static-check results in the browser console
29
+ * (see 5). With `diagnostics: { strict: true }` the snippet also sets
30
+ * `globalThis.__SYGNAL_STRICT__ = true` (same guard), which turns on the
31
+ * strict (SYG5xx) runtime checks of 'sygnal/diagnostics'. The snippet goes
32
+ * after any shebang line and directive prologue ('use strict',
33
+ * 'use client'); line numbers are unchanged and the transform returns a
34
+ * sourcemap. Opt out per app with `run(App, drivers, { diagnostics: 'off' })`
35
+ * or for the whole dev server with `sygnal({ diagnostics: 'off' })`.
36
+ * 4. `diagnostics` option. The runtime core only understands
37
+ * `__SYGNAL_DEV__ === true` ('warn'), so another mode or an ignore list
38
+ * is passed as run()'s own `diagnostics` option: imports of 'sygnal'
39
+ * from the entry files above resolve to a dev-only wrapper module
40
+ * ('\0sygnal-dev:runtime') that re-exports sygnal with a run() that adds
41
+ * `diagnostics: { mode, ignore }` unless the call sets `diagnostics`
42
+ * itself. With the default ('warn', no ignore list) there is no wrapper.
43
+ * 5. `check` option: runs sygnal-check (an optional dependency, loaded
44
+ * lazily from the project; `strict` defaults to `diagnostics.strict`)
45
+ * over `include` (default: the existing ones of src/, pages/ and
46
+ * renderer/, else the project root, with a notice) when the dev
47
+ * server starts and again after every source file change. Results go to
48
+ * the terminal in sygnal-check's format, and to the browser as a
49
+ * 'sygnal:check' HMR event that the dev client ('virtual:sygnal/dev')
50
+ * logs with console.warn (non-disruptive). The dev client asks for the
51
+ * current findings when the page loads ('sygnal:check:request'), and only
52
+ * that client is answered; after a source change the new findings go to
53
+ * every connected client, and only when they changed. Error-severity
54
+ * findings also open Vite's error overlay (`overlay: false`: never):
55
+ * SYG112, SYG124, SYG125, SYG128, SYG405 and the checker's other
56
+ * error-severity codes. The a11y lane SYG701-708 is a warning, also
57
+ * under `diagnostics.strict` (D144, amending D111); `check.a11y: 'error'`
58
+ * makes it an error (and opens the overlay). Without that opt-in a 7xx
59
+ * finding never opens the overlay, even from an older sygnal-check that
60
+ * reports it as an error under strict. Everything else is a warning or
61
+ * info and goes to the console. Warnings never use the overlay: while
62
+ * one is open Vite's client reloads the page on the next HMR update
63
+ * (`overlay: 'warn'` is treated as 'error', with a notice). The dev
64
+ * client closes a sygnal-check overlay before each update, and the
65
+ * server sends it again after the re-check, so it never causes a reload
66
+ * and doesn't go stale. A missing sygnal-check is skipped silently; a
67
+ * checker that throws only logs a warning.
68
+ * 6. Vitest (process.env.VITEST): appends the package's 'sygnal/diagnostics'
69
+ * ESM file to `test.setupFiles` (merged with a string or array value;
70
+ * not added twice), so renderComponent tests get the runtime checks.
71
+ * Its directory is added to the resolved `server.fs.allow` (G-050):
72
+ * in jsdom / happy-dom environments Vitest loads setup files through
73
+ * Vite's /@fs/ URLs, which a linked sygnal outside the workspace would
74
+ * otherwise fail. Opt out with `vitestSetup: false`. Under Vitest the transform adds
75
+ * nothing (no flags, HMR wiring, run() wrapper, dev client or checker):
76
+ * a __SYGNAL_DEV__ / __SYGNAL_STRICT__ flag set by one test file would
77
+ * leak into later test files of the same worker, and renderComponent()
78
+ * manages the diagnostics modes itself.
79
+ * 7. Vike and Astro (G-014): the app is started by sygnal's own client
80
+ * entry, which user code doesn't import. Client-side imports of
81
+ * 'sygnal/vike/onRenderClient' resolve to a dev wrapper that sets the
82
+ * flag and loads the checks first. That entry is excluded from
83
+ * pre-bundling in dev (B-020), so it shares the pages' core. Astro resolves its renderer entry on
84
+ * the server and the browser loads the file itself, so the same snippet
85
+ * is added to sygnal's 'astro/client' file by the transform. That file
86
+ * imports the public 'sygnal' entry (one shared core, B-019), so its
87
+ * 'sygnal' import gets the run() wrapper too: every `diagnostics` mode
88
+ * and ignore list works for islands. The sygnal/astro integration adds
89
+ * this plugin in `astro dev`.
90
+ * 8. `nativeGlobalThis` (G-099, default true): a `resolve.alias` from
91
+ * `globalthis` (xstream's `require('globalthis').getPolyfill()`) to the
92
+ * package's dist/shims/globalthis.cjs, which returns the native
93
+ * globalThis. Drops the polyfill chain (~4 KB gzip) from every bundle,
94
+ * in dev (including pre-bundling), build and Vitest. Dependencies left
95
+ * external (SSR, Vitest) still load the real package from Node. The
96
+ * sygnal/astro integration adds the same alias in `astro build`. Any
97
+ * dependency's `require('globalthis')` gets the stub; a `globalthis`
98
+ * entry in the user's own `resolve.alias` wins (R2-7).
99
+ * 9. `devtools` option (D77, default true): in dev the same files (run()
100
+ * importers, the Vike wrapper, the Astro client) also get
101
+ * `import 'sygnal/devtools';`, first, which installs the DevTools bridge
102
+ * (window.__SYGNAL_DEVTOOLS__) for the browser extension. Independent of
103
+ * `diagnostics` ('off' still injects it). Never in `vite build` or under
104
+ * Vitest: production builds carry no DevTools code. `devtools: false`
105
+ * injects nothing. PLAN-4 3-E: `devtools: { redux: true }` also calls
106
+ * connectReduxDevtools() (actions + state to the Redux DevTools extension).
107
+ *
108
+ * Why not Vite's `define`? Vite's dependency optimizer does not apply user
109
+ * `define` replacements to pre-bundled dependencies (only process.env.NODE_ENV),
110
+ * so a `define` would never reach sygnal's own code when sygnal is pre-bundled.
111
+ * Vite's client does copy `define` entries onto globalThis at runtime, but only
112
+ * when `/@vite/client` is loaded (not under SSR or Vitest, where it would also
113
+ * switch diagnostics on for every test run). Injecting the flag into the
114
+ * entry file that calls run() is deterministic and scoped to the dev app; it
115
+ * runs before run() is called, which is when the diagnostics mode is resolved.
13
116
  *
14
117
  * The HMR transform finds the pattern:
15
118
  * import { run } from 'sygnal'
@@ -21,7 +124,65 @@
21
124
  * import.meta.hot.accept('./App.jsx', __sygnal.hmr)
22
125
  * import.meta.hot.dispose(__sygnal.dispose)
23
126
  * }
127
+ *
128
+ * HMR wiring is only added for a recognized top-level (column 0) run()
129
+ * statement whose result reference is actually defined (see RUN_CALL_RE). It
130
+ * is skipped — the dev flag is still injected — for any other shape (e.g.
131
+ * `app = run(App)`, nested or indented calls), for test files (*.test.*,
132
+ * *.spec.*) and under Vitest (process.env.VITEST).
24
133
  */
134
+ // Node built-ins (the plugin runs in Node). The package has no @types/node.
135
+ // @ts-ignore
136
+ import fs from 'node:fs'
137
+ // @ts-ignore
138
+ import path from 'node:path'
139
+ // @ts-ignore
140
+ import { createRequire } from 'node:module'
141
+ // @ts-ignore
142
+ import { pathToFileURL } from 'node:url'
143
+ import { globalThisAlias } from './globalthis'
144
+
145
+ const nodeProcess: any = (globalThis as any).process
146
+
147
+ export type DiagnosticsMode = 'off' | 'collect' | 'warn' | 'error'
148
+
149
+ export interface DiagnosticsPluginOptions {
150
+ /** Runtime diagnostics mode in dev. @default 'warn' */
151
+ mode?: DiagnosticsMode
152
+ /** Strict (canonical-form, SYG5xx) runtime checks: sets globalThis.__SYGNAL_STRICT__. @default false */
153
+ strict?: boolean
154
+ /** Diagnostic codes to drop, e.g. ['SYG105'] */
155
+ ignore?: string[]
156
+ }
157
+
158
+ export interface CheckPluginOptions {
159
+ /** Also run sygnal-check's strict (SYG5xx) rules. @default `diagnostics.strict` */
160
+ strict?: boolean
161
+ /**
162
+ * Severity of the a11y lane (SYG701-708). 'warn' keeps it a warning, also
163
+ * under `strict` (D144); 'error' makes it an error, which opens the overlay.
164
+ * @default 'warn'
165
+ */
166
+ a11y?: 'warn' | 'error'
167
+ /**
168
+ * Files, directories or globs to check, relative to the Vite root.
169
+ * @default the existing ones of ['src', 'pages', 'renderer'], else the project root
170
+ */
171
+ include?: string[]
172
+ /** Codes to drop. @default the `diagnostics` ignore list */
173
+ ignore?: string[]
174
+ /**
175
+ * Error-severity findings also open Vite's error overlay ('error', the
176
+ * default); false: never. All findings are logged in the terminal and the
177
+ * browser console. Warnings never use the overlay (Vite reloads the page on
178
+ * the next update while one is open): 'warn' is accepted but treated as
179
+ * 'error'. Error-severity codes: SYG112, SYG124, SYG125, SYG128, SYG405
180
+ * and the checker's other errors; the a11y lane SYG701-708 only with
181
+ * `a11y: 'error'` (D144).
182
+ * @default 'error'
183
+ */
184
+ overlay?: 'error' | 'warn' | false
185
+ }
25
186
 
26
187
  export interface SygnalPluginOptions {
27
188
  /**
@@ -37,19 +198,98 @@ export interface SygnalPluginOptions {
37
198
  * @default false
38
199
  */
39
200
  disableHmr?: boolean
201
+
202
+ /**
203
+ * Runtime diagnostics in dev (`vite`, not `vite build`): a mode, or
204
+ * { mode, strict, ignore }. 'off' injects no checks (DevTools: `devtools`). A `diagnostics` option
205
+ * passed to run() itself still wins.
206
+ * @default 'warn'
207
+ */
208
+ diagnostics?: DiagnosticsMode | DiagnosticsPluginOptions
209
+
210
+ /**
211
+ * Run sygnal-check in dev on startup and on file changes. Skipped silently
212
+ * when sygnal-check isn't installed.
213
+ * @default true
214
+ */
215
+ check?: boolean | CheckPluginOptions
216
+
217
+ /**
218
+ * Under Vitest, add 'sygnal/diagnostics' to `test.setupFiles` so tests get
219
+ * the runtime checks.
220
+ * @default true
221
+ */
222
+ vitestSetup?: boolean
223
+
224
+ /**
225
+ * Resolve xstream's `globalthis` dependency to a tiny stub that returns the
226
+ * native `globalThis`, instead of the npm polyfill and its dependency chain
227
+ * (~4 KB gzip), in dev, build and Vitest (a `resolve.alias`; every
228
+ * dependency's `globalthis` import gets the stub). Not added when your own
229
+ * `resolve.alias` already maps `globalthis`. false: keep the original package.
230
+ * @default true
231
+ */
232
+ nativeGlobalThis?: boolean
233
+
234
+ /**
235
+ * In dev (`vite`, never `vite build` or Vitest), import 'sygnal/devtools' in the
236
+ * same files as the diagnostics snippet, which installs the DevTools bridge for
237
+ * the browser extension. Production builds never contain it. false: not injected.
238
+ * PLAN-4 3-E: `{ redux: true }` also sends the actions and the root's state to the
239
+ * Redux DevTools extension (connectReduxDevtools() from 'sygnal/devtools').
240
+ * @default true
241
+ */
242
+ devtools?: boolean | { redux?: boolean }
40
243
  }
41
244
 
245
+ // Virtual modules (dev server only)
246
+ const DEV_CLIENT = 'virtual:sygnal/dev'
247
+ const DEV_CLIENT_ID = '\0' + DEV_CLIENT
248
+ const RUNTIME_ID = '\0sygnal-dev:runtime'
249
+ const VIKE_CLIENT = 'sygnal/vike/onRenderClient'
250
+ const VIKE_CLIENT_ID = '\0sygnal-dev:vike-client'
251
+ const CHECK_EVENT = 'sygnal:check'
252
+ // Sent by the dev client when it loads: the server answers that client only
253
+ const CHECK_REQUEST = 'sygnal:check:request'
254
+ const OVERLAY_PLUGIN = 'sygnal-check'
255
+ // Register the runtime checks with the core that loaded last (see the Vike wrapper)
256
+ const REINSTALL_IMPORT = `import { installChecks as __sygnalInstallChecks } from 'sygnal/diagnostics';`
257
+ const REINSTALL_CALL = 'try { __sygnalInstallChecks() } catch (e) { console.warn(e) }\n'
258
+
42
259
  export default function sygnal(options: SygnalPluginOptions = {}) {
43
- const { disableJsx = false, disableHmr = false } = options
260
+ const { disableJsx = false, disableHmr = false, vitestSetup = true, nativeGlobalThis = true } = options
261
+ const diagnostics = normalizeDiagnostics(options.diagnostics)
262
+ const devOn = diagnostics.mode !== 'off'
263
+ // A mode other than the flag's 'warn', or an ignore list, needs run()'s option
264
+ const wrapRun = devOn && (diagnostics.mode !== 'warn' || diagnostics.ignore.length > 0)
44
265
  let isServe = false
266
+ let isVitest = false
267
+ let root: string = nodeProcess.cwd()
268
+ // Files whose `import ... from 'sygnal'` resolves to the run() wrapper
269
+ const runtimeImporters = new Set<string>()
270
+ // Statements run before the app starts: dev flag (+ strict flag)
271
+ const flags = DEV_FLAG + (diagnostics.strict ? STRICT_FLAG : '')
272
+ const devImports = `import 'sygnal/diagnostics';import '${DEV_CLIENT}';`
273
+ // D77: the DevTools bridge, dev only. First, so the diagnostics entry finds it.
274
+ const devtoolsOn = options.devtools !== false
275
+ const redux = typeof options.devtools == 'object' && !!options.devtools?.redux
276
+ const devtoolsImport = !devtoolsOn ? ''
277
+ : redux ? `import { connectReduxDevtools as __sygnalReduxDevtools } from 'sygnal/devtools';__sygnalReduxDevtools();`
278
+ : `import 'sygnal/devtools';`
279
+ // What a dev entry gets: DevTools, then (unless diagnostics are 'off') flags + checks
280
+ const devSnippet = devtoolsImport + (devOn ? flags + devImports : '')
281
+ const devInject = devOn || devtoolsOn
282
+ let astroClient: Set<string> | undefined
45
283
 
46
284
  return {
47
285
  name: 'vite-plugin-sygnal',
48
286
 
49
- config(_config: any, env: { command: string }) {
287
+ config(this: any, config: any, env: { command: string }) {
50
288
  isServe = env.command === 'serve'
289
+ isVitest = !!nodeProcess?.env?.VITEST
290
+ root = config?.root ? path.resolve(config.root) : nodeProcess.cwd()
51
291
 
52
- const config: any = {
292
+ const result: any = {
53
293
  // Ensure sygnal is bundled for SSR rather than externalized.
54
294
  // Without this, Node fails to resolve sygnal's exports from
55
295
  // Vike's server chunks in production builds.
@@ -58,95 +298,719 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
58
298
  },
59
299
  }
60
300
 
301
+ // G-099: xstream's `require('globalthis')` gets the native-globalThis
302
+ // stub. An alias (not resolveId) so it also applies to pre-bundling.
303
+ // R2-7: not when the user's config already aliases `globalthis`.
304
+ const alias = nativeGlobalThis ? globalThisAlias(config?.resolve?.alias) : []
305
+ if (alias.length) result.resolve = { alias }
306
+
61
307
  if (!disableJsx) {
62
- config.oxc = {
308
+ result.oxc = {
63
309
  jsx: {
64
310
  runtime: 'automatic' as const,
65
311
  importSource: 'sygnal',
66
312
  },
67
313
  }
314
+ // Vite 7 and older (Astro 6, Vitest on Vite 7) compile JSX with
315
+ // esbuild and ignore `oxc` (B-027); Vite 8 warns about `esbuild`
316
+ // from a plugin, so it is only set below Vite 8.
317
+ if (viteMajor(this, root) < 8) {
318
+ result.esbuild = { jsx: 'automatic', jsxImportSource: 'sygnal' }
319
+ }
320
+ // Vite 8's dependency scanner doesn't use the `oxc` options: without
321
+ // this it compiles JSX for React, fails to resolve
322
+ // react/jsx-dev-runtime and skips pre-bundling (a reload on first load).
323
+ if (isServe && !isVitest) {
324
+ result.optimizeDeps = { rolldownOptions: { transform: { jsx: { runtime: 'automatic', importSource: 'sygnal' } } } }
325
+ }
326
+ }
327
+
328
+ if (isVitest) {
329
+ if (vitestSetup) {
330
+ const setup = diagnosticsSetupFile(root)
331
+ const existing = ([] as string[]).concat(config?.test?.setupFiles || [])
332
+ const loaded = existing.some(f => typeof f === 'string' &&
333
+ (f === 'sygnal/diagnostics' || (!!setup && path.resolve(root, f) === setup)))
334
+ // Vite merges arrays (and a string with an array) by concatenation
335
+ if (setup && !loaded) result.test = { setupFiles: [setup] }
336
+ }
337
+ } else if (isServe && devInject && sygnalInNodeModules(root)) {
338
+ // The dependency scan reads the original sources, which don't import
339
+ // the checks (or DevTools) entry: pre-bundle it with sygnal so the first
340
+ // page load doesn't trigger a re-optimization. Only for an installed
341
+ // sygnal: a linked one is served from source, and pre-bundling the
342
+ // checks alone would give them their own copy of the core.
343
+ const include = [...(devtoolsOn ? ['sygnal/devtools'] : []), ...(devOn ? ['sygnal/diagnostics'] : [])]
344
+ result.optimizeDeps = { ...result.optimizeDeps, include }
345
+ }
346
+
347
+ // B-020: Vike adds its client entry 'sygnal/vike/onRenderClient' to
348
+ // optimizeDeps.include. With a linked sygnal, the pages' 'sygnal' is
349
+ // served from source while that pre-bundle inlines a second copy of the
350
+ // core. Unbundled, the entry's own `import 'sygnal'` resolves like the
351
+ // pages' imports (source when linked, the shared pre-bundle when
352
+ // installed): one core. Vike honours the exclude list.
353
+ // G-098: Vite doesn't discover the imports of an excluded dependency, so for an
354
+ // installed sygnal 'sygnal' is pre-bundled up front (pages that reach it only through
355
+ // the JSX runtime would otherwise get it, and its CommonJS xstream, unbundled).
356
+ if (isServe && !isVitest) {
357
+ const include = result.optimizeDeps?.include || []
358
+ result.optimizeDeps = {
359
+ ...result.optimizeDeps,
360
+ exclude: [VIKE_CLIENT],
361
+ ...(sygnalInNodeModules(root) ? { include: [...include, 'sygnal'] } : {}),
362
+ }
363
+ }
364
+
365
+ return result
366
+ },
367
+
368
+ configResolved(config: any) {
369
+ if (config?.root) root = config.root
370
+ // G-050: Vitest loads setup files by URL (/@fs/...) in browser-like
371
+ // environments (jsdom, happy-dom), and Vite refuses files outside
372
+ // server.fs.allow, which is the project's workspace by default. A linked
373
+ // (or hoisted) sygnal lives outside it, so allow its setup file's
374
+ // directory. The resolved list already holds Vite's defaults.
375
+ if (isVitest) {
376
+ const setup = diagnosticsSetupFile(root)
377
+ const allow = config?.server?.fs?.allow
378
+ if (setup && Array.isArray(allow)) {
379
+ const dir = path.dirname(setup)
380
+ const inside = (base: string) => {
381
+ const rel = path.relative(path.resolve(base), dir)
382
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))
383
+ }
384
+ if (!allow.some(inside)) allow.push(dir)
385
+ }
68
386
  }
387
+ },
69
388
 
70
- return config
389
+ resolveId: {
390
+ order: 'pre' as const,
391
+ async handler(this: any, source: string, importer: string | undefined, opts: any) {
392
+ if (!isServe || isVitest) return null
393
+ if (source === DEV_CLIENT) return DEV_CLIENT_ID
394
+ if (source === 'sygnal' && importer && wrapRun && runtimeImporters.has(cleanId(importer))) {
395
+ return RUNTIME_ID
396
+ }
397
+ if (source === VIKE_CLIENT && devInject && !opts?.ssr) {
398
+ if (importer !== VIKE_CLIENT_ID) return VIKE_CLIENT_ID
399
+ // The wrapper's own import of the real entry: resolve it normally.
400
+ // The real Vike client's run() then gets the run() wrapper too, when
401
+ // it is served unbundled (a pre-bundled copy stays in 'warn' mode).
402
+ const real = await this.resolve(source, importer, { ...opts, skipSelf: true })
403
+ if (real && wrapRun) runtimeImporters.add(cleanId(real.id))
404
+ return real
405
+ }
406
+ return null
407
+ },
71
408
  },
72
409
 
73
- transform(code: string, id: string) {
74
- if (disableHmr) return null
410
+ load(id: string) {
411
+ if (id === DEV_CLIENT_ID) return devClientModule()
412
+ if (id === RUNTIME_ID) return runtimeModule(diagnostics)
413
+ // The checks register again once the real entry has loaded: a
414
+ // pre-bundled copy of it can carry its own copy of the Sygnal core
415
+ // (e.g. with a linked sygnal), which then holds the diagnostics bridge.
416
+ if (id === VIKE_CLIENT_ID) {
417
+ if (!devOn) return `${devtoolsImport}\nexport * from '${VIKE_CLIENT}';\n`
418
+ return `${devtoolsImport}${devImports}${REINSTALL_IMPORT}\nexport * from '${VIKE_CLIENT}';\n${flags}\n${REINSTALL_CALL}`
419
+ }
420
+ return null
421
+ },
422
+
423
+ configureServer(server: any) {
424
+ if (isVitest || options.check === false) return
425
+ const checkOptions = typeof options.check === 'object' && options.check ? options.check : {}
426
+ // Deferred: the dev server doesn't wait for the first check
427
+ const checkDefaults = { strict: diagnostics.strict, ignore: diagnostics.ignore }
428
+ setTimeout(() => {
429
+ startChecker(server, root, checkOptions, checkDefaults, options.check !== undefined)
430
+ .catch(err => server?.config?.logger?.warn?.(`[sygnal] sygnal-check could not start: ${err?.message || err}`))
431
+ }, 0)
432
+ },
433
+
434
+ transform(this: any, code: string, id: string, opts?: { ssr?: boolean }) {
435
+ // SYG666 (PLAN-5 W-2): an adapter entry imported without its optional peers; every mode
436
+ const adapters = adapterImports(this, code, id)
437
+ if (adapters) return checkPeers(this, adapters, id).then(() => devTransform.call(this, code, id, opts))
438
+ return devTransform.call(this, code, id, opts)
439
+ },
440
+ }
441
+
442
+ function devTransform(this: any, code: string, id: string, opts?: { ssr?: boolean }) {
75
443
  if (!isServe) return null
76
444
 
445
+ // sygnal/astro/client (Astro islands): flags + checks first. It imports
446
+ // the public 'sygnal' entry (the app's core, B-019), which resolves to
447
+ // the run() wrapper when the diagnostics option needs it.
448
+ if (devInject && !isVitest && !opts?.ssr) {
449
+ astroClient = astroClient || astroClientFiles(root)
450
+ if (astroClient.has(cleanId(id))) {
451
+ if (wrapRun) runtimeImporters.add(cleanId(id))
452
+ return withSourcemap(code, [[0, devSnippet]], '', id)
453
+ }
454
+ }
455
+
77
456
  // Only transform JS/TS/JSX/TSX files
78
457
  if (!/\.[jt]sx?$/.test(id)) return null
79
458
  // Skip node_modules
80
459
  if (id.includes('node_modules')) return null
81
460
  // Must import run from sygnal
82
461
  if (!code.includes('sygnal')) return null
83
- // Skip if HMR is already manually wired
84
- if (code.includes('import.meta.hot')) return null
462
+
463
+ // Match against the code with comments blanked out (same offsets), so
464
+ // e.g. `// run(App)` or a commented-out import is never matched.
465
+ const noComments = blankOut(code, false)
85
466
 
86
467
  // Find: import { run, ... } from 'sygnal'
87
468
  const runImportRe = /import\s+\{[^}]*\brun\b[^}]*\}\s+from\s+['"]sygnal['"]/
88
- if (!runImportRe.test(code)) return null
469
+ if (!runImportRe.test(noComments)) {
470
+ return disableHmr || isVitest ? null : elementHmr(code, noComments, id)
471
+ }
89
472
 
90
- // Find: run(ComponentName — capture the component identifier
91
- const runCallMatch = code.match(
92
- /(?:(?:const|let|var)\s+(?:\{[^}]*\}|\w+)\s*=\s*)?run\s*\(\s*([A-Z]\w*)/
93
- )
94
- if (!runCallMatch) return null
473
+ // Dev snippet: injected into every file that imports run() — also when
474
+ // HMR wiring below is skipped. Inserted after any shebang line and
475
+ // directive prologue, without adding a line. Nothing under Vitest: a
476
+ // global flag set by one test file would leak dev/strict mode into the
477
+ // later test files of the same worker; there the checks come from
478
+ // test.setupFiles and renderComponent() manages the modes.
479
+ const inserts: Array<[number, string]> = []
480
+ if (devInject && !isVitest) {
481
+ inserts.push(flagInsertion(code, devSnippet))
482
+ if (wrapRun) runtimeImporters.add(cleanId(id))
483
+ }
484
+ const done = (tail = '') => (inserts.length || tail ? withSourcemap(code, inserts, tail, id) : null)
485
+
486
+ // Skip HMR wiring if disabled, in tests, or already manually wired
487
+ if (disableHmr || isVitest || /\.(test|spec)\.[jt]sx?$/.test(id)) return done()
488
+ if (noComments.includes('import.meta.hot')) return done()
95
489
 
96
- const componentName = runCallMatch[1]
490
+ // Strings and template literals blanked out too: a statement-level match only
491
+ const runCall = RUN_CALL_RE.exec(blankOut(code, true))
492
+ if (!runCall) return done()
493
+ const [, binding, componentName] = runCall
97
494
 
98
495
  // Find the import path for this component
99
496
  // Handles: import App from './App.jsx'
100
497
  // import App from "./App"
101
498
  const componentImportRe = new RegExp(
102
- `import\\s+${componentName}\\s+from\\s+['"]([^'"]+)['"]`
499
+ `import\\s+${componentName.replace(/\$/g, '\\$')}\\s+from\\s+['"]([^'"]+)['"]`
103
500
  )
104
- const componentImportMatch = code.match(componentImportRe)
105
- if (!componentImportMatch) return null
106
-
501
+ const componentImportMatch = noComments.match(componentImportRe)
502
+ if (!componentImportMatch) return done()
107
503
  const componentPath = componentImportMatch[1]
108
504
 
109
- // Determine how to access the run() result
110
- let transformed = code
111
-
112
- // Pattern 1: const { hmr, dispose, ... } = run(App, ...)
113
- const destructureMatch = code.match(
114
- /const\s+\{([^}]*)\}\s*=\s*run\s*\(/
115
- )
116
- if (destructureMatch) {
117
- const bindings = destructureMatch[1]
118
- const hasHmr = /\bhmr\b/.test(bindings)
119
- const hasDispose = /\bdispose\b/.test(bindings)
120
-
121
- if (hasHmr && hasDispose) {
122
- // Already has both bindings, just add hot.accept/dispose
123
- transformed += hmrBlock(componentPath, 'hmr', 'dispose')
124
- return { code: transformed, map: null }
125
- }
505
+ // Pattern 3: bare run(App, ...) — no result captured → const __sygnal = run(
506
+ if (!binding) {
507
+ inserts.push([runCall.index, 'const __sygnal = '])
508
+ return done(hmrBlock(componentPath, '__sygnal.hmr', '__sygnal.dispose'))
126
509
  }
127
510
 
128
511
  // Pattern 2: const app = run(App, ...)
129
- const varMatch = code.match(
130
- /(?:const|let|var)\s+(\w+)\s*=\s*run\s*\(/
131
- )
132
- if (varMatch) {
133
- const varName = varMatch[1]
134
- transformed += hmrBlock(componentPath, `${varName}.hmr`, `${varName}.dispose`)
135
- return { code: transformed, map: null }
512
+ if (binding[0] !== '{') {
513
+ return done(hmrBlock(componentPath, `${binding}.hmr`, `${binding}.dispose`))
136
514
  }
137
515
 
138
- // Pattern 3: bare run(App, ...) — no result captured
139
- // Replace the first run( with __sygnal = run(
140
- transformed = transformed.replace(
141
- /\brun\s*\(/,
142
- 'const __sygnal = run('
143
- )
144
- transformed += hmrBlock(componentPath, '__sygnal.hmr', '__sygnal.dispose')
145
- return { code: transformed, map: null }
146
- },
516
+ // Pattern 1: const { hmr, dispose, ... } = run(App, ...) — only when both
517
+ // are bound under their own names (a rename would leave them undefined)
518
+ const names = binding.slice(1, -1).split(',').map(s => s.trim())
519
+ if (names.includes('hmr') && names.includes('dispose')) {
520
+ return done(hmrBlock(componentPath, 'hmr', 'dispose'))
521
+ }
522
+ return done()
523
+ }
524
+ }
525
+
526
+ // PLAN-5 W-2 (D209): the adapter entries and the optional peer dependencies each one imports
527
+ // (the Zag packages pinned together: they depend on each other's exact versions)
528
+ const ZAG = '~1.45.0'
529
+ const ADAPTER_PEERS: Record<string, string[]> = {
530
+ react: ['react', 'react-dom'],
531
+ zag: ['@zag-js/vanilla'],
532
+ // D211: one subpath per Zag part, each needing only its own machine
533
+ 'ui/menu': ['@zag-js/vanilla', '@zag-js/menu'],
534
+ 'ui/select': ['@zag-js/vanilla', '@zag-js/select'],
535
+ 'ui/combobox': ['@zag-js/vanilla', '@zag-js/combobox'],
536
+ }
537
+ // G-440: a dynamic import's specifier may be a template literal without substitutions
538
+ const ADAPTER_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"`])sygnal\/(react|zag|ui\/(?:menu|select|combobox))\1/g
539
+ const ADAPTER_ID = /^sygnal\/(react|zag|ui\/(?:menu|select|combobox))$/
540
+
541
+ /**
542
+ * The adapter entries an app module imports (statically, dynamically or in a re-export), or null.
543
+ * G-415: real imports only. The module's AST from the plugin context's parse() (Vite and Rollup
544
+ * provide it; the code is plain JS by then); when there is none, or the code doesn't parse, a
545
+ * scan of the code with comments blanked whose `from` / `import` keyword isn't inside a string
546
+ * or template literal. A literal that mentions an entry (a docs snippet) is never an import.
547
+ */
548
+ function adapterImports(ctx: any, code: string, id: string): string[] | null {
549
+ if (!code.includes('sygnal/') || id.includes('node_modules') || !/\.[cm]?[jt]sx?$/.test(cleanId(id))) return null
550
+ const noComments = blankOut(code, false)
551
+ if (!ADAPTER_RE.test(noComments)) return null
552
+ ADAPTER_RE.lastIndex = 0
553
+ const found = new Set<string>()
554
+ let ast: any = null
555
+ try { ast = typeof ctx?.parse == 'function' ? ctx.parse(code) : null } catch (_) {}
556
+ if (ast) {
557
+ const walk = (n: any): void => {
558
+ if (!n || typeof n != 'object') return
559
+ if (Array.isArray(n)) return n.forEach(walk)
560
+ if (/^(Import|Export(Named|All))Declaration$|^ImportExpression$/.test(n.type)) {
561
+ // G-440: import(`sygnal/zag`): a template literal without substitutions is a known specifier
562
+ const src = n.source, q = src?.type == 'TemplateLiteral' && !src.expressions?.length ? src.quasis?.[0]?.value?.cooked : src?.value
563
+ const m = ADAPTER_ID.exec(q ?? '')
564
+ if (m) found.add(m[1])
565
+ }
566
+ for (const k in n) if (k != 'type' && typeof n[k] == 'object') walk(n[k])
567
+ }
568
+ walk(ast)
569
+ } else {
570
+ // strings and templates blanked too: a keyword there is text, not an import
571
+ const noStrings = blankOut(code, true)
572
+ for (const m of noComments.matchAll(ADAPTER_RE)) {
573
+ if (noStrings.startsWith(m[0].slice(0, m[0][0] == 'f' ? 4 : 6), m.index!)) found.add(m[2])
574
+ }
575
+ }
576
+ return found.size ? [...found] : null
577
+ }
578
+
579
+ /** SYG666: resolves each entry's peers from the importing module; reports the missing ones */
580
+ async function checkPeers(ctx: any, entries: string[], id: string): Promise<void> {
581
+ for (const e of entries) {
582
+ const missing: string[] = []
583
+ for (const peer of ADAPTER_PEERS[e]) {
584
+ let r: any = null
585
+ try { r = await ctx.resolve(peer, id, { skipSelf: true }) } catch (_) {}
586
+ if (!r) missing.push(peer)
587
+ }
588
+ if (!missing.length) continue
589
+ const install = missing.map(m => (m.startsWith('@zag-js/') ? `${m}@${ZAG}` : m)).join(' ')
590
+ ctx.error(`[Sygnal SYG666] 'sygnal/${e}' needs ${missing.join(', ')}, which ${missing.length > 1 ? "aren't" : "isn't"} installed: npm install ${install}.` +
591
+ (e == 'react' ? " (For Preact: alias react, react-dom and react-dom/client to preact/compat.)" : '') +
592
+ ' https://sygnal.js.org/reference/errors#syg666')
147
593
  }
148
594
  }
149
595
 
596
+ /**
597
+ * Major version of the running Vite: from the plugin context (this.meta,
598
+ * Vite 7+), else from the vite package the project resolves. Infinity when
599
+ * unknown (treated as Vite 8+: no `esbuild` option).
600
+ */
601
+ function viteMajor(ctx: any, root: string): number {
602
+ const fromMeta = ctx?.meta?.viteVersion
603
+ if (typeof fromMeta === 'string') return parseInt(fromMeta, 10)
604
+ if (ctx?.meta?.rolldownVersion) return 8
605
+ try {
606
+ const req = createRequire(path.join(root, 'package.json'))
607
+ const v = JSON.parse(fs.readFileSync(req.resolve('vite/package.json'), 'utf8')).version
608
+ const major = parseInt(v, 10)
609
+ if (major > 0) return major
610
+ } catch (_) {}
611
+ return Infinity
612
+ }
613
+
614
+ interface NormalizedDiagnostics { mode: DiagnosticsMode, strict: boolean, ignore: string[] }
615
+
616
+ const MODES: DiagnosticsMode[] = ['off', 'collect', 'warn', 'error']
617
+
618
+ function normalizeDiagnostics(value: SygnalPluginOptions['diagnostics']): NormalizedDiagnostics {
619
+ const o: DiagnosticsPluginOptions = typeof value === 'string' ? { mode: value } : value || {}
620
+ const mode = o.mode === undefined ? 'warn' : o.mode
621
+ if (!MODES.includes(mode)) {
622
+ throw new Error(`[vite-plugin-sygnal] diagnostics mode must be one of ${MODES.map(m => `'${m}'`).join(', ')}; got ${JSON.stringify(mode)}`)
623
+ }
624
+ return { mode, strict: !!o.strict, ignore: Array.isArray(o.ignore) ? o.ignore.map(String) : [] }
625
+ }
626
+
627
+ /** Module id without a query or hash. */
628
+ function cleanId(id: string): string {
629
+ return id.replace(/[?#].*$/, '')
630
+ }
631
+
632
+ /** 'virtual:sygnal/dev': logs sygnal-check results in the browser console. */
633
+ function devClientModule(): string {
634
+ // Vite's client reloads the page on the first HMR update while an error
635
+ // overlay is open. A sygnal-check overlay is closed before each update (the
636
+ // server sends it again after the re-check), so it never causes a reload.
637
+ return `if (import.meta.hot) {
638
+ let shown = 0;
639
+ let ours = false;
640
+ const closeOurs = () => {
641
+ if (!ours || typeof document === 'undefined') return;
642
+ document.querySelectorAll('vite-error-overlay').forEach((n) => n.close ? n.close() : n.remove());
643
+ ours = false;
644
+ };
645
+ import.meta.hot.on('vite:error', (payload) => {
646
+ ours = !!(payload && payload.err && payload.err.plugin === '${OVERLAY_PLUGIN}');
647
+ });
648
+ import.meta.hot.on('vite:beforeUpdate', closeOurs);
649
+ import.meta.hot.on('${CHECK_EVENT}', (payload) => {
650
+ const list = (payload && payload.diagnostics) || [];
651
+ if (!list.some((d) => d.severity === 'error')) closeOurs();
652
+ if (!list.length) {
653
+ if (shown) console.info('[sygnal-check] all findings resolved');
654
+ shown = 0;
655
+ return;
656
+ }
657
+ shown = list.length;
658
+ for (const d of list) console[d.severity === 'error' ? 'error' : 'warn']('[sygnal-check] ' + d.text);
659
+ console.info('[sygnal-check] ' + payload.summary);
660
+ });
661
+ import.meta.hot.send('${CHECK_REQUEST}');
662
+ }
663
+ `
664
+ }
665
+
666
+ /** '\0sygnal-dev:runtime': sygnal with a run() that adds the plugin's diagnostics option. */
667
+ function runtimeModule(d: NormalizedDiagnostics): string {
668
+ const option = JSON.stringify({ mode: d.mode, ignore: d.ignore })
669
+ return `import { run as __run } from 'sygnal';
670
+ export * from 'sygnal';
671
+ export function run(app, drivers, options) {
672
+ if (options && options.diagnostics !== undefined) return __run(app, drivers, options);
673
+ return __run(app, drivers, Object.assign({}, options, { diagnostics: ${option} }));
674
+ }
675
+ `
676
+ }
677
+
678
+ /** Directory of the 'sygnal' package the project resolves, or undefined. */
679
+ function sygnalPackageDir(root: string): string | undefined {
680
+ try {
681
+ const req = createRequire(path.join(root, 'package.json'))
682
+ let dir = path.dirname(req.resolve('sygnal'))
683
+ while (dir !== path.dirname(dir)) {
684
+ const pkgFile = path.join(dir, 'package.json')
685
+ if (fs.existsSync(pkgFile) && JSON.parse(fs.readFileSync(pkgFile, 'utf8')).name === 'sygnal') return dir
686
+ dir = path.dirname(dir)
687
+ }
688
+ } catch (_) {}
689
+ return undefined
690
+ }
691
+
692
+ /** Real paths of sygnal's 'astro/client' ESM file. */
693
+ function astroClientFiles(root: string): Set<string> {
694
+ const files = new Set<string>()
695
+ const dir = sygnalPackageDir(root)
696
+ if (!dir) return files
697
+ try {
698
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))
699
+ const entry = pkg.exports?.['./astro/client']
700
+ const rel = typeof entry === 'string' ? entry : entry?.import
701
+ if (typeof rel !== 'string') return files
702
+ const file = path.resolve(dir, rel)
703
+ files.add(file.split(path.sep).join('/'))
704
+ files.add(fs.realpathSync(file).split(path.sep).join('/'))
705
+ } catch (_) {}
706
+ return files
707
+ }
708
+
709
+ function sygnalInNodeModules(root: string): boolean {
710
+ const dir = sygnalPackageDir(root)
711
+ if (!dir) return false
712
+ try {
713
+ return fs.realpathSync(dir).split(path.sep).includes('node_modules')
714
+ } catch (_) {
715
+ return false
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Absolute path of the ESM build of 'sygnal/diagnostics' (its "import"
721
+ * condition), so the checks share the core instance the tests import.
722
+ */
723
+ function diagnosticsSetupFile(root: string): string | undefined {
724
+ const dir = sygnalPackageDir(root)
725
+ if (!dir) return undefined
726
+ try {
727
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))
728
+ const entry = pkg.exports?.['./diagnostics']
729
+ const rel = typeof entry === 'string' ? entry : entry?.import?.default || entry?.import || entry?.default
730
+ if (typeof rel !== 'string') return undefined
731
+ const file = path.resolve(dir, rel)
732
+ return fs.existsSync(file) ? file : undefined
733
+ } catch (_) {
734
+ return undefined
735
+ }
736
+ }
737
+
738
+ /** Load sygnal-check from the project (optional dependency); undefined when it is missing. */
739
+ async function loadSygnalCheck(root: string): Promise<any> {
740
+ try {
741
+ const file = createRequire(path.join(root, 'package.json')).resolve('sygnal-check')
742
+ return await import(/* @vite-ignore */ pathToFileURL(file).href)
743
+ } catch (_) {}
744
+ try {
745
+ const name = 'sygnal-check'
746
+ return await import(/* @vite-ignore */ name)
747
+ } catch (_) {
748
+ return undefined
749
+ }
750
+ }
751
+
752
+ const SOURCE_RE = /\.[cm]?[jt]sx?$/
753
+
754
+ /** sygnal-check's one-line format: `file:line:col CODE [severity] Component: message (fix)` */
755
+ function formatLine(d: any): string {
756
+ const where = d.file ? `${d.file}:${d.line}:${d.column}` : '<sygnal-check>'
757
+ const sev = d.severity === 'warn' ? '' : ` [${d.severity}]`
758
+ return `${where} ${d.code}${sev} ${d.component ? d.component + ': ' : ''}${d.message}${d.fix ? ` (${d.fix})` : ''}`
759
+ }
760
+
761
+ // Source directories checked by default, those that exist (Vike apps use
762
+ // pages/ and renderer/, often without src/)
763
+ const DEFAULT_INCLUDE = ['src', 'pages', 'renderer']
764
+
765
+ /**
766
+ * What sygnal-check checks: `include` when given, else the existing default
767
+ * directories, else the project root (sygnal-check skips node_modules, dist
768
+ * and other build output). Logs one notice when the default finds no
769
+ * directory, or when none of the given paths exists, so an empty check is
770
+ * never a silent all-clear.
771
+ */
772
+ function checkInclude(root: string, include: string[] | undefined, notice: (m: string) => void): string[] {
773
+ const exists = (p: string) => /[*?[{]/.test(p) || fs.existsSync(path.resolve(root, p))
774
+ if (include && include.length) {
775
+ if (!include.some(exists)) notice(`[sygnal] sygnal-check: none of check.include (${include.join(', ')}) exists under ${root}, so nothing is checked`)
776
+ return include
777
+ }
778
+ const dirs = DEFAULT_INCLUDE.filter(exists)
779
+ if (dirs.length) return dirs
780
+ notice(`[sygnal] sygnal-check: no ${DEFAULT_INCLUDE.join('/, ')}/ directory under ${root}; checking the project root (set check.include to choose)`)
781
+ return ['.']
782
+ }
783
+
784
+ /** Run sygnal-check now and after source changes; report to the terminal and the browser. */
785
+ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
786
+ defaults: { strict: boolean, ignore: string[] }, explicit: boolean) {
787
+ const mod = await loadSygnalCheck(root)
788
+ const logger = server?.config?.logger || console
789
+ if (!mod || typeof mod.check !== 'function') {
790
+ if (explicit) logger.info('[sygnal] sygnal-check is not installed, so static checks are off (npm i -D sygnal-check)')
791
+ return
792
+ }
793
+ const include = checkInclude(root, opts.include, (m: string) => logger.info(m))
794
+ const ignore = opts.ignore || defaults.ignore
795
+ const strict = opts.strict === undefined ? defaults.strict : !!opts.strict
796
+ // D144: the a11y lane is a warning unless asked for as an error, under strict too
797
+ const a11y = opts.a11y === 'error' ? 'error' : 'warn'
798
+ // Only error-severity findings open Vite's overlay: while an overlay is open
799
+ // Vite's client reloads the page on the first HMR update, so warnings stay
800
+ // in the terminal and the browser console. overlay: 'warn' is treated as
801
+ // 'error' (with a one-time notice).
802
+ if (opts.overlay === 'warn') {
803
+ logger.info("[sygnal] check.overlay: 'warn' is treated as 'error': warnings go to the browser console, not Vite's error overlay (an open overlay makes Vite reload the page on the next update)")
804
+ }
805
+ const overlay = opts.overlay !== false
806
+ let lastKey: string | undefined
807
+ let payload: any = null
808
+ let overlayErr: any = null
809
+
810
+ const event = () => ({ type: 'custom', event: CHECK_EVENT, data: payload })
811
+ const errorMessage = () => ({ type: 'error', err: overlayErr })
812
+ // Every connected client (after a source change). An error with no client
813
+ // connected would be buffered by Vite and shown on the next page load.
814
+ const broadcast = (findingsChanged: boolean) => {
815
+ const ws = server.ws
816
+ if (!ws || typeof ws.send !== 'function') return
817
+ try {
818
+ if (findingsChanged && payload) ws.send(event())
819
+ if (overlayErr && !(ws.clients && ws.clients.size === 0)) ws.send(errorMessage())
820
+ } catch (_) {}
821
+ }
822
+
823
+ const run = (initial = false) => {
824
+ let diags: any[]
825
+ try {
826
+ diags = mod.check(include, { cwd: root, strict, ignore, a11y })
827
+ } catch (err: any) {
828
+ logger.warn(`[sygnal] sygnal-check failed: ${err?.message || err}`, { timestamp: true })
829
+ return
830
+ }
831
+ // A sygnal-check from before D144 reports SYG7xx as errors under strict: keep them warnings
832
+ if (a11y !== 'error') {
833
+ diags = diags.map(d => d && d.severity === 'error' && /^SYG7\d\d$/.test(d.code) ? { ...d, severity: 'warn' } : d)
834
+ }
835
+ const shown = diags.filter(d => d.severity !== 'info')
836
+ const lines = shown.map(formatLine)
837
+ const key = lines.join('\n')
838
+ const changed = key !== lastKey
839
+ if (changed) {
840
+ lastKey = key
841
+ const text: string = typeof mod.formatDiagnostics === 'function'
842
+ ? mod.formatDiagnostics(diags)
843
+ : lines.concat(`sygnal-check: ${shown.length} finding(s)`).join('\n')
844
+ const summary = text.split('\n').pop() || ''
845
+ if (shown.length) logger.warn(text, { timestamp: true })
846
+ else logger.info(summary, { timestamp: true })
847
+ payload = {
848
+ summary,
849
+ diagnostics: shown.map((d, i) => ({ code: d.code, severity: d.severity, file: d.file, line: d.line, column: d.column, text: lines[i] })),
850
+ }
851
+ const forOverlay = overlay ? shown.filter(d => d.severity === 'error') : []
852
+ const first = forOverlay[0]
853
+ overlayErr = first ? {
854
+ plugin: OVERLAY_PLUGIN,
855
+ message: forOverlay.map(formatLine).join('\n'),
856
+ stack: '',
857
+ id: path.resolve(root, first.file || ''),
858
+ loc: { file: path.resolve(root, first.file || ''), line: first.line, column: first.column },
859
+ } : null
860
+ }
861
+ // The first check runs before any page is open; pages ask for the
862
+ // findings when they load (CHECK_REQUEST). After a change the overlay is
863
+ // sent again even when the findings are the same: the HMR update closed it.
864
+ if (!initial) broadcast(changed)
865
+ }
866
+
867
+ run(true)
868
+ // A page that loads (or reloads) asks for the current findings: answer
869
+ // that client only, never every client.
870
+ try {
871
+ server.ws?.on?.(CHECK_REQUEST, (_data: any, client: any) => {
872
+ try {
873
+ if (payload) client?.send?.(event())
874
+ if (overlayErr) client?.send?.(errorMessage())
875
+ } catch (_) {}
876
+ })
877
+ } catch (_) {}
878
+
879
+ let timer: any
880
+ const onChange = (file: string) => {
881
+ if (!SOURCE_RE.test(file) || file.split(/[\\/]/).includes('node_modules')) return
882
+ clearTimeout(timer)
883
+ timer = setTimeout(run, 100)
884
+ }
885
+ const watcher = server?.watcher
886
+ if (watcher && typeof watcher.on === 'function') {
887
+ watcher.on('change', onChange)
888
+ watcher.on('add', onChange)
889
+ watcher.on('unlink', onChange)
890
+ }
891
+ try { server.httpServer?.once?.('close', () => clearTimeout(timer)) } catch (_) {}
892
+ }
893
+
894
+ const DEV_FLAG = 'if (globalThis.__SYGNAL_DEV__ === undefined) globalThis.__SYGNAL_DEV__ = true;'
895
+ const STRICT_FLAG = 'if (globalThis.__SYGNAL_STRICT__ === undefined) globalThis.__SYGNAL_STRICT__ = true;'
896
+
897
+ // A top-level (column 0) run(Component, ...) statement: bare, or declaring a
898
+ // new binding. Assignments to existing variables, nested calls and indented
899
+ // calls (e.g. inside a function or test callback) do not match (B-007). It is
900
+ // matched against the code with comments and strings blanked out.
901
+ const RUN_CALL_RE = /^(?:(?:const|let|var)\s+(\{[^}]*\}|[\w$]+)\s*=\s*)?run\s*\(\s*([A-Z][\w$]*)/m
902
+
903
+ // Comments, string literals and template literals (a regex literal containing
904
+ // a quote or `//` can over-blank; that only ever skips HMR wiring).
905
+ const COMMENT_OR_STRING_RE = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|(['"])(?:\\.|(?!\1)[^\\\n])*\1?|`(?:\\[\s\S]|[^\\`])*`?/g
906
+
907
+ /** Replace comments (and, with `strings`, string/template literals) by spaces, keeping offsets and newlines. */
908
+ function blankOut(code: string, strings: boolean): string {
909
+ return code.replace(COMMENT_OR_STRING_RE, m =>
910
+ !strings && m[0] !== '/' ? m : m.replace(/[^\n]/g, ' ')
911
+ )
912
+ }
913
+
914
+ // One directive ('use strict', "use client", ...) with any leading whitespace
915
+ // and comments. It must end with `;` (group 2) or at the end of its line.
916
+ const DIRECTIVE_RE = /(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*(['"])(?:\\.|(?!\1)[^\\\n])*\1(?:[ \t]*(;)|(?=[ \t]*(?:\/\/[^\n]*|\/\*[^\n]*?\*\/)?[ \t]*(?:\r?\n|$)))/y
917
+
918
+ /** Insert `text` (the dev snippet) after a shebang line and the directive prologue. */
919
+ function flagInsertion(code: string, text: string): [number, string] {
920
+ let at = code.startsWith('#!') ? (code.indexOf('\n') + 1 || code.length) : 0
921
+ let semicolon = true
922
+ DIRECTIVE_RE.lastIndex = at
923
+ let m
924
+ while ((m = DIRECTIVE_RE.exec(code))) {
925
+ at = DIRECTIVE_RE.lastIndex
926
+ semicolon = !!m[2]
927
+ }
928
+ return [at, (semicolon ? '' : ';') + text]
929
+ }
930
+
931
+ const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
932
+
933
+ function vlq(n: number): string {
934
+ let v = n < 0 ? (-n << 1) | 1 : n << 1
935
+ let out = ''
936
+ do {
937
+ let digit = v & 31
938
+ v >>>= 5
939
+ if (v) digit |= 32
940
+ out += B64[digit]
941
+ } while (v)
942
+ return out
943
+ }
944
+
945
+ /**
946
+ * Apply single-line insertions (at offsets in the original code) plus an
947
+ * appended tail, and build a v3 sourcemap without extra dependencies. No line
948
+ * is added before the tail, so every original line maps to itself; on a line
949
+ * with an insertion, the text after it maps back to its original column.
950
+ */
951
+ function withSourcemap(code: string, inserts: Array<[number, string]>, tail: string, id: string) {
952
+ inserts.sort((a, b) => a[0] - b[0])
953
+ let out = ''
954
+ let last = 0
955
+ for (const [at, text] of inserts) {
956
+ out += code.slice(last, at) + text
957
+ last = at
958
+ }
959
+ out += code.slice(last) + tail
960
+
961
+ let offset = 0
962
+ let k = 0
963
+ let prevLine = 0
964
+ let prevCol = 0
965
+ const mappings = code.split('\n').map((line, i) => {
966
+ const segments: Array<[number, number]> = [[0, 0]] // [generated column, original column]
967
+ let shift = 0
968
+ while (k < inserts.length && inserts[k][0] <= offset + line.length) {
969
+ const col = inserts[k][0] - offset
970
+ shift += inserts[k][1].length
971
+ segments.push([col + shift, col])
972
+ k++
973
+ }
974
+ offset += line.length + 1
975
+ let genCol = 0
976
+ return segments.map(([gen, col]) => {
977
+ const seg = vlq(gen - genCol) + 'A' + vlq(i - prevLine) + vlq(col - prevCol)
978
+ genCol = gen
979
+ prevLine = i
980
+ prevCol = col
981
+ return seg
982
+ }).join(',')
983
+ }).join(';')
984
+
985
+ return {
986
+ code: out,
987
+ map: { version: 3, sources: [id], sourcesContent: [code], names: [] as string[], mappings },
988
+ }
989
+ }
990
+
991
+ // A top-level (column 0) defineElement(...) call, as a statement or a declaration's value
992
+ const DEFINE_ELEMENT_CALL_RE = /^(?:(?:export\s+)?(?:const|let|var)\s+[\w$]+\s*=\s*)?defineElement\s*\(/m
993
+ const ELEMENT_IMPORT_RE = /import\s+\{[^}]*\bdefineElement\b[^}]*\}\s+from\s+['"]sygnal\/element['"]/
994
+
995
+ /**
996
+ * PLAN-4 GS-13: a module that defines custom elements with 'sygnal/element' accepts its own
997
+ * hot updates. Re-running it calls defineElement again for the same tags, which swaps the
998
+ * component in every live element and keeps each one's state; an edited component module it
999
+ * imports reaches it the same way. Only for a top-level defineElement call, outside test
1000
+ * files, when the module has no HMR code of its own and doesn't import run() (an entry's
1001
+ * run() wiring decides there).
1002
+ */
1003
+ function elementHmr(code: string, noComments: string, id: string) {
1004
+ if (/\.(test|spec)\.[jt]sx?$/.test(id)) return null
1005
+ if (!ELEMENT_IMPORT_RE.test(noComments) || noComments.includes('import.meta.hot')) return null
1006
+ if (!DEFINE_ELEMENT_CALL_RE.test(blankOut(code, true))) return null
1007
+ return withSourcemap(code, [], ELEMENT_HMR_BLOCK, id)
1008
+ }
1009
+
1010
+ const ELEMENT_HMR_BLOCK = `
1011
+ if (import.meta.hot) import.meta.hot.accept()
1012
+ `
1013
+
150
1014
  function hmrBlock(componentPath: string, hmrRef: string, disposeRef: string): string {
151
1015
  return `
152
1016
  if (import.meta.hot) {