knobkit 0.0.1

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 (361) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/cell-renderer-CLTRlCa5-DIlwS99c.js +1 -0
  3. package/dist/assets/chart-D8ctp-_1.js +36 -0
  4. package/dist/assets/code-BMuLQBYq.js +2 -0
  5. package/dist/assets/column.service-C6hByxPy-XG9X0y3N.js +1 -0
  6. package/dist/assets/debounce-PCRWZliA-BjJpj_P7.js +32 -0
  7. package/dist/assets/dimension.helpers-CGKwSvw6-D_czicbS.js +1 -0
  8. package/dist/assets/dist-1hsZpGRf.js +23 -0
  9. package/dist/assets/dist-B-y4Etc5.js +1 -0
  10. package/dist/assets/dist-B8BXgMDk.js +10 -0
  11. package/dist/assets/dist-BJlXPLNt.js +1 -0
  12. package/dist/assets/dist-ByhR2UY_.js +1 -0
  13. package/dist/assets/dist-C0bxYHYH.js +2 -0
  14. package/dist/assets/dist-C8dagUDy.js +6 -0
  15. package/dist/assets/dist-CtLpohkg.js +1 -0
  16. package/dist/assets/dist-D00mNtIr.js +1 -0
  17. package/dist/assets/dist-Dh1Dvy3h.js +1 -0
  18. package/dist/assets/dist-DlwQ1Qqm.js +1 -0
  19. package/dist/assets/dist-DtZDI7jp.js +1 -0
  20. package/dist/assets/dist-thZFs69d.js +9 -0
  21. package/dist/assets/edit.utils-Dnnbd0xG-OAxDw8WC.js +1 -0
  22. package/dist/assets/events-BvSmBueA-4kqQ57iN.js +1 -0
  23. package/dist/assets/filter.button-BFwo1uvz-CyvQhOO5.js +1 -0
  24. package/dist/assets/header-cell-renderer-BMmXRsd_-BHbC7fao.js +1 -0
  25. package/dist/assets/index-Db3qZoW5-peeY7EGw.js +1 -0
  26. package/dist/assets/markdown-dDCgur7g.js +29 -0
  27. package/dist/assets/revo-grid.entry-CfI6s-uT.js +1 -0
  28. package/dist/assets/revogr-attribution_7.entry-6fUjzImt.js +1 -0
  29. package/dist/assets/revogr-clipboard_3.entry-DmI7LkER.js +2 -0
  30. package/dist/assets/revogr-data_4.entry-CYZIiXNw.js +1 -0
  31. package/dist/assets/revogr-filter-panel.entry-TmQHTQxw.js +1 -0
  32. package/dist/assets/table-Zn7rpfG-.js +1 -0
  33. package/dist/assets/text-editor-C3RUSwH5-DuDr9wKc.js +1 -0
  34. package/dist/assets/theme.service-BmnDvr6P-DftEgmbe.js +3 -0
  35. package/dist/assets/throttle-CaUDyxyU-Djj__DCp.js +1 -0
  36. package/dist/assets/viewport.helpers-CoCAvmZs-ByVRjjkF.js +1 -0
  37. package/dist/assets/viewport.store-_c579YyM-B_ZSqqka.js +1 -0
  38. package/dist/cli/config.d.ts +5 -0
  39. package/dist/cli/config.js +82 -0
  40. package/dist/cli/index.d.ts +2 -0
  41. package/dist/cli/index.js +77 -0
  42. package/dist/cli/mount.d.ts +2 -0
  43. package/dist/cli/mount.js +26 -0
  44. package/dist/cli/serve.d.ts +1 -0
  45. package/dist/cli/serve.js +21 -0
  46. package/dist/client/app.d.ts +8 -0
  47. package/dist/client/app.js +40 -0
  48. package/dist/client/context.d.ts +2 -0
  49. package/dist/client/context.js +16 -0
  50. package/dist/client/mount.d.ts +3 -0
  51. package/dist/client/mount.js +42 -0
  52. package/dist/client/runtime.d.ts +19 -0
  53. package/dist/client/runtime.js +88 -0
  54. package/dist/client/view.d.ts +12 -0
  55. package/dist/client/view.js +1 -0
  56. package/dist/client/widgets/accordion/index.d.ts +5 -0
  57. package/dist/client/widgets/accordion/index.js +8 -0
  58. package/dist/client/widgets/annotated-image/index.d.ts +8 -0
  59. package/dist/client/widgets/annotated-image/index.js +34 -0
  60. package/dist/client/widgets/audio/index.d.ts +5 -0
  61. package/dist/client/widgets/audio/index.js +5 -0
  62. package/dist/client/widgets/button/index.d.ts +4 -0
  63. package/dist/client/widgets/button/index.js +5 -0
  64. package/dist/client/widgets/chart/index.d.ts +5 -0
  65. package/dist/client/widgets/chart/index.js +23 -0
  66. package/dist/client/widgets/chart/lazy.d.ts +3 -0
  67. package/dist/client/widgets/chart/lazy.js +9 -0
  68. package/dist/client/widgets/chat/index.d.ts +6 -0
  69. package/dist/client/widgets/chat/index.js +77 -0
  70. package/dist/client/widgets/checkbox/index.d.ts +6 -0
  71. package/dist/client/widgets/checkbox/index.js +9 -0
  72. package/dist/client/widgets/checkbox-group/index.d.ts +6 -0
  73. package/dist/client/widgets/checkbox-group/index.js +12 -0
  74. package/dist/client/widgets/code/index.d.ts +5 -0
  75. package/dist/client/widgets/code/index.js +101 -0
  76. package/dist/client/widgets/code/lazy.d.ts +3 -0
  77. package/dist/client/widgets/code/lazy.js +10 -0
  78. package/dist/client/widgets/dropdown/index.d.ts +5 -0
  79. package/dist/client/widgets/dropdown/index.js +9 -0
  80. package/dist/client/widgets/file/index.d.ts +6 -0
  81. package/dist/client/widgets/file/index.js +7 -0
  82. package/dist/client/widgets/frame/index.d.ts +6 -0
  83. package/dist/client/widgets/frame/index.js +11 -0
  84. package/dist/client/widgets/gallery/index.d.ts +6 -0
  85. package/dist/client/widgets/gallery/index.js +8 -0
  86. package/dist/client/widgets/highlighted-text/index.d.ts +7 -0
  87. package/dist/client/widgets/highlighted-text/index.js +20 -0
  88. package/dist/client/widgets/html/index.d.ts +4 -0
  89. package/dist/client/widgets/html/index.js +8 -0
  90. package/dist/client/widgets/image/index.d.ts +4 -0
  91. package/dist/client/widgets/image/index.js +4 -0
  92. package/dist/client/widgets/json/index.d.ts +4 -0
  93. package/dist/client/widgets/json/index.js +4 -0
  94. package/dist/client/widgets/label/index.d.ts +7 -0
  95. package/dist/client/widgets/label/index.js +8 -0
  96. package/dist/client/widgets/layout/index.d.ts +4 -0
  97. package/dist/client/widgets/layout/index.js +7 -0
  98. package/dist/client/widgets/log/index.d.ts +4 -0
  99. package/dist/client/widgets/log/index.js +4 -0
  100. package/dist/client/widgets/mic/index.d.ts +6 -0
  101. package/dist/client/widgets/mic/index.js +70 -0
  102. package/dist/client/widgets/number/index.d.ts +5 -0
  103. package/dist/client/widgets/number/index.js +8 -0
  104. package/dist/client/widgets/output/index.d.ts +6 -0
  105. package/dist/client/widgets/output/index.js +13 -0
  106. package/dist/client/widgets/output/markdown.d.ts +3 -0
  107. package/dist/client/widgets/output/markdown.js +8 -0
  108. package/dist/client/widgets/progress/index.d.ts +6 -0
  109. package/dist/client/widgets/progress/index.js +6 -0
  110. package/dist/client/widgets/radio/index.d.ts +6 -0
  111. package/dist/client/widgets/radio/index.js +10 -0
  112. package/dist/client/widgets/registry.d.ts +2 -0
  113. package/dist/client/widgets/registry.js +69 -0
  114. package/dist/client/widgets/slider/index.d.ts +6 -0
  115. package/dist/client/widgets/slider/index.js +9 -0
  116. package/dist/client/widgets/table/index.d.ts +6 -0
  117. package/dist/client/widgets/table/index.js +72 -0
  118. package/dist/client/widgets/table/lazy.d.ts +3 -0
  119. package/dist/client/widgets/table/lazy.js +16 -0
  120. package/dist/client/widgets/tabs/index.d.ts +5 -0
  121. package/dist/client/widgets/tabs/index.js +10 -0
  122. package/dist/client/widgets/text/index.d.ts +6 -0
  123. package/dist/client/widgets/text/index.js +10 -0
  124. package/dist/client/widgets/upload/index.d.ts +6 -0
  125. package/dist/client/widgets/upload/index.js +16 -0
  126. package/dist/client/widgets/video/index.d.ts +5 -0
  127. package/dist/client/widgets/video/index.js +5 -0
  128. package/dist/client/widgets/webcam/index.d.ts +6 -0
  129. package/dist/client/widgets/webcam/index.js +62 -0
  130. package/dist/client.css +1 -0
  131. package/dist/client.js +11 -0
  132. package/dist/knobkit.browser.css +2 -0
  133. package/dist/knobkit.browser.js +151 -0
  134. package/dist/lib/bound.d.ts +12 -0
  135. package/dist/lib/bound.js +10 -0
  136. package/dist/lib/controls.d.ts +9 -0
  137. package/dist/lib/controls.js +35 -0
  138. package/dist/lib/ctx.d.ts +9 -0
  139. package/dist/lib/ctx.js +22 -0
  140. package/dist/lib/declare.d.ts +18 -0
  141. package/dist/lib/declare.js +43 -0
  142. package/dist/lib/event.d.ts +2 -0
  143. package/dist/lib/event.js +9 -0
  144. package/dist/lib/index.d.ts +10 -0
  145. package/dist/lib/index.js +6 -0
  146. package/dist/lib/knobkit.d.ts +19 -0
  147. package/dist/lib/knobkit.js +48 -0
  148. package/dist/lib/on.d.ts +2 -0
  149. package/dist/lib/on.js +1 -0
  150. package/dist/lib/stream.d.ts +1 -0
  151. package/dist/lib/stream.js +33 -0
  152. package/dist/lib/types.d.ts +48 -0
  153. package/dist/lib/types.js +1 -0
  154. package/dist/lib/widget.d.ts +7 -0
  155. package/dist/lib/widget.js +4 -0
  156. package/dist/lib/widgets/annotated-image.d.ts +14 -0
  157. package/dist/lib/widgets/annotated-image.js +16 -0
  158. package/dist/lib/widgets/audio.d.ts +10 -0
  159. package/dist/lib/widgets/audio.js +13 -0
  160. package/dist/lib/widgets/button.d.ts +11 -0
  161. package/dist/lib/widgets/button.js +17 -0
  162. package/dist/lib/widgets/chart.d.ts +20 -0
  163. package/dist/lib/widgets/chart.js +24 -0
  164. package/dist/lib/widgets/chat.d.ts +26 -0
  165. package/dist/lib/widgets/chat.js +24 -0
  166. package/dist/lib/widgets/checkbox-group.d.ts +4 -0
  167. package/dist/lib/widgets/checkbox-group.js +5 -0
  168. package/dist/lib/widgets/checkbox.d.ts +4 -0
  169. package/dist/lib/widgets/checkbox.js +4 -0
  170. package/dist/lib/widgets/code.d.ts +5 -0
  171. package/dist/lib/widgets/code.js +10 -0
  172. package/dist/lib/widgets/dropdown.d.ts +4 -0
  173. package/dist/lib/widgets/dropdown.js +4 -0
  174. package/dist/lib/widgets/embed.d.ts +5 -0
  175. package/dist/lib/widgets/embed.js +23 -0
  176. package/dist/lib/widgets/file.d.ts +11 -0
  177. package/dist/lib/widgets/file.js +15 -0
  178. package/dist/lib/widgets/frame.d.ts +18 -0
  179. package/dist/lib/widgets/frame.js +25 -0
  180. package/dist/lib/widgets/gallery.d.ts +12 -0
  181. package/dist/lib/widgets/gallery.js +17 -0
  182. package/dist/lib/widgets/highlighted-text.d.ts +12 -0
  183. package/dist/lib/widgets/highlighted-text.js +15 -0
  184. package/dist/lib/widgets/html.d.ts +9 -0
  185. package/dist/lib/widgets/html.js +12 -0
  186. package/dist/lib/widgets/image.d.ts +7 -0
  187. package/dist/lib/widgets/image.js +12 -0
  188. package/dist/lib/widgets/index.d.ts +31 -0
  189. package/dist/lib/widgets/index.js +31 -0
  190. package/dist/lib/widgets/json.d.ts +7 -0
  191. package/dist/lib/widgets/json.js +12 -0
  192. package/dist/lib/widgets/label.d.ts +15 -0
  193. package/dist/lib/widgets/label.js +18 -0
  194. package/dist/lib/widgets/layout.d.ts +21 -0
  195. package/dist/lib/widgets/layout.js +29 -0
  196. package/dist/lib/widgets/log.d.ts +8 -0
  197. package/dist/lib/widgets/log.js +15 -0
  198. package/dist/lib/widgets/mic.d.ts +19 -0
  199. package/dist/lib/widgets/mic.js +28 -0
  200. package/dist/lib/widgets/number.d.ts +5 -0
  201. package/dist/lib/widgets/number.js +4 -0
  202. package/dist/lib/widgets/output.d.ts +10 -0
  203. package/dist/lib/widgets/output.js +13 -0
  204. package/dist/lib/widgets/progress.d.ts +10 -0
  205. package/dist/lib/widgets/progress.js +15 -0
  206. package/dist/lib/widgets/radio.d.ts +4 -0
  207. package/dist/lib/widgets/radio.js +5 -0
  208. package/dist/lib/widgets/slider.d.ts +6 -0
  209. package/dist/lib/widgets/slider.js +6 -0
  210. package/dist/lib/widgets/table.d.ts +32 -0
  211. package/dist/lib/widgets/table.js +36 -0
  212. package/dist/lib/widgets/text.d.ts +4 -0
  213. package/dist/lib/widgets/text.js +4 -0
  214. package/dist/lib/widgets/upload.d.ts +3 -0
  215. package/dist/lib/widgets/upload.js +5 -0
  216. package/dist/lib/widgets/value.d.ts +9 -0
  217. package/dist/lib/widgets/value.js +20 -0
  218. package/dist/lib/widgets/video.d.ts +12 -0
  219. package/dist/lib/widgets/video.js +14 -0
  220. package/dist/lib/widgets/webcam.d.ts +21 -0
  221. package/dist/lib/widgets/webcam.js +29 -0
  222. package/dist/server/context.d.ts +2 -0
  223. package/dist/server/context.js +10 -0
  224. package/dist/server/serve.d.ts +5 -0
  225. package/dist/server/serve.js +131 -0
  226. package/package.json +71 -0
  227. package/src/cli/config.ts +83 -0
  228. package/src/cli/index.ts +82 -0
  229. package/src/cli/mount.ts +25 -0
  230. package/src/cli/serve.ts +22 -0
  231. package/src/client/app.test.tsx +70 -0
  232. package/src/client/app.tsx +62 -0
  233. package/src/client/browser-runtime.test.ts +22 -0
  234. package/src/client/browser.ts +3 -0
  235. package/src/client/context.ts +17 -0
  236. package/src/client/embed.test.tsx +58 -0
  237. package/src/client/entry.tsx +25 -0
  238. package/src/client/mount.test.tsx +36 -0
  239. package/src/client/mount.tsx +48 -0
  240. package/src/client/runtime.test.ts +64 -0
  241. package/src/client/runtime.ts +112 -0
  242. package/src/client/serve-stub.ts +3 -0
  243. package/src/client/styles.css +131 -0
  244. package/src/client/view.ts +16 -0
  245. package/src/client/widgets/accordion/accordion.css +35 -0
  246. package/src/client/widgets/accordion/index.tsx +17 -0
  247. package/src/client/widgets/annotated-image/annotated-image.css +62 -0
  248. package/src/client/widgets/annotated-image/index.tsx +73 -0
  249. package/src/client/widgets/audio/audio.css +6 -0
  250. package/src/client/widgets/audio/index.tsx +6 -0
  251. package/src/client/widgets/button/button.css +25 -0
  252. package/src/client/widgets/button/index.tsx +11 -0
  253. package/src/client/widgets/chart/chart.css +12 -0
  254. package/src/client/widgets/chart/index.tsx +63 -0
  255. package/src/client/widgets/chart/lazy.tsx +15 -0
  256. package/src/client/widgets/chat/chat.css +97 -0
  257. package/src/client/widgets/chat/index.tsx +121 -0
  258. package/src/client/widgets/checkbox/checkbox.css +15 -0
  259. package/src/client/widgets/checkbox/index.tsx +15 -0
  260. package/src/client/widgets/checkbox-group/checkbox-group.css +20 -0
  261. package/src/client/widgets/checkbox-group/index.tsx +22 -0
  262. package/src/client/widgets/code/code.css +31 -0
  263. package/src/client/widgets/code/index.tsx +108 -0
  264. package/src/client/widgets/code/lazy.tsx +16 -0
  265. package/src/client/widgets/dropdown/dropdown.css +0 -0
  266. package/src/client/widgets/dropdown/index.tsx +19 -0
  267. package/src/client/widgets/file/file.css +26 -0
  268. package/src/client/widgets/file/index.tsx +12 -0
  269. package/src/client/widgets/frame/frame.css +17 -0
  270. package/src/client/widgets/frame/index.tsx +15 -0
  271. package/src/client/widgets/gallery/gallery.css +26 -0
  272. package/src/client/widgets/gallery/index.tsx +18 -0
  273. package/src/client/widgets/highlighted-text/highlighted-text.css +21 -0
  274. package/src/client/widgets/highlighted-text/index.tsx +42 -0
  275. package/src/client/widgets/html/index.tsx +8 -0
  276. package/src/client/widgets/image/index.tsx +5 -0
  277. package/src/client/widgets/json/index.tsx +5 -0
  278. package/src/client/widgets/json/json.css +0 -0
  279. package/src/client/widgets/label/index.tsx +20 -0
  280. package/src/client/widgets/label/label.css +39 -0
  281. package/src/client/widgets/layout/index.tsx +14 -0
  282. package/src/client/widgets/log/index.tsx +5 -0
  283. package/src/client/widgets/log/log.css +0 -0
  284. package/src/client/widgets/mic/index.tsx +85 -0
  285. package/src/client/widgets/mic/mic.css +8 -0
  286. package/src/client/widgets/number/index.tsx +10 -0
  287. package/src/client/widgets/number/number.css +0 -0
  288. package/src/client/widgets/output/index.tsx +19 -0
  289. package/src/client/widgets/output/markdown.tsx +12 -0
  290. package/src/client/widgets/output/output.css +75 -0
  291. package/src/client/widgets/progress/index.tsx +14 -0
  292. package/src/client/widgets/progress/progress.css +26 -0
  293. package/src/client/widgets/radio/index.tsx +20 -0
  294. package/src/client/widgets/radio/radio.css +20 -0
  295. package/src/client/widgets/registry.tsx +71 -0
  296. package/src/client/widgets/slider/index.tsx +23 -0
  297. package/src/client/widgets/slider/slider.css +18 -0
  298. package/src/client/widgets/table/index.tsx +95 -0
  299. package/src/client/widgets/table/lazy.tsx +23 -0
  300. package/src/client/widgets/table/table.css +15 -0
  301. package/src/client/widgets/tabs/index.tsx +28 -0
  302. package/src/client/widgets/tabs/tabs.css +30 -0
  303. package/src/client/widgets/text/index.tsx +16 -0
  304. package/src/client/widgets/text/text.css +21 -0
  305. package/src/client/widgets/upload/index.tsx +30 -0
  306. package/src/client/widgets/upload/upload.css +30 -0
  307. package/src/client/widgets/video/index.tsx +10 -0
  308. package/src/client/widgets/video/video.css +8 -0
  309. package/src/client/widgets/webcam/index.tsx +81 -0
  310. package/src/client/widgets/webcam/webcam.css +46 -0
  311. package/src/css.d.ts +1 -0
  312. package/src/env.d.ts +1 -0
  313. package/src/lib/bound.ts +30 -0
  314. package/src/lib/controls.ts +36 -0
  315. package/src/lib/ctx.ts +31 -0
  316. package/src/lib/declare.test.ts +46 -0
  317. package/src/lib/declare.ts +74 -0
  318. package/src/lib/event.ts +12 -0
  319. package/src/lib/index.ts +21 -0
  320. package/src/lib/knobkit.ts +57 -0
  321. package/src/lib/on.ts +3 -0
  322. package/src/lib/stream.ts +38 -0
  323. package/src/lib/types.ts +63 -0
  324. package/src/lib/widget.ts +11 -0
  325. package/src/lib/widgets/annotated-image.ts +34 -0
  326. package/src/lib/widgets/audio.ts +20 -0
  327. package/src/lib/widgets/button.ts +27 -0
  328. package/src/lib/widgets/chart.ts +44 -0
  329. package/src/lib/widgets/chat.ts +43 -0
  330. package/src/lib/widgets/checkbox-group.ts +6 -0
  331. package/src/lib/widgets/checkbox.ts +5 -0
  332. package/src/lib/widgets/code.ts +11 -0
  333. package/src/lib/widgets/dropdown.ts +5 -0
  334. package/src/lib/widgets/embed.ts +26 -0
  335. package/src/lib/widgets/file.ts +23 -0
  336. package/src/lib/widgets/frame.ts +36 -0
  337. package/src/lib/widgets/gallery.ts +29 -0
  338. package/src/lib/widgets/highlighted-text.ts +29 -0
  339. package/src/lib/widgets/html.ts +18 -0
  340. package/src/lib/widgets/image.ts +18 -0
  341. package/src/lib/widgets/index.ts +31 -0
  342. package/src/lib/widgets/json.ts +18 -0
  343. package/src/lib/widgets/label.ts +29 -0
  344. package/src/lib/widgets/layout.ts +47 -0
  345. package/src/lib/widgets/log.ts +22 -0
  346. package/src/lib/widgets/mic.ts +42 -0
  347. package/src/lib/widgets/number.ts +5 -0
  348. package/src/lib/widgets/output.ts +20 -0
  349. package/src/lib/widgets/progress.ts +21 -0
  350. package/src/lib/widgets/radio.ts +6 -0
  351. package/src/lib/widgets/slider.ts +7 -0
  352. package/src/lib/widgets/table.ts +58 -0
  353. package/src/lib/widgets/text.ts +5 -0
  354. package/src/lib/widgets/upload.ts +6 -0
  355. package/src/lib/widgets/value.ts +28 -0
  356. package/src/lib/widgets/video.ts +22 -0
  357. package/src/lib/widgets/webcam.ts +46 -0
  358. package/src/server/context.ts +12 -0
  359. package/src/server/serve.test.ts +121 -0
  360. package/src/server/serve.ts +130 -0
  361. package/tsconfig.base.json +14 -0
