@kitn.ai/ui 0.28.0 → 0.30.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 (615) hide show
  1. package/README.md +11 -7
  2. package/dist/{Icon-DObhsRtm.js → Icon-BzBET3E-.js} +1 -1
  3. package/dist/{action-icons-CcgNN2oT.js → action-icons-DaW2pU6m.js} +1 -1
  4. package/dist/agent-tooling/construct/public.d.ts +15 -0
  5. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  6. package/dist/arrow-down-D783iMJj.js +1 -0
  7. package/dist/arrow-left-0VLM5kyg.js +1 -0
  8. package/dist/artifact-DGjzHYUT.js +1 -0
  9. package/dist/{attachments-Qr4kg-2b.js → attachments-YvXHlg88.js} +1 -1
  10. package/dist/{audio-visualizer-DToYdfSc.js → audio-visualizer-9lz5IHsL.js} +1 -1
  11. package/dist/{badge-CdEu_jn2.js → badge-Dh_vp8A5.js} +1 -1
  12. package/dist/{bell-DQAAOpDz.js → bell-CT7_WUwS.js} +1 -1
  13. package/dist/{button-DkuOnGLy.js → button-C_3te6fm.js} +1 -1
  14. package/dist/{card-renderer-Jx70m6_l.js → card-renderer-DZVaGH2e.js} +1 -1
  15. package/dist/check-CiEOVnEN.js +1 -0
  16. package/dist/{checkbox-D7iRB3nL.js → checkbox-Bu2T2e0U.js} +1 -1
  17. package/dist/{checkbox-group-B2_rpoPM.js → checkbox-group-BiryEXPQ.js} +1 -1
  18. package/dist/chevron-down-CSobuZRG.js +1 -0
  19. package/dist/chevron-right-DR9ZAP-2.js +1 -0
  20. package/dist/{choice-card-m0BYqqOK.js → choice-card-CM6IjkmL.js} +1 -1
  21. package/dist/circle-BC66EYF7.js +1 -0
  22. package/dist/circle-check-BqmahW0O.js +1 -0
  23. package/dist/{circle-x-BPG0iPst.js → circle-x-Bk-dA5Wf.js} +1 -1
  24. package/dist/{code-block-ClMLKb7C.js → code-block-B26t-Q7q.js} +1 -1
  25. package/dist/{collapsible-B4DlWicL.js → collapsible-Bhc636Cf.js} +1 -1
  26. package/dist/components/builder-composer-triggers.d.ts +62 -0
  27. package/dist/components/builder-layout.d.ts +54 -0
  28. package/dist/components/builder-message-actions.d.ts +72 -0
  29. package/dist/components/builder-panel.d.ts +189 -0
  30. package/dist/components/builder-preview.d.ts +11 -0
  31. package/dist/components/builder-shell-controls.d.ts +69 -0
  32. package/dist/components/builder-skeleton.d.ts +58 -0
  33. package/dist/components/builder-start.d.ts +75 -0
  34. package/dist/components/builder-workspace-variants.d.ts +23 -0
  35. package/dist/components/captions.d.ts +66 -0
  36. package/dist/{composer-sQ0HcvyG.js → composer-DMI85B6j.js} +1 -1
  37. package/dist/{confirm-card-rQWDQsnC.js → confirm-card-B0YIAwXp.js} +1 -1
  38. package/dist/construct-cli.es.js +3 -3
  39. package/dist/construct.d.ts +5 -0
  40. package/dist/construct.js +327 -0
  41. package/dist/{context-0kg4ZX1J.js → context-DWgfhqWp.js} +1 -1
  42. package/dist/controllable-BagRpyHv.js +1 -0
  43. package/dist/{conversation-item-BxwQLaFJ.js → conversation-item-BsOsVGmT.js} +1 -1
  44. package/dist/{conversation-list-BCfGN1JU.js → conversation-list-aX3Vtc7L.js} +1 -1
  45. package/dist/{copy-n9u9rqpi.js → copy-BmEwsNhJ.js} +1 -1
  46. package/dist/core-C8fzo39E.js +1 -12
  47. package/dist/{create-tween-uPkArjIG.js → create-tween-CNOAU3Fx.js} +1 -1
  48. package/dist/{create-tween-C-q-JCM-.js → create-tween-WfqiENls.js} +1 -1
  49. package/dist/{create-tween-CRrJXxBd.js → create-tween-wlFtW9Kh.js} +1 -1
  50. package/dist/{create-tween-DZ3RRDHD.js → create-tween-zt79ba32.js} +1 -1
  51. package/dist/{default-input-CaMrJkgH.js → default-input-DMV5Eaze.js} +1 -1
  52. package/dist/define-CI0y3eE4.js +1 -0
  53. package/dist/define.js +40 -40
  54. package/dist/define.server.js +45 -45
  55. package/dist/{disclosure-WUImBbDv.js → disclosure-NnACBxsC.js} +1 -1
  56. package/dist/{dropdown-wZVeJOMd.js → dropdown-DT7G0RGR.js} +1 -1
  57. package/dist/elements/agent-card.js +1 -1
  58. package/dist/elements/artifact.js +1 -1
  59. package/dist/elements/attachments.js +1 -1
  60. package/dist/elements/audio-visualizer.js +1 -1
  61. package/dist/elements/avatar.js +1 -1
  62. package/dist/elements/badge.js +1 -1
  63. package/dist/elements/button.js +1 -1
  64. package/dist/elements/card.js +1 -1
  65. package/dist/elements/cards.js +1 -1
  66. package/dist/elements/chain-of-thought.js +1 -1
  67. package/dist/elements/chat-scope-picker.js +1 -1
  68. package/dist/elements/chat-workspace.js +1 -1
  69. package/dist/elements/chat.js +1 -1
  70. package/dist/elements/checkbox-group.js +1 -1
  71. package/dist/elements/checkbox.js +1 -1
  72. package/dist/elements/checkpoint.js +1 -1
  73. package/dist/elements/choice.js +1 -1
  74. package/dist/elements/coachmark.js +1 -1
  75. package/dist/elements/code-block.js +1 -1
  76. package/dist/elements/command.js +1 -1
  77. package/dist/elements/compare.js +1 -1
  78. package/dist/elements/composer.js +1 -1
  79. package/dist/elements/confirm-card.js +1 -1
  80. package/dist/elements/context-meter.js +1 -1
  81. package/dist/elements/conversation-item.js +1 -1
  82. package/dist/elements/conversation-list.js +1 -1
  83. package/dist/elements/dialog.js +1 -1
  84. package/dist/elements/dock.js +1 -1
  85. package/dist/elements/dropdown.js +1 -1
  86. package/dist/elements/editable-label.js +1 -1
  87. package/dist/elements/embed.js +1 -1
  88. package/dist/elements/empty.js +1 -1
  89. package/dist/elements/feedback-bar.js +1 -1
  90. package/dist/elements/file-tree.js +1 -1
  91. package/dist/elements/file-upload.js +1 -1
  92. package/dist/elements/form.js +1 -1
  93. package/dist/elements/hover-card.js +1 -1
  94. package/dist/elements/icon.js +1 -1
  95. package/dist/elements/image.js +1 -1
  96. package/dist/elements/input.js +1 -1
  97. package/dist/elements/kbd.js +1 -1
  98. package/dist/elements/link-preview.js +1 -1
  99. package/dist/elements/loader.js +1 -1
  100. package/dist/elements/markdown.js +1 -1
  101. package/dist/elements/menu.js +1 -1
  102. package/dist/elements/message-skills.js +1 -1
  103. package/dist/elements/message.js +1 -1
  104. package/dist/elements/model-switcher.js +1 -1
  105. package/dist/elements/nav.js +1 -1
  106. package/dist/elements/notice.js +1 -1
  107. package/dist/elements/pane-grid.js +1 -1
  108. package/dist/elements/pane-group.js +1 -1
  109. package/dist/elements/pane.js +1 -1
  110. package/dist/elements/popover.js +1 -1
  111. package/dist/elements/progress-bar.js +1 -1
  112. package/dist/elements/prompt-dock.js +1 -1
  113. package/dist/elements/prompt-input.js +1 -1
  114. package/dist/elements/prompt-suggestions.js +1 -1
  115. package/dist/elements/radio-group.js +1 -1
  116. package/dist/elements/reasoning.js +1 -1
  117. package/dist/elements/remote.js +1 -1
  118. package/dist/elements/resizable.js +1 -1
  119. package/dist/elements/response-stream.js +1 -1
  120. package/dist/elements/screen.js +1 -1
  121. package/dist/elements/scroll-area.js +1 -1
  122. package/dist/elements/scroll-button.js +1 -1
  123. package/dist/elements/search.js +1 -1
  124. package/dist/elements/segmented.js +1 -1
  125. package/dist/elements/select.js +1 -1
  126. package/dist/elements/separator.js +1 -1
  127. package/dist/elements/setting-item.js +1 -1
  128. package/dist/elements/settings-group.js +1 -1
  129. package/dist/elements/skeleton.js +1 -1
  130. package/dist/elements/slider.js +1 -1
  131. package/dist/elements/source.js +1 -1
  132. package/dist/elements/status.js +1 -1
  133. package/dist/elements/switch.js +1 -1
  134. package/dist/elements/tabs.js +1 -1
  135. package/dist/elements/tasks.js +1 -1
  136. package/dist/elements/text-shimmer.js +1 -1
  137. package/dist/elements/thinking-bar.js +1 -1
  138. package/dist/elements/thread.js +1 -1
  139. package/dist/elements/toast.js +1 -1
  140. package/dist/elements/tool.js +1 -1
  141. package/dist/elements/tooltip.js +1 -1
  142. package/dist/elements/voice-input.js +1 -1
  143. package/dist/elements/voice-output.js +1 -1
  144. package/dist/{ellipsis-CbOpJnU3.js → ellipsis-6pJ7SxYT.js} +1 -1
  145. package/dist/{embed-CrfjkbUG.js → embed-BMje-nxj.js} +1 -1
  146. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  147. package/dist/{external-link-ByXAwBBQ.js → external-link-Beh9DKeR.js} +1 -1
  148. package/dist/{file-text-DMib2qwt.js → file-text-D1GPfZ8Q.js} +1 -1
  149. package/dist/{file-tree-CJqM2aP7.js → file-tree-Dwj2lqg-.js} +1 -1
  150. package/dist/{folder-_BAb929C.js → folder-b6D8x1DY.js} +1 -1
  151. package/dist/{form-DSZgZQZk.js → form-DWgdxDXu.js} +1 -1
  152. package/dist/{hover-card-BQbul9eq.js → hover-card-CWRkqm40.js} +1 -1
  153. package/dist/{icon-CdcfRhYI.js → icon-CL0jdhsf.js} +1 -1
  154. package/dist/index.js +1741 -1725
  155. package/dist/index.server.js +1582 -1566
  156. package/dist/{info-DsFjGuI_.js → info-0cOUherQ.js} +1 -1
  157. package/dist/{input-DKbmkMos.js → input-BH6Ru91Q.js} +1 -1
  158. package/dist/kai.es.js +1 -1
  159. package/dist/{kbd-PvdGhX4s.js → kbd-DRAnVtNX.js} +1 -1
  160. package/dist/{link-zF8PzWfF.js → link-Dlr0vssB.js} +1 -1
  161. package/dist/{link-preview-DbM1866N.js → link-preview-DODgRyV9.js} +1 -1
  162. package/dist/loader-KvJB6NrT.js +1 -0
  163. package/dist/{markdown-LMvmAyBk.js → markdown-DX9tuJVj.js} +1 -1
  164. package/dist/mcp.es.js +9 -5
  165. package/dist/{message-BYZECyek.js → message-BqQ7Ot5q.js} +1 -1
  166. package/dist/{message-BSsgvHvf.js → message-DkaGfBd8.js} +1 -1
  167. package/dist/message-circle-BPpp5QKv.js +1 -0
  168. package/dist/message-square-BLbsd9BJ.js +1 -0
  169. package/dist/{minimize-2-BQRoBV1V.js → minimize-2-CyOHJwcN.js} +1 -1
  170. package/dist/{model-switcher-CMpCOYcB.js → model-switcher-0hrKDiK2.js} +1 -1
  171. package/dist/{overlay-Cb-MkyDa.js → overlay-DBMFXie6.js} +1 -1
  172. package/dist/{panel-right-i8warFxT.js → panel-right-t8SWJL6e.js} +1 -1
  173. package/dist/{paperclip-ByY3UVO6.js → paperclip-CdzuOjbU.js} +1 -1
  174. package/dist/play-BXy1u7rW.js +1 -0
  175. package/dist/primitives/use-auto-resize.d.ts +11 -0
  176. package/dist/{progress-bar-jVAHvMUf.js → progress-bar-CiBtxMWT.js} +1 -1
  177. package/dist/{prompt-suggestion-CD-qtNgC.js → prompt-suggestion-I_DHP21i.js} +1 -1
  178. package/dist/{radio-BsoBMhkF.js → radio-C_p3n39l.js} +1 -1
  179. package/dist/{reasoning-4kBPtlTx.js → reasoning-BDwy_tIe.js} +1 -1
  180. package/dist/register-impl-DvEnhIxx.js +424 -0
  181. package/dist/{resizable-uPmY9VY-.js → resizable-SYC-fStM.js} +1 -1
  182. package/dist/{rotate-ccw-B2xn93py.js → rotate-ccw-B1nwmwuY.js} +1 -1
  183. package/dist/{rotate-cw-CMVLoCyH.js → rotate-cw-CLAnBcm1.js} +1 -1
  184. package/dist/{scroll-area-BqLUo8ZZ.js → scroll-area-zRyXoLhI.js} +1 -1
  185. package/dist/{scroll-button-DslXiJyL.js → scroll-button-jzrbYCG9.js} +1 -1
  186. package/dist/{select-2cLRtPGT.js → select-DM-0nEe0.js} +1 -1
  187. package/dist/{separator-BWQtNJcM.js → separator-BToOOoOt.js} +1 -1
  188. package/dist/{settings-BBR63jLg.js → settings-PaGo_S4U.js} +1 -1
  189. package/dist/{settings-group-B2ciy-Iw.js → settings-group-BFXGIfXq.js} +1 -1
  190. package/dist/{skeleton-CkHI-HgC.js → skeleton-DYpmFnnm.js} +1 -1
  191. package/dist/{slider-DDR9U-i0.js → slider-ebutAmkQ.js} +1 -1
  192. package/dist/{solid-DruVOXnf.js → solid-15v_3WVL.js} +3563 -3412
  193. package/dist/{solid-BzCFdL25.js → solid-5P-otgdi.js} +4502 -4296
  194. package/dist/solid.d.ts +6 -0
  195. package/dist/solid.js +259 -254
  196. package/dist/solid.server.js +259 -254
  197. package/dist/{source-GEnFF528.js → source-c1gpRl9h.js} +1 -1
  198. package/dist/{star-CilHq8GP.js → star-pcV_3LqF.js} +1 -1
  199. package/dist/{store-Ciyk6eZG.js → store-CSgbFFCg.js} +1 -1
  200. package/dist/{switch-KkeQIbuH.js → switch-Bh2OdLV2.js} +1 -1
  201. package/dist/{tasks-card-BrN4bggJ.js → tasks-card-CbqQAn2S.js} +1 -1
  202. package/dist/text-shimmer-BSzgEnDH.js +1 -0
  203. package/dist/textarea-DrYNt_0z.js +1 -0
  204. package/dist/theme.tokens.css +22 -21
  205. package/dist/{thumbs-up-CsT9Wzb9.js → thumbs-up-BL3-NenK.js} +1 -1
  206. package/dist/{toast-store-tgK6MuMv.js → toast-store-DkOlYczt.js} +1 -1
  207. package/dist/{tool-DzUjIBT8.js → tool-CeIWUVUe.js} +1 -1
  208. package/dist/{tooltip-Bkkg_4t0.js → tooltip-GA-nmg06.js} +1 -1
  209. package/dist/{trash-2-jBKxjGGQ.js → trash-2-CT66QWlm.js} +1 -1
  210. package/dist/{triangle-alert-DlQ68ppf.js → triangle-alert-CeHLWxhD.js} +1 -1
  211. package/dist/ui/color-field.d.ts +48 -0
  212. package/dist/ui/textarea.d.ts +4 -0
  213. package/dist/ui/toggle-chip.d.ts +40 -0
  214. package/dist/{upload-TcAHXv9W.js → upload-9Csb0F0J.js} +1 -1
  215. package/dist/{use-card-resolution-DaZqAdWf.js → use-card-resolution-Zg0a08Z-.js} +1 -1
  216. package/dist/{variant-aurora-DbW_OWL2.js → variant-aurora-4VBQHIKV.js} +1 -1
  217. package/dist/{variant-aurora-CNmfHhcx.js → variant-aurora-DBeCAQNk.js} +2 -2
  218. package/dist/{variant-aurora-D6GsdTPA.js → variant-aurora-FzHVXHby.js} +2 -2
  219. package/dist/{variant-aurora-BhLuYxJR.js → variant-aurora-LE3fqxgw.js} +1 -1
  220. package/dist/{variant-custom-BvYHR62b.js → variant-custom-AFRGcOBS.js} +1 -1
  221. package/dist/{variant-custom-DV2x254s.js → variant-custom-BQkNuPpE.js} +2 -2
  222. package/dist/{variant-custom-BRvaNHSi.js → variant-custom-DI5R0rln.js} +1 -1
  223. package/dist/{variant-custom-DMTrEO5w.js → variant-custom-WWnrrRHS.js} +2 -2
  224. package/dist/{variant-wave-CyBueXj9.js → variant-wave-DUCbH5oI.js} +2 -2
  225. package/dist/{variant-wave-BNMGrDfj.js → variant-wave-DuQV7DhQ.js} +2 -2
  226. package/dist/{variant-wave-BpDd8t3Z.js → variant-wave-nSI2sfII.js} +1 -1
  227. package/dist/{variant-wave-sp-qwsdI.js → variant-wave-wTP5v_jU.js} +1 -1
  228. package/dist/x-eiO9V6J6.js +1 -0
  229. package/{src/elements/styles.css → kit-base.css} +27 -44
  230. package/package.json +32 -17
  231. package/solid.css +33 -0
  232. package/theme.css +39 -23
  233. package/bin/route.test.js +0 -58
  234. package/dist/arrow-down-CjRU3JgV.js +0 -1
  235. package/dist/arrow-left-Bnpb1UEW.js +0 -1
  236. package/dist/artifact-Bwt7OgcR.js +0 -1
  237. package/dist/check-njkVdZu2.js +0 -1
  238. package/dist/chevron-down-B72s6PXR.js +0 -1
  239. package/dist/chevron-right-BatuHzUm.js +0 -1
  240. package/dist/circle-check-B62pODB3.js +0 -1
  241. package/dist/circle-zx0ByzY9.js +0 -1
  242. package/dist/controllable-CnGpMF6r.js +0 -1
  243. package/dist/define-By-uL588.js +0 -1
  244. package/dist/loader-BM4CEz4G.js +0 -1
  245. package/dist/message-circle-DruwObfX.js +0 -1
  246. package/dist/message-square-BG6q6mlm.js +0 -1
  247. package/dist/play-BvKKq3fX.js +0 -1
  248. package/dist/register-impl-B1hLkrI8.js +0 -424
  249. package/dist/text-shimmer-Dcj1MHuq.js +0 -1
  250. package/dist/textarea-Dpvbngbg.js +0 -1
  251. package/dist/x-Z5UntkIJ.js +0 -1
  252. package/src/agent-tooling/README.md +0 -192
  253. package/src/agent-tooling/archetypes.ts +0 -205
  254. package/src/agent-tooling/catalog/README.md +0 -676
  255. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  256. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  257. package/src/agent-tooling/catalog/invariants.ts +0 -284
  258. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  259. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  260. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  261. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  262. package/src/agent-tooling/construct/cli.ts +0 -147
  263. package/src/agent-tooling/construct/codegen.ts +0 -1635
  264. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  265. package/src/agent-tooling/construct/dev.ts +0 -148
  266. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  267. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  268. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  269. package/src/agent-tooling/construct/schema.ts +0 -452
  270. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  271. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  272. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  273. package/src/agent-tooling/integrations/mastra.ts +0 -182
  274. package/src/agent-tooling/integrations/mock.ts +0 -148
  275. package/src/agent-tooling/integrations/ollama.ts +0 -97
  276. package/src/agent-tooling/integrations/openai.ts +0 -95
  277. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  278. package/src/agent-tooling/integrations/pi.ts +0 -176
  279. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  280. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  281. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  282. package/src/agent-tooling/mcp/manifest.ts +0 -422
  283. package/src/agent-tooling/mcp/server.ts +0 -116
  284. package/src/agent-tooling/mcp/stdio.ts +0 -18
  285. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  286. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  287. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  288. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  289. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  290. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  291. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  292. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  293. package/src/agent-tooling/recipes/index.ts +0 -22
  294. package/src/agent-tooling/recipes/types.ts +0 -32
  295. package/src/agent-tooling/registry.ts +0 -132
  296. package/src/agent-tooling/route-emit.ts +0 -305
  297. package/src/agent-tooling/types.ts +0 -390
  298. package/src/components/artifact-card.tsx +0 -120
  299. package/src/components/artifact.tsx +0 -914
  300. package/src/components/attachment-types.ts +0 -66
  301. package/src/components/attachments.tsx +0 -536
  302. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  303. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  304. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  305. package/src/components/audio-visualizer/index.tsx +0 -528
  306. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  307. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  308. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  309. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  310. package/src/components/audio-visualizer/sizes.ts +0 -54
  311. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  312. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  313. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  314. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  315. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  316. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  317. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  318. package/src/components/card-fallback.tsx +0 -60
  319. package/src/components/card-renderer.tsx +0 -220
  320. package/src/components/card.tsx +0 -212
  321. package/src/components/chain-of-thought.tsx +0 -348
  322. package/src/components/chat-container.tsx +0 -89
  323. package/src/components/chat-scope-picker.tsx +0 -57
  324. package/src/components/chat-thread.tsx +0 -1014
  325. package/src/components/checkpoint.tsx +0 -88
  326. package/src/components/choice-card.tsx +0 -743
  327. package/src/components/coachmark.tsx +0 -249
  328. package/src/components/code-block.tsx +0 -181
  329. package/src/components/composer-dom.ts +0 -159
  330. package/src/components/composer-highlight.ts +0 -242
  331. package/src/components/composer-history.ts +0 -82
  332. package/src/components/composer.tsx +0 -1075
  333. package/src/components/confirm-card.tsx +0 -402
  334. package/src/components/context.tsx +0 -377
  335. package/src/components/conversation-item.tsx +0 -241
  336. package/src/components/conversation-list.tsx +0 -429
  337. package/src/components/conversation-panel.tsx +0 -118
  338. package/src/components/dismissed-stub.tsx +0 -80
  339. package/src/components/embed.tsx +0 -200
  340. package/src/components/empty.tsx +0 -177
  341. package/src/components/feedback-bar.tsx +0 -166
  342. package/src/components/file-tree.tsx +0 -530
  343. package/src/components/file-upload.tsx +0 -167
  344. package/src/components/form-widgets.tsx +0 -525
  345. package/src/components/form.tsx +0 -1296
  346. package/src/components/home-panel.tsx +0 -159
  347. package/src/components/image.tsx +0 -65
  348. package/src/components/link-preview.tsx +0 -195
  349. package/src/components/loader.tsx +0 -367
  350. package/src/components/markdown.tsx +0 -162
  351. package/src/components/message-skills.tsx +0 -40
  352. package/src/components/message.tsx +0 -765
  353. package/src/components/model-switcher.tsx +0 -132
  354. package/src/components/prompt-input.tsx +0 -188
  355. package/src/components/prompt-suggestion.tsx +0 -150
  356. package/src/components/reasoning.tsx +0 -293
  357. package/src/components/response-compare-types.ts +0 -152
  358. package/src/components/response-compare.tsx +0 -455
  359. package/src/components/response-stream.tsx +0 -109
  360. package/src/components/screen.tsx +0 -181
  361. package/src/components/scroll-button.tsx +0 -93
  362. package/src/components/source.tsx +0 -192
  363. package/src/components/tasks-card.tsx +0 -801
  364. package/src/components/text-shimmer.tsx +0 -37
  365. package/src/components/thinking-bar.tsx +0 -50
  366. package/src/components/thread.tsx +0 -228
  367. package/src/components/toast.tsx +0 -503
  368. package/src/components/tool-classify.ts +0 -26
  369. package/src/components/tool-types.ts +0 -49
  370. package/src/components/tool.tsx +0 -190
  371. package/src/components/use-card-resolution.ts +0 -44
  372. package/src/components/voice-input.tsx +0 -222
  373. package/src/components/voice-output.tsx +0 -242
  374. package/src/components/widget-tab-bar.tsx +0 -73
  375. package/src/components/workspace-shell.tsx +0 -285
  376. package/src/diagnostics/hook.ts +0 -352
  377. package/src/diagnostics/index.ts +0 -82
  378. package/src/diagnostics/report-request.ts +0 -296
  379. package/src/elements/agent-card.tsx +0 -67
  380. package/src/elements/artifact.tsx +0 -194
  381. package/src/elements/attachments.tsx +0 -140
  382. package/src/elements/audio-visualizer.tsx +0 -227
  383. package/src/elements/autoloader.ts +0 -89
  384. package/src/elements/avatar.tsx +0 -33
  385. package/src/elements/badge.tsx +0 -32
  386. package/src/elements/button.tsx +0 -149
  387. package/src/elements/card.tsx +0 -133
  388. package/src/elements/cards.tsx +0 -266
  389. package/src/elements/chain-of-thought.tsx +0 -141
  390. package/src/elements/chat-scope-picker.tsx +0 -65
  391. package/src/elements/chat-types.ts +0 -102
  392. package/src/elements/chat-workspace.tsx +0 -188
  393. package/src/elements/chat.tsx +0 -304
  394. package/src/elements/checkbox-group.tsx +0 -164
  395. package/src/elements/checkbox.tsx +0 -134
  396. package/src/elements/checkpoint.tsx +0 -47
  397. package/src/elements/choice.tsx +0 -100
  398. package/src/elements/coachmark.tsx +0 -90
  399. package/src/elements/code-block.tsx +0 -60
  400. package/src/elements/command.tsx +0 -197
  401. package/src/elements/compare.tsx +0 -111
  402. package/src/elements/compiled.css +0 -2
  403. package/src/elements/composer.tsx +0 -162
  404. package/src/elements/confirm-card.tsx +0 -76
  405. package/src/elements/context-meter.tsx +0 -107
  406. package/src/elements/conversation-item.tsx +0 -120
  407. package/src/elements/conversation-list.tsx +0 -203
  408. package/src/elements/css.ts +0 -5
  409. package/src/elements/default-input.tsx +0 -392
  410. package/src/elements/define-entry.ts +0 -12
  411. package/src/elements/define.tsx +0 -563
  412. package/src/elements/diagnostic-events.ts +0 -114
  413. package/src/elements/dialog.tsx +0 -145
  414. package/src/elements/disclosure.ts +0 -95
  415. package/src/elements/dock.tsx +0 -215
  416. package/src/elements/dropdown.tsx +0 -123
  417. package/src/elements/editable-label.tsx +0 -140
  418. package/src/elements/element-data-types.ts +0 -152
  419. package/src/elements/element-diagnostics.ts +0 -392
  420. package/src/elements/element-manifest.json +0 -354
  421. package/src/elements/element-nonscalar.json +0 -187
  422. package/src/elements/element-types.d.ts +0 -4353
  423. package/src/elements/embed.tsx +0 -35
  424. package/src/elements/empty.tsx +0 -29
  425. package/src/elements/feedback-bar.tsx +0 -66
  426. package/src/elements/file-tree.tsx +0 -62
  427. package/src/elements/file-upload.tsx +0 -44
  428. package/src/elements/form.tsx +0 -94
  429. package/src/elements/hover-card.tsx +0 -80
  430. package/src/elements/icon.tsx +0 -41
  431. package/src/elements/image.tsx +0 -32
  432. package/src/elements/input.tsx +0 -492
  433. package/src/elements/kbd.tsx +0 -45
  434. package/src/elements/link-preview.tsx +0 -34
  435. package/src/elements/loader.tsx +0 -25
  436. package/src/elements/markdown.tsx +0 -38
  437. package/src/elements/menu.tsx +0 -241
  438. package/src/elements/message-skills.tsx +0 -83
  439. package/src/elements/message.tsx +0 -361
  440. package/src/elements/model-switcher.tsx +0 -121
  441. package/src/elements/nav.tsx +0 -87
  442. package/src/elements/notice.tsx +0 -53
  443. package/src/elements/pane-grid.tsx +0 -111
  444. package/src/elements/pane-group.tsx +0 -119
  445. package/src/elements/pane.tsx +0 -129
  446. package/src/elements/popover.tsx +0 -81
  447. package/src/elements/progress-bar.tsx +0 -39
  448. package/src/elements/prompt-dock.tsx +0 -82
  449. package/src/elements/prompt-input.tsx +0 -240
  450. package/src/elements/prompt-suggestions.tsx +0 -113
  451. package/src/elements/radio-group.tsx +0 -129
  452. package/src/elements/reasoning.tsx +0 -71
  453. package/src/elements/register-impl.ts +0 -148
  454. package/src/elements/register.ts +0 -76
  455. package/src/elements/remote.tsx +0 -225
  456. package/src/elements/resizable.globals.d.ts +0 -36
  457. package/src/elements/resizable.tsx +0 -704
  458. package/src/elements/response-stream.tsx +0 -40
  459. package/src/elements/screen.tsx +0 -90
  460. package/src/elements/scroll-area.tsx +0 -33
  461. package/src/elements/scroll-button.tsx +0 -179
  462. package/src/elements/search.tsx +0 -185
  463. package/src/elements/segmented.tsx +0 -129
  464. package/src/elements/select.tsx +0 -165
  465. package/src/elements/separator.tsx +0 -36
  466. package/src/elements/setting-item.tsx +0 -50
  467. package/src/elements/settings-group.tsx +0 -37
  468. package/src/elements/skeleton.tsx +0 -45
  469. package/src/elements/slider.tsx +0 -171
  470. package/src/elements/slot-text.ts +0 -73
  471. package/src/elements/slots.ts +0 -787
  472. package/src/elements/source.tsx +0 -126
  473. package/src/elements/status.tsx +0 -42
  474. package/src/elements/switch.tsx +0 -118
  475. package/src/elements/tabs.tsx +0 -95
  476. package/src/elements/tasks.tsx +0 -103
  477. package/src/elements/text-shimmer.tsx +0 -28
  478. package/src/elements/thinking-bar.tsx +0 -43
  479. package/src/elements/thread.tsx +0 -139
  480. package/src/elements/toast.tsx +0 -106
  481. package/src/elements/tool.tsx +0 -78
  482. package/src/elements/tooltip.tsx +0 -85
  483. package/src/elements/validate-messages.ts +0 -76
  484. package/src/elements/voice-input.tsx +0 -100
  485. package/src/elements/voice-output.tsx +0 -82
  486. package/src/index.ts +0 -430
  487. package/src/primitives/audio-bands.ts +0 -174
  488. package/src/primitives/card-component-types.ts +0 -63
  489. package/src/primitives/card-contract.ts +0 -80
  490. package/src/primitives/card-data-types.ts +0 -361
  491. package/src/primitives/card-host.tsx +0 -37
  492. package/src/primitives/card-recovery.ts +0 -160
  493. package/src/primitives/card-registry.tsx +0 -78
  494. package/src/primitives/card-resolution.ts +0 -36
  495. package/src/primitives/card-routing.ts +0 -85
  496. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  497. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  498. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  499. package/src/primitives/card-schemas/choice.schema.json +0 -75
  500. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  501. package/src/primitives/card-schemas/embed.schema.json +0 -63
  502. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  503. package/src/primitives/card-schemas/form.schema.json +0 -53
  504. package/src/primitives/card-schemas/link.schema.json +0 -54
  505. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  506. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  507. package/src/primitives/card-tags.ts +0 -61
  508. package/src/primitives/card-validate-cards.ts +0 -255
  509. package/src/primitives/card-validate-schemas.ts +0 -58
  510. package/src/primitives/card-validate.ts +0 -131
  511. package/src/primitives/chat-config.tsx +0 -103
  512. package/src/primitives/composer-model.ts +0 -49
  513. package/src/primitives/composer-triggers.ts +0 -35
  514. package/src/primitives/controllable.ts +0 -21
  515. package/src/primitives/conversation-store.ts +0 -248
  516. package/src/primitives/create-kai-chat.ts +0 -66
  517. package/src/primitives/create-tween.ts +0 -266
  518. package/src/primitives/embed-providers.ts +0 -254
  519. package/src/primitives/field-mask.ts +0 -256
  520. package/src/primitives/field-semantics.ts +0 -115
  521. package/src/primitives/highlighter.ts +0 -157
  522. package/src/primitives/input-mask.ts +0 -853
  523. package/src/primitives/link-preview.ts +0 -87
  524. package/src/primitives/message-feedback.ts +0 -124
  525. package/src/primitives/pdf-preview.ts +0 -121
  526. package/src/primitives/toast-store.ts +0 -338
  527. package/src/primitives/url-scheme-policy.ts +0 -70
  528. package/src/primitives/use-audio-analysis.ts +0 -340
  529. package/src/primitives/use-auto-resize.ts +0 -31
  530. package/src/primitives/use-resize-observer.ts +0 -45
  531. package/src/primitives/use-sequencer.ts +0 -72
  532. package/src/primitives/use-speech-recognition.ts +0 -146
  533. package/src/primitives/use-stick-to-bottom.ts +0 -75
  534. package/src/primitives/use-text-stream.ts +0 -143
  535. package/src/primitives/use-voice-recorder.ts +0 -62
  536. package/src/primitives/visualizer-sequences.ts +0 -350
  537. package/src/remote/host-embed.ts +0 -345
  538. package/src/remote/index.ts +0 -2
  539. package/src/remote/origin.ts +0 -30
  540. package/src/remote/provider-runtime.ts +0 -262
  541. package/src/remote/provider.ts +0 -2
  542. package/src/remote/validate.ts +0 -22
  543. package/src/remote/version.ts +0 -12
  544. package/src/remote/wire.ts +0 -48
  545. package/src/schemas/from-tool-call.ts +0 -151
  546. package/src/schemas/index.ts +0 -341
  547. package/src/schemas/provider-subsets.ts +0 -538
  548. package/src/schemas/registry.ts +0 -355
  549. package/src/schemas/tool-defs.ts +0 -793
  550. package/src/solid.ts +0 -173
  551. package/src/state/index.ts +0 -44
  552. package/src/state/messages.ts +0 -53
  553. package/src/state/mock.ts +0 -297
  554. package/src/state/parts.ts +0 -295
  555. package/src/state/persistence.ts +0 -180
  556. package/src/state/stream.ts +0 -246
  557. package/src/state/suggestions.ts +0 -9
  558. package/src/state/threads.ts +0 -142
  559. package/src/test-utils/fake-clock.ts +0 -88
  560. package/src/types.ts +0 -95
  561. package/src/ui/action-icons.ts +0 -53
  562. package/src/ui/agent-card.tsx +0 -218
  563. package/src/ui/avatar.tsx +0 -23
  564. package/src/ui/badge.tsx +0 -21
  565. package/src/ui/button.tsx +0 -49
  566. package/src/ui/card.tsx +0 -245
  567. package/src/ui/checkbox-group.tsx +0 -153
  568. package/src/ui/checkbox.tsx +0 -63
  569. package/src/ui/collapsible.tsx +0 -163
  570. package/src/ui/command.tsx +0 -103
  571. package/src/ui/dialog.tsx +0 -223
  572. package/src/ui/dock.tsx +0 -630
  573. package/src/ui/dropdown.tsx +0 -636
  574. package/src/ui/editable-label.tsx +0 -112
  575. package/src/ui/hover-card.tsx +0 -345
  576. package/src/ui/icon.tsx +0 -167
  577. package/src/ui/input.tsx +0 -391
  578. package/src/ui/kbd.tsx +0 -118
  579. package/src/ui/nav.tsx +0 -282
  580. package/src/ui/notice.tsx +0 -84
  581. package/src/ui/overlay.tsx +0 -277
  582. package/src/ui/pane-grid.tsx +0 -117
  583. package/src/ui/pane-group.tsx +0 -291
  584. package/src/ui/pane.tsx +0 -193
  585. package/src/ui/popover.tsx +0 -106
  586. package/src/ui/progress-bar.tsx +0 -83
  587. package/src/ui/prompt-dock.tsx +0 -145
  588. package/src/ui/radio.tsx +0 -150
  589. package/src/ui/resizable.tsx +0 -693
  590. package/src/ui/scroll-area.tsx +0 -26
  591. package/src/ui/segmented.tsx +0 -109
  592. package/src/ui/select.tsx +0 -168
  593. package/src/ui/separator.tsx +0 -10
  594. package/src/ui/settings-group.tsx +0 -67
  595. package/src/ui/skeleton.tsx +0 -73
  596. package/src/ui/slider.tsx +0 -178
  597. package/src/ui/status.tsx +0 -48
  598. package/src/ui/switch.tsx +0 -119
  599. package/src/ui/tabs.tsx +0 -168
  600. package/src/ui/textarea.tsx +0 -21
  601. package/src/ui/tooltip.tsx +0 -118
  602. package/src/utils/cn.ts +0 -30
  603. package/src/wire/chunk.ts +0 -248
  604. package/src/wire/consume.ts +0 -599
  605. package/src/wire/diagnostics.ts +0 -727
  606. package/src/wire/encode-probe.ts +0 -214
  607. package/src/wire/encode.ts +0 -947
  608. package/src/wire/files.ts +0 -342
  609. package/src/wire/formats/anthropic.ts +0 -274
  610. package/src/wire/formats/openai.ts +0 -228
  611. package/src/wire/index.ts +0 -95
  612. package/src/wire/media-types.ts +0 -344
  613. package/src/wire/read.ts +0 -396
  614. package/src/wire/sink-helpers.ts +0 -50
  615. package/src/wire/sse.ts +0 -153
