@kitn.ai/ui 0.27.0 → 0.29.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 (612) hide show
  1. package/README.md +10 -2
  2. package/dist/{Icon-BnFKqqtY.js → Icon-DObhsRtm.js} +1 -1
  3. package/dist/{action-icons-CBskfBMz.js → action-icons-CcgNN2oT.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-CjRU3JgV.js +1 -0
  7. package/dist/arrow-left-Bnpb1UEW.js +1 -0
  8. package/dist/{artifact-CvXEm1lS.js → artifact-Bwt7OgcR.js} +1 -1
  9. package/dist/{attachments-C43ZegEq.js → attachments-Qr4kg-2b.js} +1 -1
  10. package/dist/{audio-visualizer-B-79y1Tj.js → audio-visualizer-DToYdfSc.js} +1 -1
  11. package/dist/{badge-BpUbq8c0.js → badge-CdEu_jn2.js} +1 -1
  12. package/dist/{bell-sn17Ip9v.js → bell-DQAAOpDz.js} +1 -1
  13. package/dist/{button-BH6rN0U-.js → button-DkuOnGLy.js} +1 -1
  14. package/dist/{card-renderer-Cb1gQmth.js → card-renderer-Jx70m6_l.js} +1 -1
  15. package/dist/{card-routing-DifQgS7n.js → card-routing-0YidER_3.js} +1 -1
  16. package/dist/check-njkVdZu2.js +1 -0
  17. package/dist/{checkbox-Dc9Yov_B.js → checkbox-D7iRB3nL.js} +1 -1
  18. package/dist/{checkbox-group-B1kDLLYk.js → checkbox-group-B2_rpoPM.js} +1 -1
  19. package/dist/chevron-down-B72s6PXR.js +1 -0
  20. package/dist/chevron-right-BatuHzUm.js +1 -0
  21. package/dist/{choice-card-BT3c1S8g.js → choice-card-m0BYqqOK.js} +1 -1
  22. package/dist/circle-check-B62pODB3.js +1 -0
  23. package/dist/{circle-x-wobWkoSx.js → circle-x-BPG0iPst.js} +1 -1
  24. package/dist/circle-zx0ByzY9.js +1 -0
  25. package/dist/code-block-ClMLKb7C.js +1 -0
  26. package/dist/{collapsible-CPJbf6FM.js → collapsible-B4DlWicL.js} +1 -1
  27. package/dist/components/chat-thread.d.ts +89 -1
  28. package/dist/components/conversation-item.d.ts +16 -0
  29. package/dist/components/conversation-panel.d.ts +29 -0
  30. package/dist/components/home-panel.d.ts +34 -0
  31. package/dist/components/widget-tab-bar.d.ts +16 -0
  32. package/dist/{composer-CjngLzal.js → composer-sQ0HcvyG.js} +3 -3
  33. package/dist/{confirm-card-DCx_A4bU.js → confirm-card-rQWDQsnC.js} +1 -1
  34. package/dist/construct-cli.es.js +185 -17
  35. package/dist/construct.d.ts +5 -0
  36. package/dist/construct.js +327 -0
  37. package/dist/{context-BY8V-Nlp.js → context-0kg4ZX1J.js} +1 -1
  38. package/dist/controllable-CnGpMF6r.js +1 -0
  39. package/dist/conversation-item-BxwQLaFJ.js +1 -0
  40. package/dist/conversation-list-BCfGN1JU.js +1 -0
  41. package/dist/{copy-B6DO4PqB.js → copy-n9u9rqpi.js} +1 -1
  42. package/dist/core-C8fzo39E.js +1 -12
  43. package/dist/{create-tween-CnMHviKB.js → create-tween-C-q-JCM-.js} +1 -1
  44. package/dist/{create-tween-D1aiGZZh.js → create-tween-CRrJXxBd.js} +1 -1
  45. package/dist/{create-tween-BPzaefTp.js → create-tween-DZ3RRDHD.js} +1 -1
  46. package/dist/{create-tween-DUSQUKpr.js → create-tween-uPkArjIG.js} +1 -1
  47. package/dist/custom-elements.json +51 -2
  48. package/dist/{default-input-CucQEOqH.js → default-input-CaMrJkgH.js} +1 -1
  49. package/dist/define-By-uL588.js +1 -0
  50. package/dist/define.js +2 -2
  51. package/dist/define.server.js +2 -2
  52. package/dist/{disclosure-CbLtajUQ.js → disclosure-WUImBbDv.js} +1 -1
  53. package/dist/{dropdown-BwhViJFd.js → dropdown-wZVeJOMd.js} +1 -1
  54. package/dist/elements/agent-card.js +1 -1
  55. package/dist/elements/artifact.js +1 -1
  56. package/dist/elements/attachments.js +1 -1
  57. package/dist/elements/audio-visualizer.js +1 -1
  58. package/dist/elements/avatar.js +1 -1
  59. package/dist/elements/badge.js +1 -1
  60. package/dist/elements/button.js +1 -1
  61. package/dist/elements/card.js +1 -1
  62. package/dist/elements/cards.js +1 -1
  63. package/dist/elements/chain-of-thought.js +1 -1
  64. package/dist/elements/chat-scope-picker.js +1 -1
  65. package/dist/elements/chat-workspace.js +1 -1
  66. package/dist/elements/chat.js +1 -1
  67. package/dist/elements/checkbox-group.js +1 -1
  68. package/dist/elements/checkbox.js +1 -1
  69. package/dist/elements/checkpoint.js +1 -1
  70. package/dist/elements/choice.js +1 -1
  71. package/dist/elements/coachmark.js +1 -1
  72. package/dist/elements/code-block.js +1 -1
  73. package/dist/elements/command.js +1 -1
  74. package/dist/elements/compare.js +1 -1
  75. package/dist/elements/composer.js +1 -1
  76. package/dist/elements/confirm-card.js +1 -1
  77. package/dist/elements/context-meter.js +1 -1
  78. package/dist/elements/conversation-item.js +1 -1
  79. package/dist/elements/conversation-list.js +1 -1
  80. package/dist/elements/dialog.js +1 -1
  81. package/dist/elements/dock.js +3 -3
  82. package/dist/elements/dropdown.js +1 -1
  83. package/dist/elements/editable-label.js +1 -1
  84. package/dist/elements/embed.js +1 -1
  85. package/dist/elements/empty.js +1 -1
  86. package/dist/elements/feedback-bar.js +1 -1
  87. package/dist/elements/file-tree.js +1 -1
  88. package/dist/elements/file-upload.js +1 -1
  89. package/dist/elements/form.js +1 -1
  90. package/dist/elements/hover-card.js +1 -1
  91. package/dist/elements/icon.js +1 -1
  92. package/dist/elements/image.js +1 -1
  93. package/dist/elements/input.js +1 -1
  94. package/dist/elements/kbd.js +1 -1
  95. package/dist/elements/link-preview.js +1 -1
  96. package/dist/elements/loader.js +1 -1
  97. package/dist/elements/markdown.js +1 -1
  98. package/dist/elements/menu.js +1 -1
  99. package/dist/elements/message-skills.js +1 -1
  100. package/dist/elements/message.js +1 -1
  101. package/dist/elements/model-switcher.js +1 -1
  102. package/dist/elements/nav.js +1 -1
  103. package/dist/elements/notice.js +1 -1
  104. package/dist/elements/pane-grid.js +1 -1
  105. package/dist/elements/pane-group.js +1 -1
  106. package/dist/elements/pane.js +1 -1
  107. package/dist/elements/popover.js +1 -1
  108. package/dist/elements/progress-bar.js +1 -1
  109. package/dist/elements/prompt-dock.js +1 -1
  110. package/dist/elements/prompt-input.js +1 -1
  111. package/dist/elements/prompt-suggestions.js +1 -1
  112. package/dist/elements/radio-group.js +1 -1
  113. package/dist/elements/reasoning.js +1 -1
  114. package/dist/elements/remote.js +1 -1
  115. package/dist/elements/resizable.js +1 -1
  116. package/dist/elements/response-stream.js +1 -1
  117. package/dist/elements/screen.js +1 -1
  118. package/dist/elements/scroll-area.js +1 -1
  119. package/dist/elements/scroll-button.js +1 -1
  120. package/dist/elements/search.js +1 -1
  121. package/dist/elements/segmented.js +1 -1
  122. package/dist/elements/select.js +1 -1
  123. package/dist/elements/separator.js +1 -1
  124. package/dist/elements/setting-item.js +1 -1
  125. package/dist/elements/settings-group.js +1 -1
  126. package/dist/elements/skeleton.js +1 -1
  127. package/dist/elements/slider.js +1 -1
  128. package/dist/elements/source.js +1 -1
  129. package/dist/elements/status.js +1 -1
  130. package/dist/elements/switch.js +1 -1
  131. package/dist/elements/tabs.js +1 -1
  132. package/dist/elements/tasks.js +1 -1
  133. package/dist/elements/text-shimmer.js +1 -1
  134. package/dist/elements/thinking-bar.js +1 -1
  135. package/dist/elements/thread.js +1 -1
  136. package/dist/elements/toast.js +1 -1
  137. package/dist/elements/tool.js +1 -1
  138. package/dist/elements/tooltip.js +1 -1
  139. package/dist/elements/voice-input.js +1 -1
  140. package/dist/elements/voice-output.js +1 -1
  141. package/dist/elements.d.ts +20 -4
  142. package/dist/{ellipsis-DFr4Vqr3.js → ellipsis-CbOpJnU3.js} +1 -1
  143. package/dist/{embed-5s_vU4J0.js → embed-CrfjkbUG.js} +1 -1
  144. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  145. package/dist/{external-link-u9d85ORQ.js → external-link-ByXAwBBQ.js} +1 -1
  146. package/dist/{file-text-CX7_x42o.js → file-text-DMib2qwt.js} +1 -1
  147. package/dist/{file-tree-D3kBjXI5.js → file-tree-CJqM2aP7.js} +1 -1
  148. package/dist/{folder-V-YdIL8f.js → folder-_BAb929C.js} +1 -1
  149. package/dist/{form-BRNUMCN2.js → form-DSZgZQZk.js} +1 -1
  150. package/dist/{hover-card-DaeiYrTA.js → hover-card-BQbul9eq.js} +1 -1
  151. package/dist/{icon-BpixdZNq.js → icon-CdcfRhYI.js} +1 -1
  152. package/dist/index.d.ts +3 -1
  153. package/dist/index.js +2842 -2703
  154. package/dist/index.server.js +2820 -2681
  155. package/dist/{info-8mFsIjS3.js → info-DsFjGuI_.js} +1 -1
  156. package/dist/{input-d1Yuu-hz.js → input-DKbmkMos.js} +1 -1
  157. package/dist/kai.es.js +1 -1
  158. package/dist/{kbd-961zMPFB.js → kbd-PvdGhX4s.js} +1 -1
  159. package/dist/{link-preview-DIIbV2Sd.js → link-preview-DbM1866N.js} +1 -1
  160. package/dist/{link-DlZZBGaR.js → link-zF8PzWfF.js} +1 -1
  161. package/dist/{loader-YQszHFaW.js → loader-BM4CEz4G.js} +1 -1
  162. package/dist/{markdown-CVk1MPsj.js → markdown-LMvmAyBk.js} +7 -7
  163. package/dist/mcp.es.js +79 -5
  164. package/dist/{message-CwKVEdZg.js → message-BSsgvHvf.js} +1 -1
  165. package/dist/{message-DS8lzHc1.js → message-BYZECyek.js} +1 -1
  166. package/dist/message-circle-DruwObfX.js +1 -0
  167. package/dist/message-square-BG6q6mlm.js +1 -0
  168. package/dist/{minimize-2-UEZmP2QT.js → minimize-2-BQRoBV1V.js} +1 -1
  169. package/dist/{model-switcher-Kk1jEQ5t.js → model-switcher-CMpCOYcB.js} +1 -1
  170. package/dist/{overlay-C1od6LOz.js → overlay-Cb-MkyDa.js} +1 -1
  171. package/dist/{panel-right-ryET0yyn.js → panel-right-i8warFxT.js} +1 -1
  172. package/dist/{paperclip-BDNoZFJP.js → paperclip-ByY3UVO6.js} +1 -1
  173. package/dist/play-BvKKq3fX.js +1 -0
  174. package/dist/primitives/conversation-store.d.ts +54 -0
  175. package/dist/{progress-bar-BvYX0aoy.js → progress-bar-jVAHvMUf.js} +1 -1
  176. package/dist/{prompt-suggestion-BRFu2lcu.js → prompt-suggestion-CD-qtNgC.js} +1 -1
  177. package/dist/{radio-DcL23cNJ.js → radio-BsoBMhkF.js} +1 -1
  178. package/dist/react/index.d.ts +393 -0
  179. package/dist/react.js +20 -20
  180. package/dist/{reasoning-Cackq4Rh.js → reasoning-4kBPtlTx.js} +1 -1
  181. package/dist/register-impl-B1hLkrI8.js +424 -0
  182. package/dist/{resizable-Cp9HUYih.js → resizable-uPmY9VY-.js} +1 -1
  183. package/dist/{rotate-ccw-0DLRDHH5.js → rotate-ccw-B2xn93py.js} +1 -1
  184. package/dist/{rotate-cw-DOB9IGcU.js → rotate-cw-CMVLoCyH.js} +1 -1
  185. package/dist/{scroll-area-emVmWP4K.js → scroll-area-BqLUo8ZZ.js} +1 -1
  186. package/dist/{scroll-button-CeP_gv1x.js → scroll-button-DslXiJyL.js} +1 -1
  187. package/dist/{select-CWmu00tS.js → select-2cLRtPGT.js} +1 -1
  188. package/dist/{separator-CE6vly6z.js → separator-BWQtNJcM.js} +1 -1
  189. package/dist/{settings-CE-78SvZ.js → settings-BBR63jLg.js} +1 -1
  190. package/dist/{settings-group-ZFWNxFQR.js → settings-group-B2ciy-Iw.js} +1 -1
  191. package/dist/{skeleton-BebdP4mf.js → skeleton-CkHI-HgC.js} +1 -1
  192. package/dist/{slider-BAKRYUlz.js → slider-DDR9U-i0.js} +1 -1
  193. package/dist/{solid-BE5ui6sy.js → solid-BzCFdL25.js} +7720 -7058
  194. package/dist/{solid-DFhsce3O.js → solid-DruVOXnf.js} +5864 -5283
  195. package/dist/solid.d.ts +6 -2
  196. package/dist/solid.js +217 -210
  197. package/dist/solid.server.js +217 -210
  198. package/dist/{source-B0FlguSx.js → source-GEnFF528.js} +1 -1
  199. package/dist/{star-CBMZvLuN.js → star-CilHq8GP.js} +1 -1
  200. package/dist/store-Ciyk6eZG.js +1 -0
  201. package/dist/{switch-CViARZCy.js → switch-KkeQIbuH.js} +1 -1
  202. package/dist/{tasks-card-MjCcOhR_.js → tasks-card-BrN4bggJ.js} +1 -1
  203. package/dist/{text-shimmer-DKlwRF2G.js → text-shimmer-Dcj1MHuq.js} +1 -1
  204. package/dist/{textarea-3ay2Me2-.js → textarea-Dpvbngbg.js} +1 -1
  205. package/dist/theme.tokens.css +28 -0
  206. package/dist/{thumbs-up-DQcu2HMO.js → thumbs-up-CsT9Wzb9.js} +1 -1
  207. package/dist/{toast-store-C6yKOhbk.js → toast-store-tgK6MuMv.js} +1 -1
  208. package/dist/{tool-BCYNlfKy.js → tool-DzUjIBT8.js} +1 -1
  209. package/dist/{tooltip-05zI4hjs.js → tooltip-Bkkg_4t0.js} +1 -1
  210. package/dist/{trash-2-BR1RxGky.js → trash-2-jBKxjGGQ.js} +1 -1
  211. package/dist/{triangle-alert-DHohDOCy.js → triangle-alert-DlQ68ppf.js} +1 -1
  212. package/dist/types.d.ts +48 -3
  213. package/dist/ui/dock.d.ts +29 -0
  214. package/dist/{upload-BbGW6A_u.js → upload-TcAHXv9W.js} +1 -1
  215. package/dist/{url-scheme-policy-DHpoTJwB.js → url-scheme-policy-DR5DgvkM.js} +1 -1
  216. package/dist/{use-card-resolution-CJW4grFa.js → use-card-resolution-DaZqAdWf.js} +1 -1
  217. package/dist/{variant-aurora-xiMPty8f.js → variant-aurora-BhLuYxJR.js} +1 -1
  218. package/dist/{variant-aurora-BUOVgPOB.js → variant-aurora-CNmfHhcx.js} +2 -2
  219. package/dist/{variant-aurora-CUsUJvFI.js → variant-aurora-D6GsdTPA.js} +2 -2
  220. package/dist/{variant-aurora-CW2i4bfr.js → variant-aurora-DbW_OWL2.js} +1 -1
  221. package/dist/{variant-custom-D91HjAKz.js → variant-custom-BRvaNHSi.js} +1 -1
  222. package/dist/{variant-custom-BQtOnmeO.js → variant-custom-BvYHR62b.js} +1 -1
  223. package/dist/{variant-custom-CAnzzppW.js → variant-custom-DMTrEO5w.js} +2 -2
  224. package/dist/{variant-custom-CAm9L-E5.js → variant-custom-DV2x254s.js} +2 -2
  225. package/dist/{variant-wave-Ca1Ls4_y.js → variant-wave-BNMGrDfj.js} +2 -2
  226. package/dist/{variant-wave-uY5crU_K.js → variant-wave-BpDd8t3Z.js} +1 -1
  227. package/dist/{variant-wave-CxX39DKd.js → variant-wave-CyBueXj9.js} +2 -2
  228. package/dist/{variant-wave-DYyzIIZv.js → variant-wave-sp-qwsdI.js} +1 -1
  229. package/dist/x-Z5UntkIJ.js +1 -0
  230. package/frameworks/react/index.tsx +14 -4
  231. package/llms-full.txt +7 -2
  232. package/package.json +16 -16
  233. package/src/elements/element-meta.json +49 -6
  234. package/theme.css +28 -0
  235. package/bin/route.test.js +0 -58
  236. package/dist/arrow-down-Br0qtpsz.js +0 -1
  237. package/dist/arrow-left-Dj7xgE12.js +0 -1
  238. package/dist/check-DPeeMUYx.js +0 -1
  239. package/dist/chevron-down-CHSWk7ZT.js +0 -1
  240. package/dist/chevron-right-B7l_fMgx.js +0 -1
  241. package/dist/circle-Bb0yOpqM.js +0 -1
  242. package/dist/circle-check-BGZpCiz7.js +0 -1
  243. package/dist/code-block-CUQ5mtv0.js +0 -1
  244. package/dist/controllable-D43BKmtX.js +0 -1
  245. package/dist/conversation-list-D4lLU_yS.js +0 -1
  246. package/dist/define-CGGX-7Vg.js +0 -1
  247. package/dist/message-circle-Xf8Q-vZp.js +0 -1
  248. package/dist/message-square-Cxnwgkns.js +0 -1
  249. package/dist/play-OFIvYXUQ.js +0 -1
  250. package/dist/register-impl-C7rENvPK.js +0 -424
  251. package/dist/store-7eoadydQ.js +0 -1
  252. package/dist/x-C5m1JFgi.js +0 -1
  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 -131
  263. package/src/agent-tooling/construct/codegen.ts +0 -1353
  264. package/src/agent-tooling/construct/construct.v1.schema.json +0 -250
  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 -23
  269. package/src/agent-tooling/construct/schema.ts +0 -371
  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 -518
  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 -218
  336. package/src/components/conversation-list.tsx +0 -429
  337. package/src/components/dismissed-stub.tsx +0 -80
  338. package/src/components/embed.tsx +0 -200
  339. package/src/components/empty.tsx +0 -177
  340. package/src/components/feedback-bar.tsx +0 -166
  341. package/src/components/file-tree.tsx +0 -530
  342. package/src/components/file-upload.tsx +0 -167
  343. package/src/components/form-widgets.tsx +0 -525
  344. package/src/components/form.tsx +0 -1296
  345. package/src/components/image.tsx +0 -65
  346. package/src/components/link-preview.tsx +0 -195
  347. package/src/components/loader.tsx +0 -367
  348. package/src/components/markdown.tsx +0 -162
  349. package/src/components/message-skills.tsx +0 -40
  350. package/src/components/message.tsx +0 -765
  351. package/src/components/model-switcher.tsx +0 -132
  352. package/src/components/prompt-input.tsx +0 -188
  353. package/src/components/prompt-suggestion.tsx +0 -150
  354. package/src/components/reasoning.tsx +0 -293
  355. package/src/components/response-compare-types.ts +0 -152
  356. package/src/components/response-compare.tsx +0 -455
  357. package/src/components/response-stream.tsx +0 -109
  358. package/src/components/screen.tsx +0 -181
  359. package/src/components/scroll-button.tsx +0 -93
  360. package/src/components/source.tsx +0 -192
  361. package/src/components/tasks-card.tsx +0 -801
  362. package/src/components/text-shimmer.tsx +0 -37
  363. package/src/components/thinking-bar.tsx +0 -50
  364. package/src/components/thread.tsx +0 -228
  365. package/src/components/toast.tsx +0 -503
  366. package/src/components/tool-classify.ts +0 -26
  367. package/src/components/tool-types.ts +0 -49
  368. package/src/components/tool.tsx +0 -190
  369. package/src/components/use-card-resolution.ts +0 -44
  370. package/src/components/voice-input.tsx +0 -222
  371. package/src/components/voice-output.tsx +0 -242
  372. package/src/components/workspace-shell.tsx +0 -285
  373. package/src/diagnostics/hook.ts +0 -352
  374. package/src/diagnostics/index.ts +0 -82
  375. package/src/diagnostics/report-request.ts +0 -296
  376. package/src/elements/agent-card.tsx +0 -67
  377. package/src/elements/artifact.tsx +0 -194
  378. package/src/elements/attachments.tsx +0 -140
  379. package/src/elements/audio-visualizer.tsx +0 -227
  380. package/src/elements/autoloader.ts +0 -89
  381. package/src/elements/avatar.tsx +0 -33
  382. package/src/elements/badge.tsx +0 -32
  383. package/src/elements/button.tsx +0 -149
  384. package/src/elements/card.tsx +0 -133
  385. package/src/elements/cards.tsx +0 -266
  386. package/src/elements/chain-of-thought.tsx +0 -141
  387. package/src/elements/chat-scope-picker.tsx +0 -65
  388. package/src/elements/chat-types.ts +0 -102
  389. package/src/elements/chat-workspace.tsx +0 -188
  390. package/src/elements/chat.tsx +0 -212
  391. package/src/elements/checkbox-group.tsx +0 -164
  392. package/src/elements/checkbox.tsx +0 -134
  393. package/src/elements/checkpoint.tsx +0 -47
  394. package/src/elements/choice.tsx +0 -100
  395. package/src/elements/coachmark.tsx +0 -90
  396. package/src/elements/code-block.tsx +0 -60
  397. package/src/elements/command.tsx +0 -197
  398. package/src/elements/compare.tsx +0 -111
  399. package/src/elements/compiled.css +0 -2
  400. package/src/elements/composer.tsx +0 -162
  401. package/src/elements/confirm-card.tsx +0 -76
  402. package/src/elements/context-meter.tsx +0 -107
  403. package/src/elements/conversation-item.tsx +0 -120
  404. package/src/elements/conversation-list.tsx +0 -203
  405. package/src/elements/css.ts +0 -5
  406. package/src/elements/default-input.tsx +0 -392
  407. package/src/elements/define-entry.ts +0 -12
  408. package/src/elements/define.tsx +0 -563
  409. package/src/elements/diagnostic-events.ts +0 -114
  410. package/src/elements/dialog.tsx +0 -145
  411. package/src/elements/disclosure.ts +0 -95
  412. package/src/elements/dock.tsx +0 -215
  413. package/src/elements/dropdown.tsx +0 -123
  414. package/src/elements/editable-label.tsx +0 -140
  415. package/src/elements/element-data-types.ts +0 -152
  416. package/src/elements/element-diagnostics.ts +0 -392
  417. package/src/elements/element-manifest.json +0 -354
  418. package/src/elements/element-nonscalar.json +0 -185
  419. package/src/elements/element-types.d.ts +0 -4337
  420. package/src/elements/embed.tsx +0 -35
  421. package/src/elements/empty.tsx +0 -29
  422. package/src/elements/feedback-bar.tsx +0 -66
  423. package/src/elements/file-tree.tsx +0 -62
  424. package/src/elements/file-upload.tsx +0 -44
  425. package/src/elements/form.tsx +0 -94
  426. package/src/elements/hover-card.tsx +0 -80
  427. package/src/elements/icon.tsx +0 -41
  428. package/src/elements/image.tsx +0 -32
  429. package/src/elements/input.tsx +0 -492
  430. package/src/elements/kbd.tsx +0 -45
  431. package/src/elements/link-preview.tsx +0 -34
  432. package/src/elements/loader.tsx +0 -25
  433. package/src/elements/markdown.tsx +0 -38
  434. package/src/elements/menu.tsx +0 -241
  435. package/src/elements/message-skills.tsx +0 -83
  436. package/src/elements/message.tsx +0 -361
  437. package/src/elements/model-switcher.tsx +0 -121
  438. package/src/elements/nav.tsx +0 -87
  439. package/src/elements/notice.tsx +0 -53
  440. package/src/elements/pane-grid.tsx +0 -111
  441. package/src/elements/pane-group.tsx +0 -119
  442. package/src/elements/pane.tsx +0 -129
  443. package/src/elements/popover.tsx +0 -81
  444. package/src/elements/progress-bar.tsx +0 -39
  445. package/src/elements/prompt-dock.tsx +0 -82
  446. package/src/elements/prompt-input.tsx +0 -240
  447. package/src/elements/prompt-suggestions.tsx +0 -113
  448. package/src/elements/radio-group.tsx +0 -129
  449. package/src/elements/reasoning.tsx +0 -71
  450. package/src/elements/register-impl.ts +0 -148
  451. package/src/elements/register.ts +0 -76
  452. package/src/elements/remote.tsx +0 -225
  453. package/src/elements/resizable.globals.d.ts +0 -36
  454. package/src/elements/resizable.tsx +0 -704
  455. package/src/elements/response-stream.tsx +0 -40
  456. package/src/elements/screen.tsx +0 -90
  457. package/src/elements/scroll-area.tsx +0 -33
  458. package/src/elements/scroll-button.tsx +0 -179
  459. package/src/elements/search.tsx +0 -185
  460. package/src/elements/segmented.tsx +0 -129
  461. package/src/elements/select.tsx +0 -165
  462. package/src/elements/separator.tsx +0 -36
  463. package/src/elements/setting-item.tsx +0 -50
  464. package/src/elements/settings-group.tsx +0 -37
  465. package/src/elements/skeleton.tsx +0 -45
  466. package/src/elements/slider.tsx +0 -171
  467. package/src/elements/slot-text.ts +0 -73
  468. package/src/elements/slots.ts +0 -787
  469. package/src/elements/source.tsx +0 -126
  470. package/src/elements/status.tsx +0 -42
  471. package/src/elements/styles.css +0 -328
  472. package/src/elements/switch.tsx +0 -118
  473. package/src/elements/tabs.tsx +0 -95
  474. package/src/elements/tasks.tsx +0 -103
  475. package/src/elements/text-shimmer.tsx +0 -28
  476. package/src/elements/thinking-bar.tsx +0 -43
  477. package/src/elements/thread.tsx +0 -139
  478. package/src/elements/toast.tsx +0 -106
  479. package/src/elements/tool.tsx +0 -78
  480. package/src/elements/tooltip.tsx +0 -85
  481. package/src/elements/validate-messages.ts +0 -76
  482. package/src/elements/voice-input.tsx +0 -100
  483. package/src/elements/voice-output.tsx +0 -82
  484. package/src/index.ts +0 -428
  485. package/src/primitives/audio-bands.ts +0 -174
  486. package/src/primitives/card-component-types.ts +0 -63
  487. package/src/primitives/card-contract.ts +0 -80
  488. package/src/primitives/card-data-types.ts +0 -361
  489. package/src/primitives/card-host.tsx +0 -37
  490. package/src/primitives/card-recovery.ts +0 -160
  491. package/src/primitives/card-registry.tsx +0 -78
  492. package/src/primitives/card-resolution.ts +0 -36
  493. package/src/primitives/card-routing.ts +0 -85
  494. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  495. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  496. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  497. package/src/primitives/card-schemas/choice.schema.json +0 -75
  498. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  499. package/src/primitives/card-schemas/embed.schema.json +0 -63
  500. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  501. package/src/primitives/card-schemas/form.schema.json +0 -53
  502. package/src/primitives/card-schemas/link.schema.json +0 -54
  503. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  504. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  505. package/src/primitives/card-tags.ts +0 -61
  506. package/src/primitives/card-validate-cards.ts +0 -255
  507. package/src/primitives/card-validate-schemas.ts +0 -58
  508. package/src/primitives/card-validate.ts +0 -131
  509. package/src/primitives/chat-config.tsx +0 -103
  510. package/src/primitives/composer-model.ts +0 -49
  511. package/src/primitives/composer-triggers.ts +0 -35
  512. package/src/primitives/controllable.ts +0 -21
  513. package/src/primitives/create-kai-chat.ts +0 -66
  514. package/src/primitives/create-tween.ts +0 -266
  515. package/src/primitives/embed-providers.ts +0 -254
  516. package/src/primitives/field-mask.ts +0 -256
  517. package/src/primitives/field-semantics.ts +0 -115
  518. package/src/primitives/highlighter.ts +0 -157
  519. package/src/primitives/input-mask.ts +0 -853
  520. package/src/primitives/link-preview.ts +0 -87
  521. package/src/primitives/message-feedback.ts +0 -124
  522. package/src/primitives/pdf-preview.ts +0 -121
  523. package/src/primitives/toast-store.ts +0 -338
  524. package/src/primitives/url-scheme-policy.ts +0 -70
  525. package/src/primitives/use-audio-analysis.ts +0 -340
  526. package/src/primitives/use-auto-resize.ts +0 -31
  527. package/src/primitives/use-resize-observer.ts +0 -45
  528. package/src/primitives/use-sequencer.ts +0 -72
  529. package/src/primitives/use-speech-recognition.ts +0 -146
  530. package/src/primitives/use-stick-to-bottom.ts +0 -75
  531. package/src/primitives/use-text-stream.ts +0 -143
  532. package/src/primitives/use-voice-recorder.ts +0 -62
  533. package/src/primitives/visualizer-sequences.ts +0 -350
  534. package/src/remote/host-embed.ts +0 -345
  535. package/src/remote/index.ts +0 -2
  536. package/src/remote/origin.ts +0 -30
  537. package/src/remote/provider-runtime.ts +0 -262
  538. package/src/remote/provider.ts +0 -2
  539. package/src/remote/validate.ts +0 -22
  540. package/src/remote/version.ts +0 -12
  541. package/src/remote/wire.ts +0 -48
  542. package/src/schemas/from-tool-call.ts +0 -151
  543. package/src/schemas/index.ts +0 -341
  544. package/src/schemas/provider-subsets.ts +0 -538
  545. package/src/schemas/registry.ts +0 -355
  546. package/src/schemas/tool-defs.ts +0 -793
  547. package/src/solid.ts +0 -165
  548. package/src/state/index.ts +0 -44
  549. package/src/state/messages.ts +0 -53
  550. package/src/state/mock.ts +0 -297
  551. package/src/state/parts.ts +0 -295
  552. package/src/state/persistence.ts +0 -180
  553. package/src/state/stream.ts +0 -246
  554. package/src/state/suggestions.ts +0 -9
  555. package/src/state/threads.ts +0 -142
  556. package/src/test-utils/fake-clock.ts +0 -88
  557. package/src/types.ts +0 -53
  558. package/src/ui/action-icons.ts +0 -53
  559. package/src/ui/agent-card.tsx +0 -218
  560. package/src/ui/avatar.tsx +0 -23
  561. package/src/ui/badge.tsx +0 -21
  562. package/src/ui/button.tsx +0 -49
  563. package/src/ui/card.tsx +0 -245
  564. package/src/ui/checkbox-group.tsx +0 -153
  565. package/src/ui/checkbox.tsx +0 -63
  566. package/src/ui/collapsible.tsx +0 -163
  567. package/src/ui/command.tsx +0 -103
  568. package/src/ui/dialog.tsx +0 -223
  569. package/src/ui/dock.tsx +0 -584
  570. package/src/ui/dropdown.tsx +0 -636
  571. package/src/ui/editable-label.tsx +0 -112
  572. package/src/ui/hover-card.tsx +0 -345
  573. package/src/ui/icon.tsx +0 -167
  574. package/src/ui/input.tsx +0 -391
  575. package/src/ui/kbd.tsx +0 -118
  576. package/src/ui/nav.tsx +0 -282
  577. package/src/ui/notice.tsx +0 -84
  578. package/src/ui/overlay.tsx +0 -277
  579. package/src/ui/pane-grid.tsx +0 -117
  580. package/src/ui/pane-group.tsx +0 -291
  581. package/src/ui/pane.tsx +0 -193
  582. package/src/ui/popover.tsx +0 -106
  583. package/src/ui/progress-bar.tsx +0 -83
  584. package/src/ui/prompt-dock.tsx +0 -145
  585. package/src/ui/radio.tsx +0 -150
  586. package/src/ui/resizable.tsx +0 -693
  587. package/src/ui/scroll-area.tsx +0 -26
  588. package/src/ui/segmented.tsx +0 -109
  589. package/src/ui/select.tsx +0 -168
  590. package/src/ui/separator.tsx +0 -10
  591. package/src/ui/settings-group.tsx +0 -67
  592. package/src/ui/skeleton.tsx +0 -73
  593. package/src/ui/slider.tsx +0 -178
  594. package/src/ui/status.tsx +0 -48
  595. package/src/ui/switch.tsx +0 -119
  596. package/src/ui/tabs.tsx +0 -168
  597. package/src/ui/textarea.tsx +0 -21
  598. package/src/ui/tooltip.tsx +0 -118
  599. package/src/utils/cn.ts +0 -30
  600. package/src/wire/chunk.ts +0 -248
  601. package/src/wire/consume.ts +0 -599
  602. package/src/wire/diagnostics.ts +0 -727
  603. package/src/wire/encode-probe.ts +0 -214
  604. package/src/wire/encode.ts +0 -947
  605. package/src/wire/files.ts +0 -342
  606. package/src/wire/formats/anthropic.ts +0 -274
  607. package/src/wire/formats/openai.ts +0 -228
  608. package/src/wire/index.ts +0 -95
  609. package/src/wire/media-types.ts +0 -344
  610. package/src/wire/read.ts +0 -396
  611. package/src/wire/sink-helpers.ts +0 -50
  612. 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
- }