sygnal 7.0.0 → 7.2.0-preview.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 (343) hide show
  1. package/CHANGELOG.md +632 -0
  2. package/dist/agent-ui.cjs.js +592 -0
  3. package/dist/agent-ui.cjs.js.map +1 -0
  4. package/dist/agent-ui.esm.js +582 -0
  5. package/dist/agent-ui.esm.js.map +1 -0
  6. package/dist/api.cjs.js +63 -0
  7. package/dist/api.cjs.js.map +1 -0
  8. package/dist/api.esm.js +2 -0
  9. package/dist/api.esm.js.map +1 -0
  10. package/dist/astro/index.cjs.js +968 -4
  11. package/dist/astro/index.cjs.js.map +1 -1
  12. package/dist/astro/index.mjs +968 -4
  13. package/dist/astro/index.mjs.map +1 -1
  14. package/dist/astro/server.cjs.js +73 -45
  15. package/dist/astro/server.cjs.js.map +1 -1
  16. package/dist/astro/server.mjs +73 -45
  17. package/dist/astro/server.mjs.map +1 -1
  18. package/dist/canvas.cjs.js +2919 -0
  19. package/dist/canvas.cjs.js.map +1 -0
  20. package/dist/canvas.esm.js +2866 -0
  21. package/dist/canvas.esm.js.map +1 -0
  22. package/dist/devtools.cjs.js +38 -6
  23. package/dist/devtools.cjs.js.map +1 -1
  24. package/dist/devtools.esm.js +38 -6
  25. package/dist/devtools.esm.js.map +1 -1
  26. package/dist/diagnostics.cjs.js +224 -20
  27. package/dist/diagnostics.cjs.js.map +1 -1
  28. package/dist/diagnostics.esm.js +224 -20
  29. package/dist/diagnostics.esm.js.map +1 -1
  30. package/dist/guide/accessibility.md +1 -1
  31. package/dist/guide/ai-compose.md +150 -0
  32. package/dist/guide/behaviors.md +2 -1
  33. package/dist/guide/collaborative-state.md +265 -0
  34. package/dist/guide/element-commands.md +36 -2
  35. package/dist/guide/forms-reference.md +5 -5
  36. package/dist/guide/forms.md +3 -2
  37. package/dist/guide/http.md +28 -1
  38. package/dist/guide/islands.md +240 -0
  39. package/dist/guide/recipes/code-editor.md +1 -1
  40. package/dist/guide/renderers/agent-ui.md +138 -0
  41. package/dist/guide/renderers/custom-targets.md +126 -0
  42. package/dist/guide/renderers/overview.md +120 -0
  43. package/dist/guide/renderers/react-native.md +128 -0
  44. package/dist/guide/renderers/terminal.md +306 -0
  45. package/dist/guide/renderers/three.md +332 -0
  46. package/dist/guide/renderers/write-once.md +137 -0
  47. package/dist/guide/resources.md +2 -0
  48. package/dist/guide/sandboxed-components.md +223 -0
  49. package/dist/guide/timers.md +32 -5
  50. package/dist/guide/typed-apis.md +442 -0
  51. package/dist/guide/ui/accordion.md +11 -0
  52. package/dist/guide/ui/combobox.md +112 -8
  53. package/dist/guide/ui/dialog.md +96 -20
  54. package/dist/guide/ui/menu.md +57 -5
  55. package/dist/guide/ui/overview.md +8 -4
  56. package/dist/guide/ui/popover.md +69 -9
  57. package/dist/guide/ui/select.md +54 -4
  58. package/dist/guide/ui/tabs.md +1 -1
  59. package/dist/guide/ui/tooltip.md +7 -5
  60. package/dist/guide/virtual-collections.md +3 -0
  61. package/dist/guide/widgets.md +90 -1
  62. package/dist/host.cjs.js +2181 -0
  63. package/dist/host.cjs.js.map +1 -0
  64. package/dist/host.esm.js +2146 -0
  65. package/dist/host.esm.js.map +1 -0
  66. package/dist/index.cjs.js +3948 -586
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +584 -44
  69. package/dist/index.esm.js +3918 -586
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/primitives.cjs.js +143 -0
  72. package/dist/primitives.cjs.js.map +1 -0
  73. package/dist/primitives.esm.js +133 -0
  74. package/dist/primitives.esm.js.map +1 -0
  75. package/dist/react-native.cjs.js +248 -0
  76. package/dist/react-native.cjs.js.map +1 -0
  77. package/dist/react-native.esm.js +244 -0
  78. package/dist/react-native.esm.js.map +1 -0
  79. package/dist/replay.cjs.js +39 -0
  80. package/dist/replay.cjs.js.map +1 -0
  81. package/dist/replay.esm.js +2 -0
  82. package/dist/replay.esm.js.map +1 -0
  83. package/dist/sandbox-build.cjs.js +140 -0
  84. package/dist/sandbox-build.cjs.js.map +1 -0
  85. package/dist/sandbox-build.esm.js +137 -0
  86. package/dist/sandbox-build.esm.js.map +1 -0
  87. package/dist/sandbox-kernel.esm.js +212 -0
  88. package/dist/sandbox-kernel.esm.js.map +1 -0
  89. package/dist/sandbox-worker.cjs.js +857 -0
  90. package/dist/sandbox-worker.cjs.js.map +1 -0
  91. package/dist/sandbox-worker.esm.js +854 -0
  92. package/dist/sandbox-worker.esm.js.map +1 -0
  93. package/dist/sandbox.cjs.js +1425 -0
  94. package/dist/sandbox.cjs.js.map +1 -0
  95. package/dist/sandbox.esm.js +1406 -0
  96. package/dist/sandbox.esm.js.map +1 -0
  97. package/dist/sygnal.min.js +1 -1
  98. package/dist/sygnal.min.js.map +1 -1
  99. package/dist/terminal.cjs.js +1314 -0
  100. package/dist/terminal.cjs.js.map +1 -0
  101. package/dist/terminal.esm.js +1304 -0
  102. package/dist/terminal.esm.js.map +1 -0
  103. package/dist/three.cjs.js +1111 -0
  104. package/dist/three.cjs.js.map +1 -0
  105. package/dist/three.esm.js +1079 -0
  106. package/dist/three.esm.js.map +1 -0
  107. package/dist/ui-combobox.cjs.js +256 -18
  108. package/dist/ui-combobox.cjs.js.map +1 -1
  109. package/dist/ui-combobox.esm.js +256 -18
  110. package/dist/ui-combobox.esm.js.map +1 -1
  111. package/dist/ui-menu.cjs.js +150 -16
  112. package/dist/ui-menu.cjs.js.map +1 -1
  113. package/dist/ui-menu.esm.js +151 -17
  114. package/dist/ui-menu.esm.js.map +1 -1
  115. package/dist/ui-select.cjs.js +95 -9
  116. package/dist/ui-select.cjs.js.map +1 -1
  117. package/dist/ui-select.esm.js +95 -9
  118. package/dist/ui-select.esm.js.map +1 -1
  119. package/dist/ui.cjs.js +679 -173
  120. package/dist/ui.cjs.js.map +1 -1
  121. package/dist/ui.esm.js +679 -175
  122. package/dist/ui.esm.js.map +1 -1
  123. package/dist/vike/config/package.json +1 -1
  124. package/dist/vite/plugin.cjs.js +968 -4
  125. package/dist/vite/plugin.cjs.js.map +1 -1
  126. package/dist/vite/plugin.mjs +968 -4
  127. package/dist/vite/plugin.mjs.map +1 -1
  128. package/dist/worker-page.cjs.js +669 -0
  129. package/dist/worker-page.cjs.js.map +1 -0
  130. package/dist/worker-page.esm.js +665 -0
  131. package/dist/worker-page.esm.js.map +1 -0
  132. package/dist/worker.cjs.js +377 -0
  133. package/dist/worker.cjs.js.map +1 -0
  134. package/dist/worker.esm.js +374 -0
  135. package/dist/worker.esm.js.map +1 -0
  136. package/dist/yjs-testing.cjs.js +98 -0
  137. package/dist/yjs-testing.cjs.js.map +1 -0
  138. package/dist/yjs-testing.esm.js +77 -0
  139. package/dist/yjs-testing.esm.js.map +1 -0
  140. package/dist/yjs.cjs.js +923 -0
  141. package/dist/yjs.cjs.js.map +1 -0
  142. package/dist/yjs.esm.js +900 -0
  143. package/dist/yjs.esm.js.map +1 -0
  144. package/dist/zag.cjs.js +11 -1
  145. package/dist/zag.cjs.js.map +1 -1
  146. package/dist/zag.esm.js +11 -1
  147. package/dist/zag.esm.js.map +1 -1
  148. package/llms/ai.md +65 -0
  149. package/llms/collaboration.md +5 -0
  150. package/llms/graphics.md +10 -0
  151. package/llms/renderers.md +7 -0
  152. package/llms/replay.md +5 -0
  153. package/llms/sandbox.md +5 -0
  154. package/llms/typed-apis.md +5 -0
  155. package/llms/ui.md +10 -0
  156. package/llms-full.txt +444 -0
  157. package/llms.txt +37 -35
  158. package/package.json +139 -3
  159. package/src/agent-ui/mcp.ts +152 -0
  160. package/src/agent-ui/snapshot.ts +212 -0
  161. package/src/agent-ui.d.ts +106 -0
  162. package/src/agent-ui.ts +249 -0
  163. package/src/ai.d.ts +95 -2
  164. package/src/api.d.ts +303 -0
  165. package/src/api.ts +9 -0
  166. package/src/canvas/Canvas.ts +585 -0
  167. package/src/canvas/backend.ts +627 -0
  168. package/src/canvas/geometry.ts +187 -0
  169. package/src/canvas/gesture.ts +23 -0
  170. package/src/canvas/image.ts +197 -0
  171. package/src/canvas/kit.ts +130 -0
  172. package/src/canvas/paint.ts +260 -0
  173. package/src/canvas/pointer.ts +25 -0
  174. package/src/canvas/snap.ts +68 -0
  175. package/src/canvas/svg.ts +99 -0
  176. package/src/canvas.d.ts +373 -0
  177. package/src/canvas.ts +46 -0
  178. package/src/core/actions.ts +14 -3
  179. package/src/core/cell.ts +12 -4
  180. package/src/core/debug.ts +8 -3
  181. package/src/core/hooks.ts +16 -2
  182. package/src/core/hosts/collection.ts +1 -1
  183. package/src/core/hosts/switchable.ts +26 -8
  184. package/src/core/instance.ts +98 -45
  185. package/src/core/markers/portal.ts +4 -0
  186. package/src/core/registry.ts +4 -0
  187. package/src/core/runtime.ts +34 -25
  188. package/src/core/statics.ts +5 -5
  189. package/src/cycle/dom/EventDelegator.ts +37 -33
  190. package/src/cycle/dom/MainDOMSource.ts +16 -14
  191. package/src/cycle/dom/enrichEventStream.ts +13 -9
  192. package/src/cycle/dom/fromEvent.ts +6 -10
  193. package/src/cycle/dom/index.ts +11 -7
  194. package/src/cycle/dom/isolate.ts +13 -6
  195. package/src/cycle/dom/makeDOMDriver.ts +6 -2
  196. package/src/cycle/dom/utils.ts +9 -2
  197. package/src/cycle/run/adapt.ts +4 -12
  198. package/src/devtools.d.ts +9 -0
  199. package/src/devtools.ts +2 -1
  200. package/src/extra/ai/compose/catalog.ts +2 -1
  201. package/src/extra/ai/compose/decisions.ts +1 -2
  202. package/src/extra/ai/compose/generated.ts +234 -0
  203. package/src/extra/ai/compose/index.ts +6 -2
  204. package/src/extra/ai/compose/layout.ts +3 -1
  205. package/src/extra/ai/compose/llm.ts +7 -3
  206. package/src/extra/ai/compose/types.ts +2 -2
  207. package/src/extra/ai/transports/shared.ts +3 -1
  208. package/src/extra/api/contract.ts +485 -0
  209. package/src/extra/api/index.ts +26 -0
  210. package/src/extra/behaviors.ts +3 -3
  211. package/src/extra/controlled.ts +44 -0
  212. package/src/extra/copyAsTest.ts +22 -0
  213. package/src/extra/devtools.ts +5 -3
  214. package/src/extra/devtoolsActions.ts +8 -0
  215. package/src/extra/devtoolsNext.ts +1 -1
  216. package/src/extra/diagnostics/checks/actionLog.ts +8 -3
  217. package/src/extra/diagnostics/checks/behaviors.ts +25 -0
  218. package/src/extra/diagnostics/checks/elementCommands.ts +17 -5
  219. package/src/extra/diagnostics/checks/index.ts +1 -1
  220. package/src/extra/diagnostics/checks/next.ts +78 -4
  221. package/src/extra/diagnostics/checks/props.ts +6 -1
  222. package/src/extra/diagnostics/checks/replies.ts +11 -7
  223. package/src/extra/diagnostics/checks/timers.ts +8 -2
  224. package/src/extra/diagnostics/checks/virtual.ts +11 -0
  225. package/src/extra/diagnostics/codes.ts +51 -1
  226. package/src/extra/elementCommands.ts +20 -4
  227. package/src/extra/fetchDriver.ts +4 -1
  228. package/src/extra/fetchEventSource.ts +6 -2
  229. package/src/extra/field.ts +29 -0
  230. package/src/extra/form.ts +42 -16
  231. package/src/extra/frame.ts +54 -0
  232. package/src/extra/keymap.ts +41 -0
  233. package/src/extra/pager.ts +2 -0
  234. package/src/extra/persist.ts +2 -1
  235. package/src/extra/pointer.ts +262 -0
  236. package/src/extra/processForm.ts +34 -1
  237. package/src/extra/replay/address.ts +49 -0
  238. package/src/extra/replay/entropy.ts +99 -0
  239. package/src/extra/replay/format.ts +263 -0
  240. package/src/extra/replay/index.ts +8 -0
  241. package/src/extra/replay/inert.ts +22 -0
  242. package/src/extra/replay/recorder.ts +247 -0
  243. package/src/extra/replay/replayer.ts +234 -0
  244. package/src/extra/replay/report.ts +126 -0
  245. package/src/extra/replay/schema.ts +52 -0
  246. package/src/extra/replay/version.ts +3 -0
  247. package/src/extra/router.ts +7 -3
  248. package/src/extra/selection.ts +2 -0
  249. package/src/extra/socketDriver.ts +51 -6
  250. package/src/extra/ssr.ts +128 -42
  251. package/src/extra/testing.ts +295 -39
  252. package/src/extra/timers.ts +96 -7
  253. package/src/extra/undo.ts +2 -0
  254. package/src/extra/virtual.ts +25 -2
  255. package/src/extra/widget.ts +106 -17
  256. package/src/host/actions.ts +383 -0
  257. package/src/host/delegation.ts +101 -0
  258. package/src/host/dom.ts +553 -0
  259. package/src/host/driver.ts +185 -0
  260. package/src/host/selector.ts +119 -0
  261. package/src/host/style.ts +51 -0
  262. package/src/host.d.ts +298 -0
  263. package/src/host.ts +46 -0
  264. package/src/index.d.ts +302 -38
  265. package/src/index.ts +15 -3
  266. package/src/pragma/blocks.ts +211 -0
  267. package/src/pragma/index.ts +44 -10
  268. package/src/primitives.d.ts +54 -0
  269. package/src/primitives.ts +136 -0
  270. package/src/react-native.d.ts +57 -0
  271. package/src/react-native.ts +285 -0
  272. package/src/react-peers.d.ts +21 -3
  273. package/src/replay.d.ts +185 -0
  274. package/src/replay.ts +6 -0
  275. package/src/sandbox/build.ts +119 -0
  276. package/src/sandbox/create.ts +187 -0
  277. package/src/sandbox/engine.ts +267 -0
  278. package/src/sandbox/generated.ts +195 -0
  279. package/src/sandbox/kernel.ts +180 -0
  280. package/src/sandbox/mirror.ts +269 -0
  281. package/src/sandbox/protocol.ts +128 -0
  282. package/src/sandbox/quickjs.ts +74 -0
  283. package/src/sandbox/ssr.ts +41 -0
  284. package/src/sandbox/widget.ts +60 -0
  285. package/src/sandbox/worker-entry.ts +59 -0
  286. package/src/sandbox/worker.ts +137 -0
  287. package/src/sandbox-build.d.ts +12 -0
  288. package/src/sandbox-build.ts +6 -0
  289. package/src/sandbox.d.ts +299 -0
  290. package/src/sandbox.ts +25 -0
  291. package/src/shared.ts +12 -0
  292. package/src/terminal/paint.ts +712 -0
  293. package/src/terminal.d.ts +167 -0
  294. package/src/terminal.ts +530 -0
  295. package/src/three/assets.ts +170 -0
  296. package/src/three/host.ts +36 -0
  297. package/src/three/scene.ts +217 -0
  298. package/src/three/target.ts +252 -0
  299. package/src/three.d.ts +221 -0
  300. package/src/three.ts +30 -0
  301. package/src/ui/accordion.ts +21 -7
  302. package/src/ui/dialog.ts +192 -90
  303. package/src/ui/disclosure.ts +2 -0
  304. package/src/ui/dom.ts +30 -0
  305. package/src/ui/layers.ts +132 -0
  306. package/src/ui/popover.ts +120 -21
  307. package/src/ui/shared.ts +98 -12
  308. package/src/ui/stack.ts +18 -0
  309. package/src/ui/tabs.ts +2 -0
  310. package/src/ui/toaster.ts +16 -15
  311. package/src/ui/tooltip.ts +24 -8
  312. package/src/ui/tooltips.ts +26 -14
  313. package/src/ui/zag/combobox.ts +180 -13
  314. package/src/ui/zag/menu.ts +97 -17
  315. package/src/ui/zag/select.ts +15 -4
  316. package/src/ui/zag/shared.ts +60 -8
  317. package/src/ui-combobox.d.ts +16 -1
  318. package/src/ui-menu.d.ts +22 -7
  319. package/src/ui-zag-types.d.ts +13 -5
  320. package/src/ui.d.ts +62 -7
  321. package/src/ui.ts +2 -2
  322. package/src/vite/api.ts +186 -0
  323. package/src/vite/compile.ts +280 -0
  324. package/src/vite/plugin.d.ts +34 -0
  325. package/src/vite/plugin.ts +78 -4
  326. package/src/vite/sandbox.ts +16 -0
  327. package/src/worker/page.ts +396 -0
  328. package/src/worker/protocol.ts +48 -0
  329. package/src/worker/target.ts +282 -0
  330. package/src/worker-page.d.ts +42 -0
  331. package/src/worker-page.ts +12 -0
  332. package/src/worker.d.ts +65 -0
  333. package/src/worker.ts +16 -0
  334. package/src/yjs/driver.ts +253 -0
  335. package/src/yjs/mapping.ts +389 -0
  336. package/src/yjs/shared.ts +138 -0
  337. package/src/yjs/testing.ts +91 -0
  338. package/src/yjs-testing.d.ts +34 -0
  339. package/src/yjs-testing.ts +7 -0
  340. package/src/yjs.d.ts +109 -0
  341. package/src/yjs.ts +18 -0
  342. package/src/zag.d.ts +2 -0
  343. package/src/zag.ts +8 -1