@@ -1,876 +0,0 @@
1
- import { createEffect, createMemo, onCleanup, untrack, type JSX } from 'solid-js';
2
- import { cn } from '../../utils/cn';
3
-
4
- export type UniformType =
5
- | '1f' | '1i' | '1fv' | '2f' | '3f' | '3fv' | '4f' | '4fv'
6
- | 'Matrix2fv' | 'Matrix3fv' | 'Matrix4fv';
7
-
8
- export interface UniformSpec {
9
- type: UniformType;
10
- value: number | number[];
11
- /**
12
- * For array uniforms (`1fv`, `3fv`, `4fv`, or a matrix type repeated), the
13
- * declared length. Optional: when omitted, it is inferred from
14
- * `value.length` -- see `inferArraySize`. The inferred length is exactly
15
- * what the recompile check uses too (see `effectiveArraySize`), so a
16
- * length change recompiles the shader whether `arraySize` was passed
17
- * explicitly or left to be inferred -- e.g. a per-band uniform whose
18
- * length tracks a reactive `size`/`barCount` prop stays correct across a
19
- * band-count change with no extra care from the caller.
20
- */
21
- arraySize?: number;
22
- }
23
-
24
- export interface ShaderCanvasProps {
25
- /**
26
- * GLSL defining `mainImage(out vec4 fragColor, in vec2 fragCoord)`.
27
- *
28
- * MUST output PREMULTIPLIED colour: `fragColor = vec4(rgb * alpha, alpha);`,
29
- * not `vec4(rgb, alpha)`. The canvas context uses the browser default
30
- * `premultipliedAlpha: true` (see `ShaderCanvas`'s doc for why), so a
31
- * naturally written soft/anti-aliased edge that returns straight (not
32
- * premultiplied) colour composites with a dark fringe or halo -- most
33
- * visible on a light page background.
34
- */
35
- fragment: string;
36
- /**
37
- * Custom uniforms. THIS CANVAS DECLARES THEM FOR YOU by injecting
38
- * `uniform <type> <name>;` into the shader source. Declaring them yourself
39
- * in `fragment` too is a GLSL redefinition and fails to compile.
40
- */
41
- uniforms?: Record<string, UniformSpec>;
42
- precision?: 'lowp' | 'mediump' | 'highp';
43
- /**
44
- * Called when the shader cannot render at all: no WebGL context, or a
45
- * compile/link failure. Not called again for a later value-only uniform
46
- * update (see the reactivity note on `ShaderCanvas` below) -- only when
47
- * the shader itself is rebuilt and that rebuild fails.
48
- */
49
- onError?: (message: string) => void;
50
- /**
51
- * Keep animating while the canvas is off screen. Default `false`.
52
- *
53
- * By default an off-screen canvas stops drawing AND hands its WebGL context
54
- * back to the browser (see `ShaderCanvas`'s doc), because contexts are
55
- * rationed at roughly 16 per renderer process and a page of visualizers
56
- * blows through that. Set this when a canvas must keep running unseen --
57
- * capturing frames, or a shader whose state must not visibly jump when it
58
- * scrolls back in. The cost is one permanently-held context per canvas, so
59
- * it does not scale: a page that sets this on more than a handful of
60
- * visualizers is back to the eviction problem the default exists to avoid.
61
- *
62
- * Named to match upstream's prop of the same name, though ours opts out of
63
- * something stronger: theirs only pauses the draw loop, ours also releases
64
- * the context.
65
- *
66
- * This does NOT override `prefers-reduced-motion`. Reduced motion is applied
67
- * a layer up, by the variants zeroing their own speed uniforms, so a frozen
68
- * shader stays a still image whether or not this is set -- this only decides
69
- * whether frames keep being drawn, never what they contain.
70
- */
71
- animateWhenNotVisible?: boolean;
72
- class?: string;
73
- }
74
-
75
- /** Default accent for the shader variants, matching upstream's. */
76
- export const DEFAULT_SHADER_COLOR = '#1FD5F9';
77
-
78
- /**
79
- * `#rrggbb` to three 0..1 floats, for a `3fv` uniform. Falls back to the
80
- * default on malformed input (short forms like `#fff`, bare colour names,
81
- * empty strings) rather than throwing into a render loop.
82
- *
83
- * Lives here, not duplicated, because wave, aurora, and custom variants all
84
- * need it.
85
- */
86
- export function hexToRgb(hex: string): [number, number, number] {
87
- const m = hex.trim().match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
88
- if (!m) return hexToRgb(DEFAULT_SHADER_COLOR);
89
- const [, r, g, b] = m;
90
- return [parseInt(r!, 16) / 255, parseInt(g!, 16) / 255, parseInt(b!, 16) / 255];
91
- }
92
-
93
- const GLSL_TYPE: Record<UniformType, string> = {
94
- '1f': 'float', '1i': 'int', '1fv': 'float',
95
- '2f': 'vec2', '3f': 'vec3', '3fv': 'vec3', '4f': 'vec4', '4fv': 'vec4',
96
- Matrix2fv: 'mat2', Matrix3fv: 'mat3', Matrix4fv: 'mat4',
97
- };
98
-
99
- /**
100
- * How many raw scalars make up ONE instance of an "*fv"/matrix uniform's
101
- * GLSL type -- e.g. one `3fv` (a single vec3) is 3 floats, one `Matrix4fv`
102
- * (a single mat4) is 16. Only these six types take an array `value` where
103
- * "array" is ambiguous between "one instance, passed as an array because
104
- * that is how the `*fv` GPU setters work" and "a genuine GLSL array of N
105
- * instances the caller forgot to size." `inferArraySize` below uses this to
106
- * tell the two apart: `value.length` equal to one unit's size is a single
107
- * instance (no `[N]`); a whole-number multiple greater than one is an
108
- * unsized array, so its size gets inferred.
109
- */
110
- const UNIFORM_UNIT_SIZE: Partial<Record<UniformType, number>> = {
111
- '1fv': 1, '3fv': 3, '4fv': 4,
112
- Matrix2fv: 4, Matrix3fv: 9, Matrix4fv: 16,
113
- };
114
-
115
- /**
116
- * When a caller omits `arraySize` on an array-capable uniform (`1fv`, `3fv`,
117
- * `4fv`, or a matrix type) but `value` is longer than one instance of that
118
- * type, infer the array length from `value.length` instead of silently
119
- * declaring a scalar.
120
- *
121
- * This closes a real internal inconsistency: without it, `{ type: '1fv',
122
- * value: [a, b, c] }` declared `uniform float name;` (scalar) while
123
- * `setUniform` unconditionally called `gl.uniform1fv(location, [a, b, c])`
124
- * -- a mismatch WebGL reports as `INVALID_OPERATION` with no JS exception
125
- * and no `onError`, just a stale or zero value on screen. This function is
126
- * what keeps the declaration and the setter from ever being able to
127
- * disagree.
128
- */
129
- function inferArraySize(u: UniformSpec): number | undefined {
130
- const unit = UNIFORM_UNIT_SIZE[u.type];
131
- if (!unit || !Array.isArray(u.value)) return undefined;
132
- const instances = u.value.length / unit;
133
- return instances > 1 && Number.isInteger(instances) ? instances : undefined;
134
- }
135
-
136
- /**
137
- * The array length a uniform will actually be declared and compiled with:
138
- * the caller's explicit `arraySize` if given, otherwise `inferArraySize`'s
139
- * guess from `value.length`.
140
- *
141
- * `buildFragmentSource` (the declaration) and `uniformShapeKey` (the
142
- * recompile trigger) both call this SAME function, on purpose -- it is the
143
- * one source of truth for "how big is this array," so the two can never
144
- * disagree. Before this existed, an uninferred array uniform's declaration
145
- * came from `value.length` but the recompile check only looked at the
146
- * explicit `arraySize` field: a reactive `value.length` change (e.g. a
147
- * `uBands` uniform whose length tracks a `size`/`barCount` prop, which does
148
- * change while a shader variant stays mounted) would silently NOT recompile,
149
- * reintroducing the exact declaration/setter mismatch this file exists to
150
- * prevent, just one door over. Routing both call sites through the same
151
- * function makes that impossible rather than merely documented.
152
- */
153
- function effectiveArraySize(u: UniformSpec): number | undefined {
154
- return u.arraySize ?? inferArraySize(u);
155
- }
156
-
157
- /** Every ShaderToy built-in we support, declared for every shader. */
158
- const BUILTINS = [
159
- 'uniform float iTime;',
160
- 'uniform vec2 iResolution;',
161
- 'uniform vec4 iMouse;',
162
- 'uniform int iFrame;',
163
- 'uniform vec4 iDate;',
164
- ].join('\n');
165
-
166
- const VERTEX_SOURCE = `
167
- attribute vec3 aVertexPosition;
168
- void main() { gl_Position = vec4(aVertexPosition, 1.0); }
169
- `;
170
-
171
- /**
172
- * Assemble the full fragment shader: precision, built-ins, auto-declared
173
- * custom uniforms, the caller's body, and a `main()` that forwards to
174
- * `mainImage`.
175
- *
176
- * Declares every entry in `uniforms` for the caller (`uniform <type>
177
- * <name>;`, injected right after the precision qualifier). Declaring the
178
- * SAME uniform name inside `fragment` too is a GLSL redefinition and fails
179
- * to compile -- that is the contract this function exists to protect.
180
- *
181
- * Split out from the component because it is the only part testable without
182
- * a GPU (jsdom has no WebGL at all), and it is where both a compile-breaking
183
- * duplicate declaration and an array-size mismatch would show up.
184
- */
185
- export function buildFragmentSource(
186
- fragment: string,
187
- uniforms: Record<string, UniformSpec>,
188
- precision: string,
189
- ): string {
190
- const declarations = Object.entries(uniforms)
191
- .map(([name, u]) => {
192
- const arraySize = effectiveArraySize(u);
193
- const size = arraySize ? `[${arraySize}]` : '';
194
- return `uniform ${GLSL_TYPE[u.type]} ${name}${size};`;
195
- })
196
- .join('\n');
197
-
198
- return [
199
- `precision ${precision} float;`,
200
- BUILTINS,
201
- declarations,
202
- fragment,
203
- 'void main() { mainImage(gl_FragColor, gl_FragCoord.xy); }',
204
- ].join('\n');
205
- }
206
-
207
- /**
208
- * A stable fingerprint of the uniforms' STRUCTURE -- name, GLSL type, and
209
- * EFFECTIVE array size (`effectiveArraySize`, explicit or inferred, never
210
- * raw `u.arraySize` alone) -- deliberately excluding `value` itself. Two
211
- * calls with the same names/types/sizes but different values return the
212
- * same string; two calls where an array's `value.length` differs, even with
213
- * no explicit `arraySize` on either side, return DIFFERENT strings, because
214
- * that length difference is exactly what `buildFragmentSource` would
215
- * declare differently.
216
- *
217
- * This is what lets the component recompile only when the shader itself
218
- * must change, not every time a uniform's value updates (which, for an
219
- * audio-reactive uniform, is every animation frame) -- while still
220
- * recompiling on every change that actually changes the declared source,
221
- * including a length-only change to an inferred-size array.
222
- */
223
- function uniformShapeKey(uniforms: Record<string, UniformSpec>): string {
224
- return Object.keys(uniforms)
225
- .sort()
226
- .map((name) => {
227
- const u = uniforms[name]!;
228
- return `${name}:${u.type}:${effectiveArraySize(u) ?? ''}`;
229
- })
230
- .join('|');
231
- }
232
-
233
- function compile(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | string {
234
- const shader = gl.createShader(type);
235
- if (!shader) return 'Could not create a WebGL shader.';
236
- gl.shaderSource(shader, source);
237
- gl.compileShader(shader);
238
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
239
- const log = gl.getShaderInfoLog(shader) ?? 'Unknown shader compile error.';
240
- gl.deleteShader(shader);
241
- return log;
242
- }
243
- return shader;
244
- }
245
-
246
- /** Push one uniform value to the GPU, dispatching on its declared type. */
247
- function setUniform(
248
- gl: WebGLRenderingContext,
249
- location: WebGLUniformLocation,
250
- spec: UniformSpec,
251
- ): void {
252
- const v = spec.value;
253
- switch (spec.type) {
254
- case '1f': gl.uniform1f(location, v as number); break;
255
- case '1i': gl.uniform1i(location, v as number); break;
256
- case '1fv': gl.uniform1fv(location, v as number[]); break;
257
- case '2f': gl.uniform2fv(location, v as number[]); break;
258
- case '3f': case '3fv': gl.uniform3fv(location, v as number[]); break;
259
- case '4f': case '4fv': gl.uniform4fv(location, v as number[]); break;
260
- case 'Matrix2fv': gl.uniformMatrix2fv(location, false, v as number[]); break;
261
- case 'Matrix3fv': gl.uniformMatrix3fv(location, false, v as number[]); break;
262
- case 'Matrix4fv': gl.uniformMatrix4fv(location, false, v as number[]); break;
263
- }
264
- }
265
-
266
- /**
267
- * A full-screen fragment shader on a canvas, ShaderToy-compatible.
268
- *
269
- * Deliberately does NOT support textures, `iChannel` inputs, video,
270
- * multipass, or device orientation. Those are what make upstream's runner
271
- * 988 lines, and an audio visualizer needs none of them.
272
- *
273
- * Renders with a fully transparent clear colour (`gl.clearColor(0,0,0,0)`).
274
- * Compositing over the page comes from the canvas context's default
275
- * `premultipliedAlpha: true`, NOT from `gl.blendFunc` (see the comment at
276
- * the blend call site for why that call has no effect on today's
277
- * single-pass output). Because of that default, `fragment` MUST output
278
- * premultiplied colour -- see the doc on the `fragment` prop for the exact
279
- * contract.
280
- *
281
- * Reactivity note: only `fragment`, `precision`, and the STRUCTURE of
282
- * `uniforms` (which names exist, with which types/sizes) trigger a rebuild
283
- * of the GL program. A uniform's VALUE is read fresh every animation frame
284
- * without forcing a rebuild -- see `uniformShapeKey` above. This matters
285
- * because a caller typically re-creates the `uniforms` object every render
286
- * (e.g. `{ uVolume: { type: '1f', value: volume() } }`); tracking that
287
- * object directly would tear down and recompile the whole program on every
288
- * frame the volume changes. `fragment` and `precision` are ALSO read through
289
- * their own memos, not directly, for the same reason: a variant's `precision`
290
- * is commonly computed from a prop (`size`) that arrives via a dispatcher
291
- * spread bundling unrelated fast-changing signals (`bands`), so an unmemoized
292
- * read can mark this effect stale on every one of those, even though
293
- * `precision`'s resolved value never changes. See the comment above
294
- * `fragmentMemo`/`precisionMemo` below for the measured failure this caused.
295
- *
296
- * Visibility: while the canvas is off screen this canvas RELEASES its WebGL
297
- * context outright, and takes it back when the canvas returns -- see the
298
- * `ContextState` block inside the component for the whole mechanism and the
299
- * context-budget measurement that forced it. There is no prop for this; it is
300
- * always on.
301
- */
302
- export function ShaderCanvas(props: ShaderCanvasProps): JSX.Element {
303
- let canvas!: HTMLCanvasElement;
304
-
305
- const shapeKey = createMemo(() => uniformShapeKey(props.uniforms ?? {}));
306
- // `fragment` and `precision` get the SAME insulation as `shapeKey` above,
307
- // and for the same underlying reason. A variant's props are commonly built
308
- // with `{...shared()}` (see index.tsx's dispatcher: `shared()` returns
309
- // `state`/`size`/`bands`/`frozen`/`color` together). Solid's spread-prop
310
- // merging re-invokes the WHOLE source function on ANY property read from
311
- // the merged result -- so a variant computing `precision` from `props.size`
312
- // (e.g. aurora/wave's `props.size === 'icon' || ... ? 'mediump' :
313
- // 'highp'`) transitively re-runs `shared()`, and thus re-reads `bands()`,
314
- // on every read of `precision`, even though `size` itself never changes.
315
- // Reading `props.precision` directly inside the compile effect below
316
- // subscribed it to THAT transitive `bands()` read -- at ~31 band updates a
317
- // second, `precision`'s own resolved value never changed, but the effect
318
- // reran on every single one anyway, tearing down and recompiling the whole
319
- // GL program 15-20 times a second in production (measured on the Aurora
320
- // story: ~70 recompiles in 4s, `iTime` pinned under 0.33s and periodically
321
- // NEGATIVE from `start` being re-stamped mid-flight). `shapeKey` already
322
- // proved this class of leak is real for `uniforms`; `fragment`/`precision`
323
- // needed the identical fix, matching the one just applied to
324
- // `use-sequencer.ts` for the same root cause (an effect transitively
325
- // subscribed to a signal it should not be, via a prop getter chain) --
326
- // wrap in a memo so only the RESOLVED value, never the act of reading it,
327
- // can mark the effect stale.
328
- const fragmentMemo = createMemo(() => props.fragment);
329
- const precisionMemo = createMemo(() => props.precision ?? 'highp');
330
-
331
- // ------------------------------------------------------------------------
332
- // Context lifecycle.
333
- //
334
- // Chrome caps LIVE WebGL contexts at about 16 per renderer process and
335
- // silently evicts the oldest past that -- a budget shared across
336
- // same-origin iframes too, so splitting the page up buys nothing (measured:
337
- // the AudioVisualizer docs page wanted 18, got 16, and 2 canvases failed to
338
- // compile with no error anywhere). Holding a context for the component's
339
- // whole life is what made that page unable to host the shader stories at
340
- // all.
341
- //
342
- // Merely pausing the draw loop off screen -- which is all upstream's
343
- // runner does -- does NOT return a slot: an idle context still occupies
344
- // one. So an off-screen canvas gives the context BACK, via
345
- // `WEBGL_lose_context`'s `loseContext()`, and asks for it again with
346
- // `restoreContext()` on the way in. Consequence worth stating plainly: N
347
- // off-screen visualizers now hold ZERO contexts between them, and a page
348
- // holds one per canvas actually in the viewport.
349
- //
350
- // All of this state is COMPONENT-scoped rather than living inside the
351
- // compile effect, because a context belongs to the CANVAS, which outlives
352
- // any one run of that effect. A shader released while off screen must still
353
- // be restorable after a recompile (a `size`/band-count change genuinely
354
- // rebuilds the shader while a tile is scrolled away).
355
- // ------------------------------------------------------------------------
356
-
357
- /**
358
- * Where the canvas's context is, independent of whether a program is
359
- * currently compiled against it (that is `teardown`).
360
- *
361
- * `losing` and `restoring` are the IN-FLIGHT states, and they are the whole
362
- * reason this is a state machine rather than a boolean:
363
- * `loseContext()`/`restoreContext()` do not take effect synchronously --
364
- * the browser answers later with `webglcontextlost`/`webglcontextrestored`.
365
- * A canvas that scrolls out and back in before the answer arrives must sit
366
- * in the in-flight state and re-decide when the event lands, never start a
367
- * second draw loop or compile against a context that is still gone.
368
- */
369
- type ContextState = 'none' | 'live' | 'losing' | 'lost' | 'restoring';
370
- let contextState: ContextState = 'none';
371
- let context: WebGLRenderingContext | null = null;
372
- /**
373
- * Cached WHILE THE CONTEXT IS ALIVE, deliberately: a lost context answers
374
- * `getExtension` with null, so re-fetching it on the way back in is a dead
375
- * end. This handle is the only thing that can restore what we released.
376
- */
377
- let loseExtension: WEBGL_lose_context | null = null;
378
-
379
- /** Undoes everything the current activation built. Null when nothing is built. */
380
- let teardown: (() => void) | null = null;
381
- /** The current frame callback. Null when nothing is built. */
382
- let drawFrame: ((now: number) => void) | null = null;
383
- /** The pending animation frame id, or 0 when the loop is stopped. */
384
- let raf = 0;
385
- /**
386
- * `cancelAnimationFrame`, captured at SETUP.
387
- *
388
- * `stopLoop()` below runs at dispose (via `release()`), and dispose is not
389
- * guaranteed to happen while the page that mounted this canvas is still
390
- * standing -- `component-register`'s `disconnectedCallback` defers a
391
- * microtask, and a test environment tears its DOM globals down in between.
392
- * A bare `cancelAnimationFrame` there throws from a promise nobody holds, so
393
- * it surfaces as an unhandled rejection that fails a run in which every test
394
- * passed. See tests/components/teardown-without-dom-globals.test.tsx.
395
- *
396
- * The FUNCTION, not the view. The `const win = window` capture that fixes a
397
- * bare `document` does nothing here: `window === globalThis` -- measured, in
398
- * jsdom and in real Chromium/WebKit alike -- and the teardown deletes these
399
- * keys off that very object, so `win.cancelAnimationFrame` is undefined by
400
- * the time cleanup runs. It only trades the ReferenceError for a TypeError.
401
- * `.bind` pins the receiver the WebIDL operation is specified on; Chromium
402
- * and WebKit both accept a detached call (measured), so the bind is belt and
403
- * braces against an engine that does not, at zero cost.
404
- *
405
- * GUARDED because "setup" for this component is its body, and a server render
406
- * executes component bodies. Node has no `cancelAnimationFrame` at all, so an
407
- * unguarded capture here would trade the disposal crash for an SSR crash --
408
- * measured, not hypothesised. Nothing can be scheduled without
409
- * `requestAnimationFrame` either, so the no-op fallback is exactly right.
410
- */
411
- const cancelFrame = typeof cancelAnimationFrame === 'function'
412
- ? cancelAnimationFrame.bind(globalThis)
413
- : () => {};
414
-
415
- /** The assembled source and the uniform snapshot it was assembled from. */
416
- let build: { source: string; uniforms: Record<string, UniformSpec> } | null = null;
417
- /** THIS build cannot compile; reset whenever the effect produces a new one. */
418
- let buildFailed = false;
419
- /** No WebGL at all. Permanent for this mount, matching `onError`'s contract. */
420
- let noWebGL = false;
421
- /** The BROWSER took the context away (not us). Permanent for this mount. */
422
- let hardLost = false;
423
- let disposed = false;
424
-
425
- let visibility: IntersectionObserver | undefined;
426
- let wired = false;
427
- /**
428
- * Whether the canvas is on screen. Starts true and STAYS true when there is
429
- * no `IntersectionObserver` to consult (SSR, jsdom, older browsers), which
430
- * is what keeps that environment on exactly the pre-observer behaviour:
431
- * compile on mount, draw forever.
432
- */
433
- let onScreen = true;
434
- /**
435
- * The resolved `animateWhenNotVisible`, cached rather than read from props
436
- * inside `sync`. `sync` runs inside the compile effect, and a props read
437
- * there would subscribe that effect to this flag -- rebuilding the entire GL
438
- * program every time a caller toggled it, the exact leak the fragment /
439
- * precision / shapeKey memos above exist to prevent. Its own effect below is
440
- * the single writer.
441
- */
442
- let alwaysAnimate = false;
443
-
444
- /**
445
- * The shader clock, deliberately outliving both a release and a recompile.
446
- *
447
- * `iTime` is ON-SCREEN time: it accumulates only while the loop runs, so a
448
- * canvas that comes back after a minute off screen picks up exactly where
449
- * the viewer last saw it rather than jumping a minute forward. Freezing
450
- * rather than tracking wall-clock is a choice -- nothing here is
451
- * synchronised to real time, so the only observable difference is a
452
- * discontinuity on scroll-in, and there is no reason to have one.
453
- *
454
- * What it must never do is RESTART. A previous round shipped a defect where
455
- * a spuriously re-running compile effect re-stamped the clock's origin every
456
- * few frames, pinning `iTime` under 0.33s forever (shaders looked like they
457
- * were looping a third of a second). The memos above are the fix for that
458
- * cause; keeping the clock out here means even a legitimate recompile -- a
459
- * band-count change, say -- no longer snaps the animation back to zero, so
460
- * the whole class of "the shader restarted" bugs has one fewer door.
461
- * `frame` (`iFrame`) is kept monotonic for the same reason.
462
- */
463
- let elapsedMs = 0;
464
- let runStartedAt = 0;
465
- let frame = 0;
466
-
467
- const acquireContext = (): WebGLRenderingContext | null => {
468
- if (context) return context;
469
- context = (canvas.getContext('webgl') ??
470
- canvas.getContext('experimental-webgl')) as WebGLRenderingContext | null;
471
- if (context) {
472
- contextState = 'live';
473
- // Optional call: every real context implements `getExtension`, but a
474
- // context stub that does not should degrade to "cannot release" rather
475
- // than throwing out of the compile path.
476
- loseExtension = context.getExtension?.('WEBGL_lose_context') ?? null;
477
- }
478
- return context;
479
- };
480
-
481
- const startLoop = () => {
482
- // The one gate that makes a second concurrent loop impossible, however
483
- // fast visibility toggles: a loop is running iff `raf !== 0` (a real
484
- // animation-frame id is never 0).
485
- if (raf !== 0 || !drawFrame || disposed) return;
486
- runStartedAt = performance.now();
487
- raf = requestAnimationFrame(drawFrame);
488
- };
489
-
490
- const stopLoop = () => {
491
- if (raf === 0) return;
492
- // Captured at setup -- see `cancelFrame`. This line runs at dispose.
493
- cancelFrame(raf);
494
- raf = 0;
495
- // Bank what was drawn so `iTime` resumes from here, not from zero.
496
- elapsedMs += Math.max(0, performance.now() - runStartedAt);
497
- };
498
-
499
- /** Drop the compiled program/shaders/buffer, leaving the context itself alone. */
500
- const release = () => {
501
- stopLoop();
502
- const run = teardown;
503
- teardown = null;
504
- run?.();
505
- };
506
-
507
- const requestLoss = () => {
508
- if (!loseExtension || contextState !== 'live') return;
509
- contextState = 'losing';
510
- loseExtension.loseContext();
511
- };
512
-
513
- const requestRestore = () => {
514
- if (!loseExtension) return;
515
- contextState = 'restoring';
516
- loseExtension.restoreContext();
517
- };
518
-
519
- /**
520
- * Reconcile what is built against where the canvas is. Every input --
521
- * a visibility change, a context event, a new compiled source -- routes
522
- * through here rather than acting directly, which is what keeps the
523
- * in-flight windows from producing two loops or a half-initialised context.
524
- */
525
- const sync = () => {
526
- if (disposed || noWebGL || hardLost) return;
527
- // The ONE place visibility policy is decided. `animateWhenNotVisible`
528
- // overrides the observer's verdict here rather than anywhere upstream of
529
- // it, so there is still exactly one writer deciding whether this canvas
530
- // should be drawing -- a runtime flip of the flag is just another call
531
- // into this same reconciler, like a scroll or a context event.
532
- if (onScreen || alwaysAnimate) {
533
- // Mid-flight: the `webglcontextlost`/`webglcontextrestored` handler
534
- // re-enters here once the browser answers.
535
- if (contextState === 'losing' || contextState === 'restoring') return;
536
- if (contextState === 'lost') { requestRestore(); return; }
537
- activate();
538
- } else if (loseExtension && contextState === 'live') {
539
- // Resources first, THEN the context: deleting GPU objects after the
540
- // context is gone is a spec no-op, which leaks them for as long as the
541
- // driver keeps the dead context around.
542
- release();
543
- requestLoss();
544
- } else {
545
- // No way to hand the context back (no extension), or it is already
546
- // gone. Stop burning frames either way -- upstream's pause, as the
547
- // floor rather than the ceiling.
548
- stopLoop();
549
- }
550
- };
551
-
552
- const setOnScreen = (next: boolean) => {
553
- if (next === onScreen || disposed) return;
554
- onScreen = next;
555
- sync();
556
- };
557
-
558
- const onContextLost = (e: Event) => {
559
- // Required by spec to even ALLOW a later `webglcontextrestored` --
560
- // including for the losses this component asks for itself.
561
- e.preventDefault();
562
- const deliberate = contextState === 'losing';
563
- contextState = 'lost';
564
- release();
565
- if (deliberate) {
566
- // Ours, to free the budget while off screen. If the canvas scrolled
567
- // back in while the event was in flight, this is where it gets restored.
568
- sync();
569
- return;
570
- }
571
- // The browser evicted us -- most commonly because the page blew past the
572
- // context cap. Permanent for this mount: `onError` is what lets the
573
- // dispatcher fall back to bars. Without this the draw loop would keep
574
- // issuing GL calls that are all silent no-ops on a lost context, leaving a
575
- // shader frozen mid-frame with nothing reported anywhere.
576
- hardLost = true;
577
- props.onError?.('WebGL context was lost.');
578
- };
579
-
580
- const onContextRestored = () => {
581
- contextState = 'live';
582
- // Rebuild if the canvas is still on screen, or hand the context straight
583
- // back if it scrolled away again while the restore was in flight.
584
- sync();
585
- };
586
-
587
- const wireCanvas = () => {
588
- if (wired) return;
589
- wired = true;
590
- canvas.addEventListener('webglcontextlost', onContextLost, false);
591
- canvas.addEventListener('webglcontextrestored', onContextRestored, false);
592
-
593
- if (typeof IntersectionObserver === 'undefined') return;
594
-
595
- // The observer is installed even when `animateWhenNotVisible` is set, and
596
- // its verdict is ignored in `sync` instead. Skipping the observer in that
597
- // mode would be marginally cheaper, but it would leave `onScreen` frozen
598
- // at a stale default, so flipping the flag OFF at runtime would need the
599
- // observer wired up right then and would keep drawing until its first
600
- // callback landed -- a second control path, and a wrong answer in the
601
- // meantime. Keeping it always-on means `onScreen` is continuously
602
- // accurate and a flip in either direction is a single `sync()` call.
603
- // An IntersectionObserver that never fires costs essentially nothing.
604
- //
605
- // With an observer, NOTHING is built until it reports the canvas on
606
- // screen -- unless `animateWhenNotVisible` says otherwise, which `sync`
607
- // resolves below. That is the load-bearing half of the fix: a page
608
- // mounting 18 visualizers otherwise creates 18 contexts in the mount task
609
- // and has 2 evicted by the browser before the first observer callback
610
- // could possibly run -- and an evicted context is a permanent failure,
611
- // not a recoverable one.
612
- onScreen = false;
613
- visibility = new IntersectionObserver((entries) => {
614
- const entry = entries[entries.length - 1];
615
- if (entry) setOnScreen(entry.isIntersecting);
616
- }, { threshold: 0 });
617
- visibility.observe(canvas);
618
- };
619
-
620
- const activate = () => {
621
- if (disposed || noWebGL || hardLost || buildFailed) return;
622
- const current = build;
623
- if (!current) return;
624
-
625
- // Already built and only the loop was stopped (the no-extension path
626
- // above): re-arm it rather than recompiling.
627
- if (teardown) { startLoop(); return; }
628
-
629
- const gl = acquireContext();
630
- if (!gl) {
631
- noWebGL = true;
632
- props.onError?.('WebGL is not available in this browser.');
633
- return;
634
- }
635
- if (gl.isContextLost()) {
636
- // Released earlier -- possibly by a PREVIOUS run of the compile effect,
637
- // which is why `loseExtension` is cached at component scope. Ask for it
638
- // back and let `webglcontextrestored` re-enter here.
639
- requestRestore();
640
- return;
641
- }
642
-
643
- const { source, uniforms } = current;
644
-
645
- const vs = compile(gl, gl.VERTEX_SHADER, VERTEX_SOURCE);
646
- if (typeof vs === 'string') {
647
- buildFailed = true;
648
- props.onError?.(vs);
649
- return;
650
- }
651
- const fs = compile(gl, gl.FRAGMENT_SHADER, source);
652
- if (typeof fs === 'string') {
653
- gl.deleteShader(vs);
654
- buildFailed = true;
655
- props.onError?.(fs);
656
- return;
657
- }
658
-
659
- const program = gl.createProgram();
660
- if (!program) {
661
- gl.deleteShader(vs);
662
- gl.deleteShader(fs);
663
- buildFailed = true;
664
- props.onError?.('Could not create a WebGL program.');
665
- return;
666
- }
667
- gl.attachShader(program, vs);
668
- gl.attachShader(program, fs);
669
- gl.linkProgram(program);
670
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
671
- const log = gl.getProgramInfoLog(program) ?? 'Shader program failed to link.';
672
- gl.deleteProgram(program);
673
- gl.deleteShader(vs);
674
- gl.deleteShader(fs);
675
- buildFailed = true;
676
- props.onError?.(log);
677
- return;
678
- }
679
- gl.useProgram(program);
680
-
681
- // Two triangles covering clip space. The fragment shader does the rest.
682
- const buffer = gl.createBuffer();
683
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
684
- gl.bufferData(
685
- gl.ARRAY_BUFFER,
686
- new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]),
687
- gl.STATIC_DRAW,
688
- );
689
- const attr = gl.getAttribLocation(program, 'aVertexPosition');
690
- gl.enableVertexAttribArray(attr);
691
- gl.vertexAttribPointer(attr, 3, gl.FLOAT, false, 0, 0);
692
-
693
- // This blendFunc has NO effect on today's output: `gl.clear` runs
694
- // immediately before the single `gl.drawArrays` every frame (see draw()
695
- // below), so the framebuffer is always transparent-black at draw time --
696
- // there is nothing behind the draw call to blend against. The actual
697
- // transparency mechanism is the canvas context's default
698
- // `premultipliedAlpha: true` combined with `clearColor(0,0,0,0)` below:
699
- // the browser composites the canvas's own premultiplied RGBA buffer over
700
- // the page. That default is also why `fragment` must output premultiplied
701
- // colour (see the `fragment` prop's doc) -- an unpremultiplied translucent
702
- // edge produces a dark fringe on a light page.
703
- //
704
- // The call stays enabled anyway: it is exactly what a future multipass or
705
- // non-clearing render path would need, and silently dropping it now would
706
- // be a behavioural change for that later path, not a cleanup.
707
- gl.enable(gl.BLEND);
708
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
709
-
710
- const loc = (name: string) => gl.getUniformLocation(program, name);
711
- const uTime = loc('iTime');
712
- const uResolution = loc('iResolution');
713
- const uMouse = loc('iMouse');
714
- const uFrame = loc('iFrame');
715
- const uDate = loc('iDate');
716
- const customLocations = Object.keys(uniforms).map((n) => [n, loc(n)] as const);
717
-
718
- // Only .xy (pointer position) is implemented. ShaderToy's iMouse.zw
719
- // carries click-down position; this canvas has no pointerdown tracking,
720
- // so .zw stays permanently zero.
721
- const mouse = [0, 0, 0, 0];
722
- const onMove = (e: PointerEvent) => {
723
- const r = canvas.getBoundingClientRect();
724
- mouse[0] = e.clientX - r.left;
725
- mouse[1] = r.height - (e.clientY - r.top);
726
- };
727
- canvas.addEventListener('pointermove', onMove, { passive: true });
728
-
729
- // Match the backing store to the displayed size so the shader is not blurry.
730
- const resize = () => {
731
- const dpr = globalThis.devicePixelRatio ?? 1;
732
- const w = Math.max(1, Math.floor(canvas.clientWidth * dpr));
733
- const h = Math.max(1, Math.floor(canvas.clientHeight * dpr));
734
- if (canvas.width !== w || canvas.height !== h) {
735
- canvas.width = w;
736
- canvas.height = h;
737
- gl.viewport(0, 0, w, h);
738
- }
739
- };
740
-
741
- const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : undefined;
742
- ro?.observe(canvas);
743
- resize();
744
-
745
- const draw = (now: number) => {
746
- // Belt-and-braces alongside the `webglcontextlost` listener: if a
747
- // context goes away by some path that does not fire the event
748
- // synchronously before the next frame, this still stops issuing GL
749
- // calls against it.
750
- if (hardLost || gl.isContextLost()) return;
751
- resize();
752
- // Clamped at 0: a browser's rAF callback receives the timestamp for
753
- // when the current frame BEGAN, captured before this component's
754
- // synchronous setup finishes and stamps `runStartedAt` -- so the very
755
- // first frame or two can compute a few milliseconds negative even with
756
- // nothing wrong. Harmless and self-correcting either way (confirmed:
757
- // the pathological, CONTINUOUSLY-recurring negative values reported in
758
- // production were the clock origin being re-stamped by a spuriously
759
- // re-running effect, not this), but there is no reason `iTime` should
760
- // ever go negative for a caller, so it does not.
761
- const seconds = Math.max(0, (elapsedMs + (now - runStartedAt)) / 1000);
762
-
763
- if (uTime) gl.uniform1f(uTime, seconds);
764
- if (uResolution) gl.uniform2fv(uResolution, [canvas.width, canvas.height]);
765
- if (uMouse) gl.uniform4fv(uMouse, mouse);
766
- if (uFrame) gl.uniform1i(uFrame, frame);
767
- if (uDate) {
768
- const d = new Date();
769
- gl.uniform4fv(uDate, [
770
- d.getFullYear(), d.getMonth(), d.getDate(),
771
- d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(),
772
- ]);
773
- }
774
-
775
- // Read straight off props, not the closed-over `uniforms` snapshot, so
776
- // a value change (e.g. volume ticking every frame) takes effect
777
- // immediately without going through the compile effect above at all.
778
- for (const [name, location] of customLocations) {
779
- const spec = props.uniforms?.[name];
780
- if (spec && location) setUniform(gl, location, spec);
781
- }
782
-
783
- gl.clearColor(0, 0, 0, 0);
784
- gl.clear(gl.COLOR_BUFFER_BIT);
785
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
786
-
787
- frame++;
788
- raf = requestAnimationFrame(draw);
789
- };
790
-
791
- teardown = () => {
792
- drawFrame = null;
793
- ro?.disconnect();
794
- canvas.removeEventListener('pointermove', onMove);
795
- gl.deleteProgram(program);
796
- gl.deleteShader(vs);
797
- gl.deleteShader(fs);
798
- gl.deleteBuffer(buffer);
799
- };
800
- drawFrame = draw;
801
- startLoop();
802
- };
803
-
804
- onCleanup(() => {
805
- disposed = true;
806
- visibility?.disconnect();
807
- if (wired) {
808
- canvas.removeEventListener('webglcontextlost', onContextLost);
809
- canvas.removeEventListener('webglcontextrestored', onContextRestored);
810
- }
811
- release();
812
- // Hand the budget back NOW rather than waiting for the detached canvas to
813
- // be collected: switching variants, or navigating a page full of tiles,
814
- // otherwise leaves dead contexts holding slots for a while.
815
- if (loseExtension && contextState === 'live') loseExtension.loseContext();
816
- });
817
-
818
- // Registered before the compile effect so `alwaysAnimate` already holds the
819
- // caller's policy the first time `sync()` runs, avoiding one reconcile pass
820
- // that could only ever decide to do nothing. Measured, not assumed: putting
821
- // this effect AFTER the compile effect passes every test in this file
822
- // unchanged, because both run in the same synchronous flush either way and
823
- // the canvas is built before anything can observe it. So this is tidiness,
824
- // NOT a correctness requirement -- what actually guarantees an opted-out
825
- // canvas builds without waiting on an observer callback is `sync`'s
826
- // `onScreen || alwaysAnimate`, which is the thing under test.
827
- //
828
- // Wrapped in its own memo for the same reason `fragment`/`precision` are:
829
- // a variant's props arrive through a dispatcher spread that bundles
830
- // fast-changing signals, so an unmemoized read would re-run this effect at
831
- // band cadence even though the resolved boolean never changed.
832
- const animateWhenNotVisibleMemo = createMemo(() => props.animateWhenNotVisible ?? false);
833
- createEffect(() => {
834
- alwaysAnimate = animateWhenNotVisibleMemo();
835
- // Route the change through the reconciler rather than acting on it here:
836
- // whether this means "build now", "release now", or "nothing changes"
837
- // depends on state `sync` already owns.
838
- sync();
839
- });
840
-
841
- createEffect(() => {
842
- // Reads ONLY the three memos below -- never `props.fragment` /
843
- // `props.precision` / `props.uniforms` directly -- so this effect's
844
- // dependency set is exactly {fragmentMemo, precisionMemo, shapeKey}, and
845
- // nothing a caller's props chain does elsewhere (spread-derived or not)
846
- // can mark it stale without one of those three RESOLVED values actually
847
- // changing.
848
- //
849
- // Note what this effect does NOT do any more: touch the GPU. It resolves
850
- // the source and hands it to `sync`, which owns every decision about
851
- // whether a context should exist right now. That split is what lets the
852
- // SAME setup path serve a first mount, a recompile, and a scroll-back-in
853
- // restore, instead of one path per trigger.
854
- const fragment = fragmentMemo();
855
- const precision = precisionMemo();
856
- shapeKey();
857
- const uniforms = untrack(() => props.uniforms ?? {});
858
-
859
- build = { source: buildFragmentSource(fragment, uniforms, precision), uniforms };
860
- // A new source gets a fresh attempt: a shader that failed to compile must
861
- // not poison the next one the caller supplies.
862
- buildFailed = false;
863
-
864
- wireCanvas();
865
- sync();
866
-
867
- // Runs before the NEXT execution of this effect as well as on unmount, so
868
- // a source change always drops the program compiled from the old source
869
- // before `sync` builds the new one. The context itself deliberately
870
- // survives -- it is the expensive, rationed thing, and the new source
871
- // needs it immediately.
872
- onCleanup(release);
873
- });
874
-
875
- return <canvas ref={canvas} part="canvas" class={cn('block h-full w-full', props.class)} />;
876
- }