@@ -0,0 +1,9 @@
1
+ import type { EventCtor, Widget } from "../types.js";
2
+ export interface ValueWidget<S> extends Widget<{
3
+ value: S;
4
+ }> {
5
+ changed: EventCtor<S>;
6
+ value(): Promise<S>;
7
+ set(value: S): void;
8
+ }
9
+ export declare function value<S>(type: string, initial: S, props?: Record<string, unknown>): ValueWidget<S>;
@@ -0,0 +1,20 @@
1
+ import { event } from "../event.js";
2
+ import { bound } from "../bound.js";
3
+ import { controls } from "../controls.js";
4
+ // A self-managing value input: it holds a single `value` attribute; its view (in `client`) edits that
5
+ // value locally as the user types and emits `changed` for handlers that care.
6
+ export function value(type, initial, props = {}) {
7
+ return {
8
+ type,
9
+ state: { value: initial },
10
+ changed: event(`${type}.changed`),
11
+ ...controls,
12
+ value() {
13
+ return bound(this).read(this, ["value"]);
14
+ },
15
+ set(v) {
16
+ bound(this).edit(this, "set", ["value"], v);
17
+ },
18
+ ...props,
19
+ };
20
+ }
@@ -0,0 +1,12 @@
1
+ import type { Widget } from "../types.js";
2
+ export interface VideoWidget extends Widget<{
3
+ src: string;
4
+ }> {
5
+ autoplay: boolean;
6
+ loop: boolean;
7
+ set(value: string): void;
8
+ }
9
+ export declare function video(opts?: {
10
+ autoplay?: boolean;
11
+ loop?: boolean;
12
+ }): VideoWidget;
@@ -0,0 +1,14 @@
1
+ import { bound } from "../bound.js";
2
+ import { controls } from "../controls.js";
3
+ export function video(opts = {}) {
4
+ return {
5
+ type: "video",
6
+ state: { src: "" },
7
+ autoplay: opts.autoplay ?? false,
8
+ loop: opts.loop ?? false,
9
+ ...controls,
10
+ set(value) {
11
+ bound(this).edit(this, "set", ["src"], value);
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,21 @@
1
+ import type { EventCtor, Widget } from "../types.js";
2
+ export interface WebcamWidget extends Widget<{
3
+ live: boolean;
4
+ }> {
5
+ frame: EventCtor<string>;
6
+ toggled: EventCtor<boolean>;
7
+ every: number;
8
+ control: boolean;
9
+ preview: boolean;
10
+ facing: "user" | "environment";
11
+ start(): void;
12
+ stop(): void;
13
+ toggle(): Promise<void>;
14
+ live(): Promise<boolean>;
15
+ }
16
+ export declare function webcam(opts?: {
17
+ every?: number;
18
+ control?: boolean;
19
+ preview?: boolean;
20
+ facing?: "user" | "environment";
21
+ }): WebcamWidget;
@@ -0,0 +1,29 @@
1
+ import { event } from "../event.js";
2
+ import { bound } from "../bound.js";
3
+ import { controls } from "../controls.js";
4
+ export function webcam(opts = {}) {
5
+ return {
6
+ type: "webcam",
7
+ state: { live: false },
8
+ frame: event("webcam.frame"),
9
+ toggled: event("webcam.toggled"),
10
+ every: opts.every ?? 0, // 0 = preview only; >0 = a frame every N ms
11
+ control: opts.control ?? true,
12
+ preview: opts.preview ?? true,
13
+ facing: opts.facing ?? "user",
14
+ ...controls,
15
+ start() {
16
+ bound(this).edit(this, "set", ["live"], true);
17
+ },
18
+ stop() {
19
+ bound(this).edit(this, "set", ["live"], false);
20
+ },
21
+ async toggle() {
22
+ const live = await bound(this).read(this, ["live"]);
23
+ bound(this).edit(this, "set", ["live"], !live);
24
+ },
25
+ live() {
26
+ return bound(this).read(this, ["live"]);
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,2 @@
1
+ import { type Bound } from "../lib/bound.js";
2
+ export declare function run<T>(bound: Bound, fn: () => T): T;
@@ -0,0 +1,10 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { setBoundResolver } from "../lib/bound.js";
3
+ // Per-request context binding for handlers. Each socket request runs inside `run(bound, …)`, so a
4
+ // widget method's `bound(this)` resolves to that request's snapshot reader / event sender even across
5
+ // awaits and concurrent requests.
6
+ const store = new AsyncLocalStorage();
7
+ setBoundResolver(() => store.getStore());
8
+ export function run(bound, fn) {
9
+ return store.run(bound, fn);
10
+ }
@@ -0,0 +1,5 @@
1
+ import type { KnobkitServer } from "../lib/types.js";
2
+ import type { Knobkit } from "../lib/knobkit.js";
3
+ export declare function serve(knobkit: Knobkit, opts?: {
4
+ port?: number;
5
+ }): Promise<KnobkitServer>;
@@ -0,0 +1,131 @@
1
+ import { createServer } from "node:http";
2
+ import { fileURLToPath } from "node:url";
3
+ import { resolve, sep } from "node:path";
4
+ import { readFile } from "node:fs/promises";
5
+ import { Server } from "socket.io";
6
+ import { declare } from "../lib/declare.js";
7
+ import { makeBound } from "../lib/ctx.js";
8
+ import { run } from "./context.js";
9
+ const CLIENT_JS = fileURLToPath(new URL("../../dist/client.js", import.meta.url));
10
+ const CLIENT_CSS = fileURLToPath(new URL("../../dist/client.css", import.meta.url));
11
+ const ASSETS = fileURLToPath(new URL("../../dist/assets", import.meta.url));
12
+ const isEvent = (x) => x != null && typeof x.type === "string" && "payload" in x;
13
+ function html(decl, loading) {
14
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" />
15
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
16
+ <title>${decl.title ?? "knobkit"}</title><link rel="stylesheet" href="/client.css" /></head>
17
+ <body><div id="root">${loading}</div><script type="module" src="/client.js"></script></body></html>`;
18
+ }
19
+ // node-only. The browser owns state and renders; the server is stateless. It serves the decl + client
20
+ // bundle, then for each `request` (just `{type, payload}` — no state shipped) resolves the event's
21
+ // `on(...)` handlers. Their context reads state by pulling one attribute from the client on demand
22
+ // (`read` ack), applies state with `edit`/`enable`, and re-emits produced events with `emit`.
23
+ export async function serve(knobkit, opts) {
24
+ const decl = declare(knobkit.config, knobkit.serverEvents());
25
+ let js = "";
26
+ let css = "";
27
+ try {
28
+ [js, css] = await Promise.all([readFile(CLIENT_JS, "utf8"), readFile(CLIENT_CSS, "utf8")]);
29
+ }
30
+ catch {
31
+ js = `document.getElementById("root").textContent="knobkit: build the browser client (pnpm -F knobkit build:client).";`;
32
+ }
33
+ const server = createServer(async (req, res) => {
34
+ if (req.url === "/api/decl") {
35
+ res.setHeader("content-type", "application/json");
36
+ res.end(JSON.stringify(decl));
37
+ }
38
+ else if (req.url === "/client.js") {
39
+ res.setHeader("content-type", "text/javascript; charset=utf-8");
40
+ res.end(js);
41
+ }
42
+ else if (req.url === "/client.css") {
43
+ res.setHeader("content-type", "text/css; charset=utf-8");
44
+ res.end(css);
45
+ }
46
+ else if (req.url?.startsWith("/assets/") && req.url.endsWith(".js")) {
47
+ // code-split chunks (e.g. lazy-loaded CodeMirror grammars). Resolve under dist/assets and
48
+ // confirm the result stays inside it before serving — never trust the request path.
49
+ const file = resolve(ASSETS, req.url.slice("/assets/".length).split("?")[0]);
50
+ if (!file.startsWith(ASSETS + sep)) {
51
+ res.writeHead(403, { "content-type": "text/plain" });
52
+ res.end("Forbidden");
53
+ return;
54
+ }
55
+ try {
56
+ const body = await readFile(file);
57
+ res.setHeader("content-type", "text/javascript; charset=utf-8");
58
+ res.setHeader("cache-control", "public, max-age=31536000, immutable"); // hashed name
59
+ res.end(body);
60
+ }
61
+ catch {
62
+ res.writeHead(404, { "content-type": "text/plain" });
63
+ res.end("Not found");
64
+ }
65
+ }
66
+ else if (req.url === "/" || req.url === "/index.html") {
67
+ res.setHeader("content-type", "text/html; charset=utf-8");
68
+ res.end(html(decl, knobkit.config.loading ?? ""));
69
+ }
70
+ else {
71
+ res.writeHead(404, { "content-type": "text/plain" });
72
+ res.end("Not found");
73
+ }
74
+ });
75
+ // A `read` ack can carry a large attribute (e.g. an uploaded image data URL), so lift socket.io's
76
+ // 1MB default clear of legitimate reads.
77
+ const io = new Server(server, { path: "/socket.io/", maxHttpBufferSize: 1e8 });
78
+ io.on("connection", (socket) => {
79
+ const bound = makeBound({
80
+ read: (key, path) => socket.timeout(15000).emitWithAck("read", { key, path }),
81
+ edit: (key, op, path, value) => void socket.emit("edit", { key, op, path, value }),
82
+ enable: (key, value) => void socket.emit("enable", { key, value }),
83
+ busy: (key, value) => void socket.emit("busy", { key, value }),
84
+ keyFor: (w) => knobkit.keyFor(w),
85
+ });
86
+ void (async () => {
87
+ for (const fn of knobkit.setups) {
88
+ try {
89
+ await run(bound, fn);
90
+ }
91
+ catch (err) {
92
+ console.error(err);
93
+ }
94
+ }
95
+ })();
96
+ socket.on("request", async (msg) => {
97
+ const { type } = msg ?? {};
98
+ if (typeof type !== "string")
99
+ return;
100
+ let { payload } = msg;
101
+ // socket.io delivers a typed-array payload (mic PCM samples) as a Buffer; restore the
102
+ // Float32Array the handler expects.
103
+ if (Buffer.isBuffer(payload)) {
104
+ const bytes = Uint8Array.from(payload);
105
+ payload = new Float32Array(bytes.buffer, 0, Math.floor(bytes.byteLength / 4));
106
+ }
107
+ try {
108
+ await run(bound, async () => {
109
+ for (const handler of knobkit.handlers.get(type) ?? []) {
110
+ const r = await handler(payload);
111
+ if (isEvent(r))
112
+ socket.emit("emit", r);
113
+ }
114
+ });
115
+ }
116
+ catch (err) {
117
+ console.error(err);
118
+ }
119
+ });
120
+ });
121
+ const port = opts?.port ?? 3000;
122
+ await new Promise((r) => server.listen(port, r));
123
+ const addr = server.address();
124
+ const boundPort = addr && typeof addr === "object" && addr ? addr.port : port;
125
+ const url = `http://localhost:${boundPort}/`;
126
+ console.log(`\n knobkit → ${url}\n`);
127
+ return {
128
+ url,
129
+ stop: () => new Promise((r) => io.close(() => r())),
130
+ };
131
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "knobkit",
3
+ "version": "0.0.1",
4
+ "description": "Rapid web app development — describe inputs and a function, get a web app.",
5
+ "license": "MIT",
6
+ "author": "Alain Brown",
7
+ "type": "module",
8
+ "types": "./dist/lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/lib/index.d.ts",
12
+ "default": "./dist/lib/index.js"
13
+ },
14
+ "./browser": {
15
+ "types": "./dist/lib/index.d.ts",
16
+ "default": "./dist/knobkit.browser.js"
17
+ },
18
+ "./browser.css": "./dist/knobkit.browser.css",
19
+ "./tsconfig.base.json": "./tsconfig.base.json",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "knobkit": "./dist/cli/index.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "tsconfig.base.json"
29
+ ],
30
+ "dependencies": {
31
+ "@codemirror/commands": "^6.10.3",
32
+ "@codemirror/lang-cpp": "^6.0.3",
33
+ "@codemirror/lang-css": "^6.3.1",
34
+ "@codemirror/lang-html": "^6.4.11",
35
+ "@codemirror/lang-javascript": "^6.2.5",
36
+ "@codemirror/lang-json": "^6.0.2",
37
+ "@codemirror/lang-markdown": "^6.5.0",
38
+ "@codemirror/lang-python": "^6.2.1",
39
+ "@codemirror/lang-rust": "^6.0.2",
40
+ "@codemirror/lang-sql": "^6.10.0",
41
+ "@codemirror/lang-xml": "^6.1.0",
42
+ "@codemirror/language": "^6.12.3",
43
+ "@codemirror/state": "^6.6.0",
44
+ "@codemirror/view": "^6.43.0",
45
+ "@revolist/react-datagrid": "^4.23.13",
46
+ "react": "^19.2.7",
47
+ "react-dom": "^19.2.7",
48
+ "react-markdown": "^10.1.0",
49
+ "recharts": "^3.8.1",
50
+ "remark-gfm": "^4.0.1",
51
+ "socket.io": "^4.8.3",
52
+ "socket.io-client": "^4.8.3",
53
+ "tsx": "^4.19.0",
54
+ "vite": "^8.0.16"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.10.0",
58
+ "@types/react": "^19.2.16",
59
+ "@types/react-dom": "^19.2.3",
60
+ "typescript": "^6.0.3",
61
+ "vitest": "^4.1.8"
62
+ },
63
+ "scripts": {
64
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
65
+ "build": "pnpm run clean && pnpm run build:lib && pnpm run build:client && pnpm run typecheck",
66
+ "build:lib": "tsc -p tsconfig.build.json",
67
+ "build:client": "vite build && vite build --config vite.browser.config.ts",
68
+ "typecheck": "tsc -p tsconfig.json --noEmit",
69
+ "test": "vitest run"
70
+ }
71
+ }
@@ -0,0 +1,83 @@
1
+ import { existsSync, writeFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ import { searchForWorkspaceRoot, type InlineConfig, type Plugin } from "vite";
5
+
6
+ export function ensureTsconfig(root: string): void {
7
+ const path = resolve(root, "tsconfig.json");
8
+ if (existsSync(path)) return;
9
+ const body = { extends: "knobkit/tsconfig.base.json", include: ["**/*.ts", "**/*.tsx"] };
10
+ writeFileSync(path, JSON.stringify(body, null, 2) + "\n");
11
+ console.log("knobkit: created tsconfig.json");
12
+ }
13
+
14
+ export function knobkitSource(root: string): string | null {
15
+ try {
16
+ const require = createRequire(resolve(root, "_.js"));
17
+ const src = resolve(dirname(require.resolve("knobkit/package.json")), "src/lib/index.ts");
18
+ return existsSync(src) ? src : null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ const tsSource: Plugin = {
25
+ name: "knobkit:ts-source",
26
+ enforce: "pre",
27
+ resolveId(source, importer) {
28
+ if (!importer || !source.startsWith(".") || !source.endsWith(".js")) return null;
29
+ const base = resolve(dirname(importer), source);
30
+ for (const cand of [base.replace(/\.js$/, ".tsx"), base.replace(/\.js$/, ".ts")]) {
31
+ if (existsSync(cand)) return cand;
32
+ }
33
+ return null;
34
+ },
35
+ };
36
+
37
+ export function indexHtml(entry: string): string {
38
+ return `<!doctype html>
39
+ <html lang="en">
40
+ <head>
41
+ <meta charset="utf-8" />
42
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
43
+ <title>knobkit</title>
44
+ </head>
45
+ <body>
46
+ <div id="root"></div>
47
+ <script type="module" src="/${entry}"></script>
48
+ </body>
49
+ </html>
50
+ `;
51
+ }
52
+
53
+ function virtualIndex(entry: string): Plugin {
54
+ return {
55
+ name: "knobkit:index",
56
+ configureServer(server) {
57
+ server.middlewares.use(async (req, res, next) => {
58
+ const url = req.url?.split("?")[0];
59
+ if (url !== "/" && url !== "/index.html") return next();
60
+ res.statusCode = 200;
61
+ res.setHeader("content-type", "text/html");
62
+ res.end(await server.transformIndexHtml(req.originalUrl ?? "/", indexHtml(entry)));
63
+ });
64
+ },
65
+ };
66
+ }
67
+
68
+ export function mountConfig(root: string, entry: string, ownHtml: boolean): InlineConfig {
69
+ const src = knobkitSource(root);
70
+ const entryRel = relative(root, entry);
71
+ return {
72
+ configFile: false,
73
+ root,
74
+ plugins: [tsSource, ...(ownHtml ? [] : [virtualIndex(entryRel)])],
75
+ esbuild: { jsx: "automatic" },
76
+ resolve: {
77
+ dedupe: ["react", "react-dom"],
78
+ ...(src ? { alias: [{ find: /^knobkit$/, replacement: src }] } : {}),
79
+ },
80
+ ...(ownHtml ? {} : { optimizeDeps: { entries: [entryRel] } }),
81
+ server: { fs: { allow: [searchForWorkspaceRoot(root)] } },
82
+ };
83
+ }
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { ensureTsconfig } from "./config.js";
5
+ import { buildMount, devMount } from "./mount.js";
6
+ import { runServe } from "./serve.js";
7
+
8
+ const HELP = `knobkit — build a web app from widgets and event handlers
9
+
10
+ Usage:
11
+ knobkit dev [file] Start a dev server (auto-detects mount vs serve)
12
+ knobkit build [file] Build a browser (mount) app to dist/
13
+ knobkit serve [file] Run a server (serve) app (same as: knobkit dev --serve)
14
+
15
+ file defaults to demo.tsx
16
+
17
+ Flags:
18
+ --mount Force browser (mount) mode
19
+ --serve Force server (serve) mode
20
+ --port <n> Dev server port (mount)
21
+ `;
22
+
23
+ interface Args {
24
+ file: string;
25
+ mount: boolean;
26
+ serve: boolean;
27
+ port?: number;
28
+ }
29
+
30
+ function parse(argv: string[]): Args {
31
+ const out: Args = { file: "demo.tsx", mount: false, serve: false };
32
+ let sawFile = false;
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const a = argv[i];
35
+ if (a === "--mount") out.mount = true;
36
+ else if (a === "--serve") out.serve = true;
37
+ else if (a === "--port") out.port = Number(argv[++i]);
38
+ else if (a.startsWith("--port=")) out.port = Number(a.slice("--port=".length));
39
+ else if (!a.startsWith("-") && !sawFile) {
40
+ out.file = a;
41
+ sawFile = true;
42
+ }
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function mode(file: string, args: Args): "mount" | "serve" {
48
+ if (args.serve) return "serve";
49
+ if (args.mount) return "mount";
50
+ return /\.serve\s*\(/.test(readFileSync(file, "utf8")) ? "serve" : "mount";
51
+ }
52
+
53
+ async function main(): Promise<void> {
54
+ const [cmd, ...rest] = process.argv.slice(2);
55
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
56
+ process.stdout.write(HELP);
57
+ return;
58
+ }
59
+ if (cmd !== "dev" && cmd !== "build" && cmd !== "serve") {
60
+ process.stderr.write(`knobkit: unknown command "${cmd}"\n\n${HELP}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ const args = parse(rest);
65
+ const root = process.cwd();
66
+ const file = resolve(root, args.file);
67
+ if (!existsSync(file)) {
68
+ process.stderr.write(`knobkit: no such file: ${args.file}\n`);
69
+ process.exit(1);
70
+ }
71
+ ensureTsconfig(root);
72
+
73
+ if (cmd === "build") return buildMount(root, file);
74
+ if (cmd === "serve") return runServe(file);
75
+ if (mode(file, args) === "serve") return runServe(file);
76
+ return devMount(root, file, args.port);
77
+ }
78
+
79
+ main().catch((err) => {
80
+ console.error(err);
81
+ process.exit(1);
82
+ });
@@ -0,0 +1,25 @@
1
+ import { existsSync, rmSync, writeFileSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+ import { build as viteBuild, createServer } from "vite";
4
+ import { indexHtml, mountConfig } from "./config.js";
5
+
6
+ export async function devMount(root: string, entry: string, port?: number): Promise<void> {
7
+ const ownHtml = existsSync(resolve(root, "index.html"));
8
+ const server = await createServer({ ...mountConfig(root, entry, ownHtml), server: { port } });
9
+ await server.listen();
10
+ server.printUrls();
11
+ }
12
+
13
+ export async function buildMount(root: string, entry: string): Promise<void> {
14
+ const htmlPath = resolve(root, "index.html");
15
+ const created = !existsSync(htmlPath);
16
+ if (created) writeFileSync(htmlPath, indexHtml(relative(root, entry)));
17
+ try {
18
+ await viteBuild({
19
+ ...mountConfig(root, entry, true),
20
+ build: { outDir: "dist", emptyOutDir: true },
21
+ });
22
+ } finally {
23
+ if (created) rmSync(htmlPath, { force: true });
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, resolve } from "node:path";
5
+
6
+ function tsxBin(): string {
7
+ const require = createRequire(import.meta.url);
8
+ const pkgPath = require.resolve("tsx/package.json");
9
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { bin: string | Record<string, string> };
10
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin.tsx;
11
+ return resolve(dirname(pkgPath), bin);
12
+ }
13
+
14
+ export function runServe(file: string): Promise<void> {
15
+ const child = spawn(process.execPath, [tsxBin(), "watch", file], { stdio: "inherit" });
16
+ return new Promise((res) => {
17
+ child.on("exit", (code) => {
18
+ if (code) process.exitCode = code;
19
+ res();
20
+ });
21
+ });
22
+ }
@@ -0,0 +1,70 @@
1
+ // @vitest-environment jsdom
2
+ import { test, expect } from "vitest";
3
+ import { act } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import { knobkit } from "../lib/knobkit.js";
6
+ import { declare } from "../lib/declare.js";
7
+ import { mic, log, chat, text, button, output, row, col } from "../lib/widgets/index.js";
8
+ import { App } from "./app.js";
9
+ import { createStore } from "./runtime.js";
10
+
11
+ test("App renders a live-like app (mic + log + chat) and reacts to edits", async () => {
12
+ const audio = mic();
13
+ const transcript = log();
14
+ const convo = chat();
15
+ const app = knobkit({ title: "Live", widgets: [audio, transcript, convo] });
16
+ const decl = declare(app.config, app.serverEvents());
17
+ const store = createStore(decl, () => {});
18
+
19
+ document.body.innerHTML = '<div id="root"></div>';
20
+ const root = createRoot(document.getElementById("root")!);
21
+
22
+ await act(async () => {
23
+ root.render(<App decl={decl} store={store} />);
24
+ });
25
+ expect(document.body.textContent).toContain("Live");
26
+ expect(document.querySelectorAll("button").length).toBeGreaterThan(0); // mic control
27
+ expect(document.querySelector("input")).not.toBeNull(); // chat composer
28
+
29
+ await act(async () => {
30
+ store.applyEdit(app.keyFor(transcript), "append", ["lines"], "hello");
31
+ store.applyEdit(app.keyFor(transcript), "append", ["lines"], "world");
32
+ });
33
+ expect(document.querySelector("pre")!.textContent).toMatch(/hello\s+world/);
34
+ });
35
+
36
+ test("nested layout renders: a row inside a col, children placed via slots", async () => {
37
+ const field = text();
38
+ const go = button({ label: "Go" });
39
+ const result = output();
40
+ const app = knobkit({ widgets: col(row(field, go), result) });
41
+ const decl = declare(app.config, app.serverEvents());
42
+ const store = createStore(decl, () => {});
43
+
44
+ document.body.innerHTML = '<div id="root"></div>';
45
+ const root = createRoot(document.getElementById("root")!);
46
+ await act(async () => {
47
+ root.render(<App decl={decl} store={store} />);
48
+ });
49
+
50
+ const rowEl = document.querySelector(".pu-layout-row");
51
+ expect(rowEl).not.toBeNull();
52
+ expect(rowEl!.querySelector("input")).not.toBeNull();
53
+ expect(rowEl!.querySelector("button")!.textContent).toContain("Go");
54
+ expect(document.querySelector(".pu-layout-col")).not.toBeNull();
55
+ expect(rowEl!.querySelector(".pu-output")).toBeNull();
56
+ });
57
+
58
+ test("mic({ control: false }) renders headless — no built-in button", async () => {
59
+ const audio = mic({ control: false });
60
+ const app = knobkit({ widgets: [audio] });
61
+ const decl = declare(app.config, app.serverEvents());
62
+ const store = createStore(decl, () => {});
63
+
64
+ document.body.innerHTML = '<div id="root"></div>';
65
+ const root = createRoot(document.getElementById("root")!);
66
+ await act(async () => {
67
+ root.render(<App decl={decl} store={store} />);
68
+ });
69
+ expect(document.querySelectorAll("button").length).toBe(0);
70
+ });