@@ -137,6 +137,10 @@ const CODE_TITLES = {
137
137
  SYG111: 'Controlled input has no input listener',
138
138
  SYG115: 'Unknown DOM event shorthand',
139
139
  SYG116: 'EVENTS value has no string type',
140
+ // PLAN-13 N-1: dev entry (checks/next.ts onNext) and sygnal-check
141
+ SYG117: 'next() names an action with no model entry',
142
+ // ui-sweep R1: dev entry (checks/behaviors.ts onIntent) and sygnal-check
143
+ SYG118: 'Host intent replaces a behavior trigger',
140
144
  SYG124: 'Component used as a control or selector',
141
145
  SYG125: 'Control given .intent, .model or .initialState',
142
146
  SYG126: 'Control rendered but never listened to',
@@ -172,11 +176,15 @@ const CODE_TITLES = {
172
176
  // PLAN-5 V-1: VirtualCollection (checks/virtual.ts)
173
177
  SYG430: 'VirtualCollection has no bounded height',
174
178
  SYG431: 'VirtualCollection items without ids',
179
+ // PLAN-13 F-3: dev entry (checks/next.ts onRender) and sygnal-check
180
+ SYG429: 'Switchable state map names no page',
175
181
  SYG432: 'VirtualCollection item does not render one element',
176
182
  SYG433: 'VirtualCollection scroll target not in the list',
177
183
  SYG434: 'Invalid VirtualCollection estimateSize or overscan',
178
184
  // PLAN-5 3-H (G-449): sortable over a list shown in another order (checks/sortable.ts)
179
185
  SYG435: 'Sortable list shown filtered or in another order',
186
+ // Temper feedback 2026-10: VirtualCollection (checks/virtual.ts, through the core bridge)
187
+ SYG436: 'VirtualCollection estimateSize far from the measured row heights',
180
188
  // PLAN-6 A-1 (sygnal/ai agent layer)
181
189
  SYG440: 'Two agent declarations with the same name',
182
190
  SYG441: 'Agent Collection items without unique ids',
@@ -185,8 +193,14 @@ const CODE_TITLES = {
185
193
  // SYG2xx state & reducers (1A)
186
194
  SYG201: 'STATE reducer dropped keys from the previous state',
187
195
  SYG202: 'STATE reducer returned undefined',
196
+ // sandbox research §6 (dev entry, checks/next.ts)
197
+ SYG204: 'Root component has no state, so its view never renders',
188
198
  // SYG3xx streams & intent construction (1A)
189
199
  SYG301: 'RxJS operator used on an xstream stream',
200
+ // PLAN-13 X-1: static only (sygnal-check)
201
+ SYG302: "Default import from 'xstream' or 'xstream/extra/*'",
202
+ // Temper feedback 2026-10: static only (sygnal-check; warn under --strict)
203
+ SYG303: 'STATE.stream with dropRepeats() in an intent',
190
204
  // SYG4xx collections / switchable / sub-components / context (1A)
191
205
  SYG401: "Collection 'from' field is missing or not an array",
192
206
  // SYG5xx strict mode / canonical forms (2A; dev-plans/PLAN-1-canonical-forms.md)
@@ -198,6 +212,7 @@ const CODE_TITLES = {
198
212
  SYG506: "Removed in 6.0: CHILD.select() with a component name",
199
213
  SYG507: 'Prop drilled through more than 2 component levels',
200
214
  SYG508: 'select()/errors() round trip where reply actions would do',
215
+ SYG509: 'Clock or randomness read from a global in a handler',
201
216
  // SYG9xx internal (0B)
202
217
  SYG900: 'A diagnostics check threw',
203
218
  // ---- 1E retrofit codes (SYG206-299, 402-499, 601-699, 901-999) ----
@@ -364,12 +379,23 @@ const CODE_TITLES = {
364
379
  SYG254: 'Catalog descriptions or options overlap',
365
380
  SYG256: 'Output guard rejects the updated state',
366
381
  SYG257: 'Output reads a field its action changed (7.0 timing audit)',
382
+ // record/replay: static only (sygnal-check rules/syg258-clock-in-reducer.js; SYG509 under --strict)
383
+ SYG258: 'Clock or randomness read from a global in a STATE reducer',
384
+ // sygnal/yjs (research: dev-plans/research/crdt-state): shared() reports them (dev), SYG260 statically too
385
+ SYG260: 'undo() or undoable() on a key shared() syncs',
386
+ SYG261: 'shared() inferred the kind of an empty array',
387
+ // sygnal/canvas (research: dev-plans/research/canvas-apps): the Canvas reports it (dev)
388
+ SYG262: 'Canvas data layer rebuilt with equal nodes',
367
389
  SYG454: 'State binding changed without a new component identity',
368
390
  // PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
369
391
  SYG160: 'Result part rendered outside a card',
370
392
  SYG684: 'Client tool shadows an agent tool',
371
393
  SYG685: 'Answer for a tool call that is not open',
372
394
  SYG686: 'Ask catalog without a result part',
395
+ // sygnal/api (dev preview)
396
+ SYG690: 'API contract skew',
397
+ SYG691: 'Unknown API endpoint',
398
+ SYG692: 'Sandbox event dropped',
373
399
  // PLAN-4 GS-3: a11y lane (static only, sygnal-check)
374
400
  SYG701: 'Click listener on a non-interactive element',
375
401
  SYG702: 'Form field without an accessible label',
@@ -404,6 +430,7 @@ const STRICT_CODE_SEVERITY = {
404
430
  SYG506: 'error',
405
431
  SYG507: 'info',
406
432
  SYG508: 'warn',
433
+ SYG509: 'warn',
407
434
  };
408
435
  // PLAN-3 1-G (G-143): codes only the 'sygnal/diagnostics' dev entry reports. Like the strict
409
436
  // table, kept OUT of CODE_SEVERITY (zero bytes in the main bundle); the entry registers them
@@ -415,6 +442,10 @@ const DEV_CODE_SEVERITY = {
415
442
  SYG112: 'error',
416
443
  SYG115: 'warn',
417
444
  SYG116: 'error',
445
+ SYG117: 'warn',
446
+ SYG118: 'warn',
447
+ // sandbox research §6: a root that has no state after it starts (checks/next.ts)
448
+ SYG204: 'warn',
418
449
  // PLAN-4 1-D: controls, static (sygnal-check)
419
450
  SYG124: 'error',
420
451
  SYG125: 'error',
@@ -426,6 +457,12 @@ const DEV_CODE_SEVERITY = {
426
457
  SYG129: 'warn',
427
458
  // PLAN-10 X-1: static only (sygnal-check)
428
459
  SYG170: 'warn',
460
+ // PLAN-13 X-1: static only (sygnal-check)
461
+ SYG302: 'info',
462
+ // Temper feedback 2026-10: static only (sygnal-check); its strict copy reports it at warn
463
+ SYG303: 'info',
464
+ // PLAN-13 F-3 (checks/next.ts)
465
+ SYG429: 'warn',
429
466
  SYG171: 'warn',
430
467
  SYG221: 'error',
431
468
  // PLAN-4 GS-4 (state.ts)
@@ -549,6 +586,11 @@ const DEV_CODE_SEVERITY = {
549
586
  SYG684: 'warn',
550
587
  SYG685: 'warn',
551
588
  SYG686: 'warn',
589
+ // sygnal/api: contractFetch() (SYG690); static only: sygnal-check (SYG691)
590
+ SYG690: 'warn',
591
+ SYG691: 'warn',
592
+ // sygnal/sandbox: an EVENTS type not declared in emits, or data its schema rejects (createSandbox, <Sandboxed>)
593
+ SYG692: 'warn',
552
594
  // PLAN-5 V-1: VirtualCollection (checks/virtual.ts, through the core bridge)
553
595
  SYG430: 'warn',
554
596
  SYG431: 'warn',
@@ -557,6 +599,8 @@ const DEV_CODE_SEVERITY = {
557
599
  SYG434: 'warn',
558
600
  // PLAN-5 3-H (G-449): sortable (checks/sortable.ts)
559
601
  SYG435: 'warn',
602
+ // Temper feedback 2026-10: VirtualCollection (checks/virtual.ts)
603
+ SYG436: 'warn',
560
604
  // PLAN-6 A-1 (sygnal/ai agent layer)
561
605
  SYG440: 'warn',
562
606
  SYG441: 'warn',
@@ -597,6 +641,12 @@ const DEV_CODE_SEVERITY = {
597
641
  SYG256: 'warn',
598
642
  // 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
599
643
  SYG257: 'info',
644
+ // record/replay: static only (sygnal-check); listed so the entry knows its title
645
+ SYG258: 'warn',
646
+ // sygnal/yjs: reported with the severity passed (shared() in dev; sygnal-check: SYG260)
647
+ SYG260: 'warn',
648
+ SYG261: 'warn',
649
+ SYG262: 'warn',
600
650
  // PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
601
651
  SYG454: 'warn',
602
652
  };
@@ -1147,7 +1197,9 @@ const propsCheck = {
1147
1197
  data: { prop: key },
1148
1198
  });
1149
1199
  }
1150
- if ('state' in props) {
1200
+ // PLAN-13 F-3 follow-up: a Switchable page gets the Switchable's props, whose `state` is the
1201
+ // host's binding (a per-page map too), consumed by the host: not a prop meant for the page
1202
+ if ('state' in props && component.__next?.kind !== 'page') {
1151
1203
  const value = props.state;
1152
1204
  const isLens = typeof value === 'string' || (value && typeof value === 'object' && typeof value.get === 'function');
1153
1205
  if (!isLens && value !== undefined && once(`SYG106:${name}:state`)) {
@@ -1774,8 +1826,21 @@ const notACommand = (component, name, cmd) => send('SYG641', {
1774
1826
  fix: `Send { <method>: control or selector, ...options }, e.g. { focus: Email }, or an array of them`,
1775
1827
  data: { command: cmd },
1776
1828
  });
1777
- /** SYG640 (no `el`) or SYG641 (an element, but no command of that name) for `cmd` sent by `component` */
1778
- function reportElementCommand(component, cmd, el) {
1829
+ /** does the page have an element matching `target` (a selector or a control) anywhere? */
1830
+ const onPage = (target) => {
1831
+ try {
1832
+ return !!globalThis.document?.querySelector(String(target));
1833
+ }
1834
+ catch (_) {
1835
+ return false;
1836
+ }
1837
+ };
1838
+ /**
1839
+ * SYG640 (no `el`) or SYG641 (an element, but no command of that name) for `cmd` sent by
1840
+ * `component`. `elsewhere`: the target matches outside the sender's own view (renderComponent
1841
+ * passes it from the rendered tree; otherwise the page is asked)
1842
+ */
1843
+ function reportElementCommand(component, cmd, el, elsewhere) {
1779
1844
  // a disposed instance's pending commands are dropped silently
1780
1845
  if (component && component._disposed)
1781
1846
  return;
@@ -1789,15 +1854,18 @@ function reportElementCommand(component, cmd, el) {
1789
1854
  const data = { method, command: cmd, ...(control ? { control } : { selector: target == null ? target : String(target) }) };
1790
1855
  if (!el) {
1791
1856
  const missing = target == null || target === '';
1857
+ // Temper 2026-10: say when the element is on the page, only not in the sender's view
1858
+ const outside = !missing && (elsewhere ?? onPage(target));
1792
1859
  return send('SYG640', {
1793
1860
  component,
1794
1861
  message: missing
1795
1862
  ? `${what} in ${name} has no target (${String(target)}), so the command was dropped`
1796
- : `${what} in ${name} matched no element: nothing in ${name}'s own view matches ${control ? `the control ${control}` : `'${String(target)}'`} (waited 1 s), so the command was dropped`,
1863
+ : `${what} in ${name} matched no element: nothing in ${name}'s own view matches ${control ? `the control ${control}` : `'${String(target)}'`} (waited 1 s), so the command was dropped` +
1864
+ (outside ? `. A matching element is rendered outside ${name}'s view (by an ancestor, a child component or another part of the page), and a command reaches only its sender's own elements` : ''),
1797
1865
  fix: missing
1798
1866
  ? `Give the method a control or a selector: { ${method}: Email }`
1799
- : `Render the element in ${name}'s view, or send the command from the component that renders it: a child component's elements are isolated from its parent, and a command reaches only its sender's own elements (in a Collection, each item sends its own)`,
1800
- data,
1867
+ : `Render the element in ${name}'s view, or send the command from the component that renders it: a child component's elements are isolated from its parent, and a command reaches only its sender's own elements (in a Collection, each item sends its own). For an element an ancestor renders, hand it off: emit EVENTS: event('FOCUS_NAME') from ${name} (or send PARENT) and send ELEMENT: { ${method}: … } from the ancestor's entry for it; for one inside a child, the parent can use { ${method}: focusWithin('…') }`,
1868
+ data: outside ? { ...data, outside: true } : data,
1801
1869
  });
1802
1870
  }
1803
1871
  // PLAN-5 W-1: the matched element is a widget's host (selector or control target): SYG142 names
@@ -1995,6 +2063,8 @@ function installWidgetHooks() {
1995
2063
  * 432 an item renders a fragment or text, not one element (once per host)
1996
2064
  * 433 scrollToIndex / scrollToId for an index or id not in the (filtered) list
1997
2065
  * 434 estimateSize / overscan not a positive number (or a function, for estimateSize)
2066
+ * 436 a numeric estimateSize far from the measured row heights (the median of the first 10
2067
+ * measurements is off by more than 1.3×; once per host)
1998
2068
  */
1999
2069
  const tag = (owner) => `<VirtualCollection> in ${nameOf(owner)}`;
2000
2070
  function reportVirtual(code, owner, x = {}) {
@@ -2045,6 +2115,15 @@ function reportVirtual(code, owner, x = {}) {
2045
2115
  data: x,
2046
2116
  });
2047
2117
  }
2118
+ if (code == 436) {
2119
+ const low = x.measured > x.estimate;
2120
+ return devReport('SYG436', {
2121
+ component,
2122
+ message: `${tag(owner)}: estimateSize={${x.estimate}}, but its rows measure about ${x.measured} px (the median of the first 10 measured)${low ? ', so the rows not rendered yet count as too short: a fast scroll to the end shows blank space until they are measured, and the scrollbar jumps' : ', so the scrollbar shrinks and jumps as rows are measured'}`,
2123
+ fix: `Pass estimateSize={${x.measured}} (the rows' real height; when unsure, err high), or (item, index) => px when rows differ predictably`,
2124
+ data: x,
2125
+ });
2126
+ }
2048
2127
  }
2049
2128
  /** Publish the VirtualCollection hook on the core bridge. Returns an uninstall function. */
2050
2129
  function installVirtualHooks() {
@@ -2062,6 +2141,30 @@ const what$1 = (v) => v === null || v === undefined ? String(v)
2062
2141
  : `a ${typeof v}`;
2063
2142
  const behaviorsCheck = {
2064
2143
  id: 'behaviors',
2144
+ // SYG118 (ui-sweep R1): a host intent action named like a behavior action replaces the trigger
2145
+ // the behavior built from an option the host passed (`triggers: { ACTION: 'option' }` on the
2146
+ // definition). The intent combine (extra/behaviors.ts) removes a host-replaced action from
2147
+ // `_behaviorActions`, so after the intent ran that map tells which ones the host took
2148
+ onIntent(component, names) {
2149
+ const view = component?.view, uses = view?.uses;
2150
+ if (!uses || typeof uses != 'object' || !once('SYG118:' + nameOf(component)))
2151
+ return;
2152
+ const owned = component._behaviorActions || {};
2153
+ for (const key of Object.keys(uses)) {
2154
+ const b = uses[key], tr = b?.triggers;
2155
+ for (const a in tr) {
2156
+ const opt = tr[a], name = key + '.' + a;
2157
+ if (b.options?.[opt] == null || !names.includes(name) || name in owned)
2158
+ continue;
2159
+ devReport('SYG118', {
2160
+ component,
2161
+ message: `${nameOf(component)}'s intent action '${name}' replaces the trigger the behavior builds from its '${opt}' option, so the elements '${opt}' names no longer dispatch '${name}'`,
2162
+ fix: `List every element in the option instead (${opt}: '${String(b.options[opt])}, .other'), dispatch it from another action with next('${name}'), or leave the '${opt}' option out to replace the trigger on purpose`,
2163
+ data: { key, action: a, option: opt },
2164
+ });
2165
+ }
2166
+ }
2167
+ },
2065
2168
  onModel(component) {
2066
2169
  const view = component?.view, uses = view?.uses;
2067
2170
  if (!uses || typeof uses != 'object' || !once('SYG127:' + nameOf(component)))
@@ -2506,7 +2609,7 @@ function readingSource(component, sink, source) {
2506
2609
  }
2507
2610
  return wrapped;
2508
2611
  }
2509
- function checkRequest(component, sink, req, modelActions) {
2612
+ function checkRequest(component, sink, req, modelActions, handledAbove) {
2510
2613
  if (!req || typeof req !== 'object')
2511
2614
  return;
2512
2615
  const name = nameOf(component);
@@ -2519,17 +2622,21 @@ function checkRequest(component, sink, req, modelActions) {
2519
2622
  seen.add(action);
2520
2623
  if (modelActions.includes(action) || !once(`SYG112:${name}:${action}`))
2521
2624
  continue;
2522
- const near = suggest(action, modelActions.filter(a => !a.startsWith('__')));
2625
+ // Temper 2026-10: a reply goes only to its sender; an ancestor's entry of that name never runs
2626
+ const up = handledAbove?.(action);
2627
+ const near = up ? undefined : suggest(action, modelActions.filter(a => !a.startsWith('__')));
2523
2628
  devReport('SYG112', {
2524
2629
  component,
2525
2630
  message: (connection
2526
2631
  ? `${sink} connection '${connection}' names '${action}' as its ${key} reply action, but ${name} has no model entry '${action}', so those events are dropped`
2527
2632
  : `A ${sink} request names '${action}' as its ${key} reply action, but ${name} has no model entry '${action}', so the reply is dropped`) +
2528
- (near ? ` (did you mean '${near}'?)` : ''),
2529
- fix: near
2530
- ? `Use the existing entry: ${key.startsWith('events.') ? `events: { '${key.slice(7)}': '${near}' }` : `${key}: '${near}'`}`
2531
- : `Add '${action}' to ${name}.model, e.g. ${action}: (state, data) => ({ ...state, ... }), or fix the name`,
2532
- data: { action, key, sink, ...(connection ? { connection } : {}), ...(near ? { suggestion: near } : {}), modelActions },
2633
+ (up ? `: replies go only to the component that sent the request, never to ${up}, which handles '${action}'` : near ? ` (did you mean '${near}'?)` : ''),
2634
+ fix: up
2635
+ ? `Let ${up} send it: emit EVENTS: event('...') from ${name} and send the request from ${up}'s entry for it, so the reply comes back to ${up}. For data several components load, declare it once as a resource in ${up} (resources: { name: (state) => ({ url, tags: ['name'] }) }) and re-request with ${sink}: { invalidate: 'name' } from anywhere. Or handle the reply here: add '${action}' to ${name}.model`
2636
+ : near
2637
+ ? `Use the existing entry: ${key.startsWith('events.') ? `events: { '${key.slice(7)}': '${near}' }` : `${key}: '${near}'`}`
2638
+ : `Add '${action}' to ${name}.model, e.g. ${action}: (state, data) => ({ ...state, ... }), or fix the name`,
2639
+ data: { action, key, sink, ...(connection ? { connection } : {}), ...(up ? { handledBy: up } : {}), ...(near ? { suggestion: near } : {}), modelActions },
2533
2640
  });
2534
2641
  }
2535
2642
  // SYG508 (strict): a request without reply actions whose replies this instance reads back with select()/errors()
@@ -2808,8 +2915,15 @@ catch (_) {
2808
2915
  function timerSpecProblem(s) {
2809
2916
  if (!s || typeof s != 'object')
2810
2917
  return 'is not an object ({ every, action }, { after, action } or { frame: action })';
2811
- if ('frame' in s && s.frame)
2812
- return typeof s.frame == 'string' ? '' : 'frame must be the action name (a string)';
2918
+ if ('frame' in s && s.frame) {
2919
+ if (typeof s.frame != 'string')
2920
+ return 'frame must be the action name (a string)';
2921
+ if (s.step != null && !(s.step > 0))
2922
+ return `step must be a positive number of ms (got ${brief$2(s.step)})`;
2923
+ if (s.keys != null && typeof s.keys != 'object')
2924
+ return `keys must be an object of button: key codes (got ${brief$2(s.keys)})`;
2925
+ return '';
2926
+ }
2813
2927
  if (!s.action || typeof s.action != 'string')
2814
2928
  return 'has no action (a string)';
2815
2929
  if (s.every == null && s.after == null)
@@ -3282,6 +3396,9 @@ function installRxjsHints(proto) {
3282
3396
  * sends nothing.
3283
3397
  */
3284
3398
  const BUILT_IN = /^(BOOTSTRAP|INITIALIZE|DISPOSE|RESOURCE)$/;
3399
+ // a behavior's plumbing actions (sygnal/yjs shared(): 'board.LOCAL', 'board.REMOTE') mark their
3400
+ // handlers, so they are 'built-in' too (DevTools hides them with INITIALIZE)
3401
+ const MARK = Symbol.for('sygnal.builtIn');
3285
3402
  /** now on the test's fake clock when there is one (vi.useFakeTimers()), else Date.now() */
3286
3403
  const clockNow = () => {
3287
3404
  const c = setTimeout.clock;
@@ -3291,7 +3408,8 @@ const clockNow = () => {
3291
3408
  * PLAN-4.6 R4: the same log on the core, from its hooks (04 §3.3) instead of instance
3292
3409
  * patches: onAction opens the record (the core passes the cause: 'intent', 'next', 'reply',
3293
3410
  * 'built-in', 'simulateAction'), wrapHandler sees which sinks produced a value. Same records,
3294
- * same cause rules: a built-in type is 'built-in' unless simulated; an intent action a behavior
3411
+ * same cause rules: a built-in type (or one whose handlers carry Symbol.for('sygnal.builtIn')) is
3412
+ * 'built-in' unless simulated; an intent action a behavior
3295
3413
  * owns is 'behavior'. `only(inst)`: the instances to record (renderComponent: its own tree).
3296
3414
  */
3297
3415
  function actionHooks(listener, only) {
@@ -3313,7 +3431,8 @@ function actionHooks(listener, only) {
3313
3431
  addSink(listener, rec, ['STATE'], 'STATE', undefined);
3314
3432
  },
3315
3433
  onAction(inst, a) {
3316
- if (only && !only(inst))
3434
+ // 7.2: a runtime setState (time travel, HMR, element props) isn't an action of the log
3435
+ if ((only && !only(inst)) || a.cause == 'setState')
3317
3436
  return;
3318
3437
  const type = String(a.type), owned = inst.def.behaviorActions;
3319
3438
  const hs = inst.def.handlers.get(type) || [], order = hs.map((h) => h[0]);
@@ -3323,7 +3442,7 @@ function actionHooks(listener, only) {
3323
3442
  cur.set(inst, [opened, order]);
3324
3443
  return;
3325
3444
  }
3326
- const cause = a.cause == 'simulateAction' ? a.cause : BUILT_IN.test(type) ? 'built-in'
3445
+ const cause = a.cause == 'simulateAction' ? a.cause : BUILT_IN.test(type) || hs.some((h) => h[1]?.[MARK]) ? 'built-in'
3327
3446
  : a.cause == 'next' || a.cause == 'reply' || a.cause == 'agent' ? a.cause
3328
3447
  : owned && Object.prototype.hasOwnProperty.call(owned, type) ? 'behavior' : 'intent';
3329
3448
  const rec = { type, data: a.data, component: inst.name, instance: String(inst.id), sinks: [], cause, time: clockNow(), ...(a.source !== undefined && { source: a.source }) };
@@ -3875,7 +3994,9 @@ const audited = (f, type, sink, h) => (s, d, nx, p, ...r) => {
3875
3994
  * page after a patch, G-460: on rendered elements), SYG612 (D173: a form
3876
3995
  * 6.0 removed, met at runtime; once per form and component, with a link to the migration
3877
3996
  * guide), SYG453 (PLAN-9, D470: two children with one identity share one instance), SYG426
3878
- * (PLAN-9, D471: a lowercase <collection> / <switchable> tag, deprecated in 6.3.0).
3997
+ * (PLAN-9, D471: a lowercase <collection> / <switchable> tag, deprecated in 6.3.0), SYG117
3998
+ * (PLAN-13 N-1: next() to an action the component has no model entry for), SYG204 (sandbox
3999
+ * research §6: a root that has no state after it started, so its view never runs).
3879
4000
  *
3880
4001
  * Only while diagnostics are on (the checks' own rule); production builds never load this.
3881
4002
  */
@@ -4071,6 +4192,36 @@ function lowercaseTags(iv) {
4071
4192
  };
4072
4193
  walk(raw.tmpl, 0);
4073
4194
  }
4195
+ /**
4196
+ * PLAN-13 F-3 SYG429: a Switchable's per-page `state={{ page: … }}` with a key that names no page
4197
+ * (read from the owner's template, as SYG426; 0 production bytes). Once per component and key
4198
+ */
4199
+ function switchableMaps(iv) {
4200
+ const raw = iv.i;
4201
+ if (!raw?.kids?.size || !raw.tmpl)
4202
+ return;
4203
+ const walk = (n, d) => {
4204
+ if (!n || typeof n != 'object' || n.$p || d > 300)
4205
+ return;
4206
+ const p = n.sel == 'switchable' && n.data?.props, st = p?.state;
4207
+ if (st && isPlainObject(st) && typeof st.get != 'function' && isPlainObject(p.of)) {
4208
+ const pages = Object.keys(p.of);
4209
+ for (const k of Object.keys(st))
4210
+ if (!(k in p.of) && once(`SYG429:${iv.name}:${k}`))
4211
+ devReport('SYG429', {
4212
+ component: iv.name,
4213
+ message: `${iv.name}'s Switchable state map binds '${k}', which is not one of its pages (${pages.join(', ')}), so that binding is never used${didYouMean(k, pages)}`,
4214
+ fix: 'Use the page name from of={{ … }} as the key, or remove the entry',
4215
+ data: { key: k, pages },
4216
+ });
4217
+ }
4218
+ const kids = n.children;
4219
+ if (Array.isArray(kids))
4220
+ for (const c of kids)
4221
+ walk(c, d + 1);
4222
+ };
4223
+ walk(raw.tmpl, 0);
4224
+ }
4074
4225
  /** the instance fields the checks read from today's Component instances */
4075
4226
  class Facade {
4076
4227
  constructor(__next) {
@@ -4267,6 +4418,31 @@ function vtReport(dup) {
4267
4418
  }
4268
4419
  }
4269
4420
  /** The hooks one app gets (installChecks publishes this on the bridge). */
4421
+ /**
4422
+ * SYG204 (sandbox research §6): the core calls no view while its instance's state is undefined,
4423
+ * and a root gets state only from initialState (or the state an HMR swap keeps), a model-less root's `true`, or
4424
+ * a reducer. Checked once per app after the start settles: the first flush (INITIALIZE included)
4425
+ * and the flush BOOTSTRAP's reducers cause; a root still without state is reported (0 core bytes:
4426
+ * the runtime API's flushed(), no timers).
4427
+ */
4428
+ function statelessRoot(api, iv, on) {
4429
+ if (!api?.flushed || iv.state !== undefined)
4430
+ return;
4431
+ const settled = () => api.flushed();
4432
+ // the first flush, then BOOTSTRAP (a microtask after it) and the flush it schedules
4433
+ Promise.resolve().then(settled).then(() => new Promise(r => queueMicrotask(r))).then(settled).then(() => {
4434
+ if (iv.disposed || iv.state !== undefined || !on())
4435
+ return;
4436
+ const name = iv.name, h = iv.def.handlers;
4437
+ const later = [...h.keys()].filter((k) => k != 'INITIALIZE' && k != 'BOOTSTRAP' && h.get(k).some((x) => x[0] == 'STATE'));
4438
+ devReport('SYG204', {
4439
+ component: name,
4440
+ message: `${name} is the root component and has no state after it started (no initialState, and no INITIALIZE or BOOTSTRAP reducer set one), so its view is not called and the page stays empty${later.length ? ` until one of its reducers (${later.slice(0, 3).join(', ')}${later.length > 3 ? ', …' : ''}) sets the state` : ''}`,
4441
+ fix: `Give ${name} an initialState (${name}.initialState = {} for a component with no data of its own), or return the first state from an INITIALIZE reducer`,
4442
+ data: { reducers: later },
4443
+ });
4444
+ }).catch(() => { });
4445
+ }
4270
4446
  function nextHooks(_api) {
4271
4447
  const core = bridge(), H = core.hooks;
4272
4448
  const on = () => H.on();
@@ -4293,9 +4469,18 @@ function nextHooks(_api) {
4293
4469
  const vtCheck = vtChecker(() => vtp.size && on());
4294
4470
  // SYG428: the live instances by id, to read a child's parent state
4295
4471
  const live = new Map();
4472
+ // SYG117, SYG112: the nearest ancestor whose model has an entry `type`
4473
+ const handlerAbove = (iv, type) => {
4474
+ let up = live.get(iv.parentId);
4475
+ while (up && !up.def.handlers.has(type))
4476
+ up = live.get(up.parentId);
4477
+ return up;
4478
+ };
4296
4479
  return {
4297
4480
  onCreate(iv) {
4298
4481
  live.set(iv.id, iv);
4482
+ if (iv.isRoot)
4483
+ statelessRoot(_api, iv, on);
4299
4484
  if (!on())
4300
4485
  return;
4301
4486
  log.onCreate(iv);
@@ -4347,6 +4532,7 @@ function nextHooks(_api) {
4347
4532
  removedTags(f, vnode);
4348
4533
  try {
4349
4534
  lowercaseTags(iv);
4535
+ switchableMaps(iv);
4350
4536
  }
4351
4537
  catch (_) { /* a check never breaks the app */ }
4352
4538
  // SYG453: only a view with children can render one twice
@@ -4383,12 +4569,30 @@ function nextHooks(_api) {
4383
4569
  if (sink == 'EVENTS')
4384
4570
  eventEmitted(f, v);
4385
4571
  if (src && src.__sygnalReplies === true)
4386
- checkRequest(f, sink, v, Object.keys(f.modelMap));
4572
+ checkRequest(f, sink, v, Object.keys(f.modelMap), (a) => handlerAbove(iv, a)?.name);
4387
4573
  if (src && src.__sygnalStatic === 'resources')
4388
4574
  check(f, sink, v);
4389
4575
  }
4390
4576
  catch (_) { /* a check never breaks the app */ }
4391
4577
  },
4578
+ // SYG117 (PLAN-13 N-1): next('X') goes to the component's own model only; with no entry
4579
+ // for X it does nothing (an ancestor's entry of that name never runs)
4580
+ onNext(iv, type) {
4581
+ if (!on() || iv.def.handlers.has(type))
4582
+ return;
4583
+ const name = iv.name;
4584
+ if (!once(`SYG117:${name}:${type}`))
4585
+ return;
4586
+ const up = handlerAbove(iv, type);
4587
+ devReport('SYG117', {
4588
+ component: name,
4589
+ message: `${name} calls next('${type}'), but ${name} has no model entry '${type}', so the action does nothing${up ? `: next() never reaches ${up.name}, which handles '${type}'` : ''}${didYouMean(type, iv.def.handlers.keys())}`,
4590
+ fix: up
4591
+ ? `next() stays in its own component. Send it up instead: give the entry that calls it a PARENT sink (PARENT: (state) => ({ … })) and add ${type}: CHILD.select(${name}) to ${up.name}.intent, or emit EVENTS: event('${type}', …) and select it there`
4592
+ : `Add the entry to ${name}.model (${type}: (state, data) => …), or fix the name to match an existing one`,
4593
+ data: { action: type, ...(up ? { handledBy: up.name } : {}) },
4594
+ });
4595
+ },
4392
4596
  onElementCommand(iv, cmd) {
4393
4597
  if (!on())
4394
4598
  return;